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 .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 = (
 
  44 def total_seconds_2_6(x):
 
  45     return x.microseconds / 1e6 + x.seconds + x.days * 24 * 3600
 
  48 class TasklibTest(unittest.TestCase):
 
  51         self.tmp = tempfile.mkdtemp(dir='.')
 
  52         self.tw = TaskWarrior(data_location=self.tmp, taskrc_location='/')
 
  55         shutil.rmtree(self.tmp)
 
  58 class TaskFilterTest(TasklibTest):
 
  60     def test_all_empty(self):
 
  61         self.assertEqual(len(self.tw.tasks.all()), 0)
 
  63     def test_all_non_empty(self):
 
  64         Task(self.tw, description="test task").save()
 
  65         self.assertEqual(len(self.tw.tasks.all()), 1)
 
  66         self.assertEqual(self.tw.tasks.all()[0]['description'], 'test task')
 
  67         self.assertEqual(self.tw.tasks.all()[0]['status'], 'pending')
 
  69     def test_pending_non_empty(self):
 
  70         Task(self.tw, description="test task").save()
 
  71         self.assertEqual(len(self.tw.tasks.pending()), 1)
 
  72         self.assertEqual(self.tw.tasks.pending()[0]['description'],
 
  74         self.assertEqual(self.tw.tasks.pending()[0]['status'], 'pending')
 
  76     def test_completed_empty(self):
 
  77         Task(self.tw, description="test task").save()
 
  78         self.assertEqual(len(self.tw.tasks.completed()), 0)
 
  80     def test_completed_non_empty(self):
 
  81         Task(self.tw, description="test task").save()
 
  82         self.assertEqual(len(self.tw.tasks.completed()), 0)
 
  83         self.tw.tasks.all()[0].done()
 
  84         self.assertEqual(len(self.tw.tasks.completed()), 1)
 
  86     def test_deleted_empty(self):
 
  87         Task(self.tw, description="test task").save()
 
  88         self.assertEqual(len(self.tw.tasks.deleted()), 0)
 
  90     def test_deleted_non_empty(self):
 
  91         Task(self.tw, description="test task").save()
 
  92         self.assertEqual(len(self.tw.tasks.deleted()), 0)
 
  93         self.tw.tasks.all()[0].delete()
 
  94         self.assertEqual(len(self.tw.tasks.deleted()), 1)
 
  96     def test_waiting_empty(self):
 
  97         Task(self.tw, description="test task").save()
 
  98         self.assertEqual(len(self.tw.tasks.waiting()), 0)
 
 100     def test_waiting_non_empty(self):
 
 101         Task(self.tw, description="test task").save()
 
 102         self.assertEqual(len(self.tw.tasks.waiting()), 0)
 
 104         t = self.tw.tasks.all()[0]
 
 105         t['wait'] = datetime.datetime.now() + datetime.timedelta(days=1)
 
 108         self.assertEqual(len(self.tw.tasks.waiting()), 1)
 
 110     def test_recurring_empty(self):
 
 111         Task(self.tw, description="test task").save()
 
 112         self.assertEqual(len(self.tw.tasks.recurring()), 0)
 
 114     def test_recurring_non_empty(self):
 
 115         Task(self.tw, description="test task", recur="daily",
 
 116              due=datetime.datetime.now()).save()
 
 117         self.assertEqual(len(self.tw.tasks.recurring()), 1)
 
 119     def test_filtering_by_attribute(self):
 
 120         Task(self.tw, description="no priority task").save()
 
 121         Task(self.tw, priority="H", description="high priority task").save()
 
 122         self.assertEqual(len(self.tw.tasks.all()), 2)
 
 124         # Assert that the correct number of tasks is returned
 
 125         self.assertEqual(len(self.tw.tasks.filter(priority="H")), 1)
 
 127         # Assert that the correct tasks are returned
 
 128         high_priority_task = self.tw.tasks.get(priority="H")
 
 129         self.assertEqual(high_priority_task['description'],
 
 130                          "high priority task")
 
 132     def test_filtering_by_empty_attribute(self):
 
 133         Task(self.tw, description="no priority task").save()
 
 134         Task(self.tw, priority="H", description="high priority task").save()
 
 135         self.assertEqual(len(self.tw.tasks.all()), 2)
 
 137         # Assert that the correct number of tasks is returned
 
 138         self.assertEqual(len(self.tw.tasks.filter(priority=None)), 1)
 
 140         # Assert that the correct tasks are returned
 
 141         no_priority_task = self.tw.tasks.get(priority=None)
 
 142         self.assertEqual(no_priority_task['description'], "no priority task")
 
 144     def test_filter_for_task_with_space_in_descripition(self):
 
 145         task = Task(self.tw, description="test task")
 
 148         filtered_task = self.tw.tasks.get(description="test task")
 
 149         self.assertEqual(filtered_task['description'], "test task")
 
 151     def test_filter_for_task_without_space_in_descripition(self):
 
 152         task = Task(self.tw, description="test")
 
 155         filtered_task = self.tw.tasks.get(description="test")
 
 156         self.assertEqual(filtered_task['description'], "test")
 
 158     def test_filter_for_task_with_space_in_project(self):
 
 159         task = Task(self.tw, description="test", project="random project")
 
 162         filtered_task = self.tw.tasks.get(project="random project")
 
 163         self.assertEqual(filtered_task['project'], "random project")
 
 165     def test_filter_for_task_without_space_in_project(self):
 
 166         task = Task(self.tw, description="test", project="random")
 
 169         filtered_task = self.tw.tasks.get(project="random")
 
 170         self.assertEqual(filtered_task['project'], "random")
 
 172     def test_filter_with_empty_uuid(self):
 
 173         self.assertRaises(ValueError, lambda: self.tw.tasks.get(uuid=''))
 
 175     def test_filter_dummy_by_status(self):
 
 176         t = Task(self.tw, description="test")
 
 179         tasks = self.tw.tasks.filter(status=t['status'])
 
 180         self.assertEqual(list(tasks), [t])
 
 182     def test_filter_dummy_by_uuid(self):
 
 183         t = Task(self.tw, description="test")
 
 186         tasks = self.tw.tasks.filter(uuid=t['uuid'])
 
 187         self.assertEqual(list(tasks), [t])
 
 189     def test_filter_dummy_by_entry(self):
 
 190         t = Task(self.tw, description="test")
 
 193         tasks = self.tw.tasks.filter(entry=t['entry'])
 
 194         self.assertEqual(list(tasks), [t])
 
 196     def test_filter_dummy_by_description(self):
 
 197         t = Task(self.tw, description="test")
 
 200         tasks = self.tw.tasks.filter(description=t['description'])
 
 201         self.assertEqual(list(tasks), [t])
 
 203     def test_filter_dummy_by_start(self):
 
 204         t = Task(self.tw, description="test")
 
 208         tasks = self.tw.tasks.filter(start=t['start'])
 
 209         self.assertEqual(list(tasks), [t])
 
 211     def test_filter_dummy_by_end(self):
 
 212         t = Task(self.tw, description="test")
 
 216         tasks = self.tw.tasks.filter(end=t['end'])
 
 217         self.assertEqual(list(tasks), [t])
 
 219     def test_filter_dummy_by_due(self):
 
 220         t = Task(self.tw, description="test", due=datetime.datetime.now())
 
 223         tasks = self.tw.tasks.filter(due=t['due'])
 
 224         self.assertEqual(list(tasks), [t])
 
 226     def test_filter_dummy_by_until(self):
 
 227         t = Task(self.tw, description="test")
 
 230         tasks = self.tw.tasks.filter(until=t['until'])
 
 231         self.assertEqual(list(tasks), [t])
 
 233     def test_filter_dummy_by_modified(self):
 
 234         # Older TW version does not support bumping modified
 
 236         if self.tw.version < six.text_type('2.2.0'):
 
 237             # Python2.6 does not support SkipTest. As a workaround
 
 238             # mark the test as passed by exiting.
 
 239             if getattr(unittest, 'SkipTest', None) is not None:
 
 240                 raise unittest.SkipTest()
 
 244         t = Task(self.tw, description="test")
 
 247         tasks = self.tw.tasks.filter(modified=t['modified'])
 
 248         self.assertEqual(list(tasks), [t])
 
 250     def test_filter_dummy_by_scheduled(self):
 
 251         t = Task(self.tw, description="test")
 
 254         tasks = self.tw.tasks.filter(scheduled=t['scheduled'])
 
 255         self.assertEqual(list(tasks), [t])
 
 257     def test_filter_dummy_by_tags(self):
 
 258         t = Task(self.tw, description="test", tags=["home"])
 
 261         tasks = self.tw.tasks.filter(tags=t['tags'])
 
 262         self.assertEqual(list(tasks), [t])
 
 264     def test_filter_dummy_by_projects(self):
 
 265         t = Task(self.tw, description="test", project="random")
 
 268         tasks = self.tw.tasks.filter(project=t['project'])
 
 269         self.assertEqual(list(tasks), [t])
 
 271     def test_filter_by_priority(self):
 
 272         t = Task(self.tw, description="test", priority="H")
 
 275         tasks = self.tw.tasks.filter(priority=t['priority'])
 
 276         self.assertEqual(list(tasks), [t])
 
 279 class TaskTest(TasklibTest):
 
 281     def test_create_unsaved_task(self):
 
 282         # Make sure a new task is not saved unless explicitly called for
 
 283         Task(self.tw, description="test task")
 
 284         self.assertEqual(len(self.tw.tasks.all()), 0)
 
 286     # TODO: once python 2.6 compatiblity is over, use context managers here
 
 287     #       and in all subsequent tests for assertRaises
 
 289     def test_delete_unsaved_task(self):
 
 290         t = Task(self.tw, description="test task")
 
 291         self.assertRaises(Task.NotSaved, t.delete)
 
 293     def test_complete_unsaved_task(self):
 
 294         t = Task(self.tw, description="test task")
 
 295         self.assertRaises(Task.NotSaved, t.done)
 
 297     def test_refresh_unsaved_task(self):
 
 298         t = Task(self.tw, description="test task")
 
 299         self.assertRaises(Task.NotSaved, t.refresh)
 
 301     def test_start_unsaved_task(self):
 
 302         t = Task(self.tw, description="test task")
 
 303         self.assertRaises(Task.NotSaved, t.start)
 
 305     def test_delete_deleted_task(self):
 
 306         t = Task(self.tw, description="test task")
 
 310         self.assertRaises(Task.DeletedTask, t.delete)
 
 312     def test_complete_completed_task(self):
 
 313         t = Task(self.tw, description="test task")
 
 317         self.assertRaises(Task.CompletedTask, t.done)
 
 319     def test_start_completed_task(self):
 
 320         t = Task(self.tw, description="test task")
 
 324         self.assertRaises(Task.CompletedTask, t.start)
 
 326     def test_add_completed_task(self):
 
 327         t = Task(self.tw, description="test", status="completed",
 
 328                  end=datetime.datetime.now())
 
 331     def test_add_multiple_completed_tasks(self):
 
 332         t1 = Task(self.tw, description="test1", status="completed",
 
 333                   end=datetime.datetime.now())
 
 334         t2 = Task(self.tw, description="test2", status="completed",
 
 335                   end=datetime.datetime.now())
 
 339     def test_complete_deleted_task(self):
 
 340         t = Task(self.tw, description="test task")
 
 344         self.assertRaises(Task.DeletedTask, t.done)
 
 346     def test_starting_task(self):
 
 347         t = Task(self.tw, description="test task")
 
 348         now = t.datetime_normalizer(datetime.datetime.now())
 
 352         self.assertTrue(now.replace(microsecond=0) <= t['start'])
 
 353         self.assertEqual(t['status'], 'pending')
 
 355     def test_completing_task(self):
 
 356         t = Task(self.tw, description="test task")
 
 357         now = t.datetime_normalizer(datetime.datetime.now())
 
 361         self.assertTrue(now.replace(microsecond=0) <= t['end'])
 
 362         self.assertEqual(t['status'], 'completed')
 
 364     def test_deleting_task(self):
 
 365         t = Task(self.tw, description="test task")
 
 366         now = t.datetime_normalizer(datetime.datetime.now())
 
 370         self.assertTrue(now.replace(microsecond=0) <= t['end'])
 
 371         self.assertEqual(t['status'], 'deleted')
 
 373     def test_started_task_active(self):
 
 374         t = Task(self.tw, description="test task")
 
 377         self.assertTrue(t.active)
 
 379     def test_unstarted_task_inactive(self):
 
 380         t = Task(self.tw, description="test task")
 
 381         self.assertFalse(t.active)
 
 383         self.assertFalse(t.active)
 
 385     def test_start_active_task(self):
 
 386         t = Task(self.tw, description="test task")
 
 389         self.assertRaises(Task.ActiveTask, t.start)
 
 391     def test_stop_completed_task(self):
 
 392         t = Task(self.tw, description="test task")
 
 397         self.assertRaises(Task.InactiveTask, t.stop)
 
 399         t = Task(self.tw, description="test task")
 
 403         self.assertRaises(Task.InactiveTask, t.stop)
 
 405     def test_stop_deleted_task(self):
 
 406         t = Task(self.tw, description="test task")
 
 412     def test_stop_inactive_task(self):
 
 413         t = Task(self.tw, description="test task")
 
 416         self.assertRaises(Task.InactiveTask, t.stop)
 
 418         t = Task(self.tw, description="test task")
 
 423         self.assertRaises(Task.InactiveTask, t.stop)
 
 425     def test_stopping_task(self):
 
 426         t = Task(self.tw, description="test task")
 
 427         t.datetime_normalizer(datetime.datetime.now())
 
 432         self.assertEqual(t['end'], None)
 
 433         self.assertEqual(t['status'], 'pending')
 
 434         self.assertFalse(t.active)
 
 436     def test_modify_simple_attribute_without_space(self):
 
 437         t = Task(self.tw, description="test")
 
 440         self.assertEquals(t['description'], "test")
 
 442         t['description'] = "test-modified"
 
 445         self.assertEquals(t['description'], "test-modified")
 
 447     def test_modify_simple_attribute_with_space(self):
 
 448         # Space can pose problems with parsing
 
 449         t = Task(self.tw, description="test task")
 
 452         self.assertEquals(t['description'], "test task")
 
 454         t['description'] = "test task modified"
 
 457         self.assertEquals(t['description'], "test task modified")
 
 459     def test_empty_dependency_set_of_unsaved_task(self):
 
 460         t = Task(self.tw, description="test task")
 
 461         self.assertEqual(t['depends'], set())
 
 463     def test_empty_dependency_set_of_saved_task(self):
 
 464         t = Task(self.tw, description="test task")
 
 466         self.assertEqual(t['depends'], set())
 
 468     def test_set_unsaved_task_as_dependency(self):
 
 469         # Adds only one dependency to task with no dependencies
 
 470         t = Task(self.tw, description="test task")
 
 471         dependency = Task(self.tw, description="needs to be done first")
 
 473         # We only save the parent task, dependency task is unsaved
 
 475         t['depends'] = set([dependency])
 
 477         self.assertRaises(Task.NotSaved, t.save)
 
 479     def test_set_simple_dependency_set(self):
 
 480         # Adds only one dependency to task with no dependencies
 
 481         t = Task(self.tw, description="test task")
 
 482         dependency = Task(self.tw, description="needs to be done first")
 
 487         t['depends'] = set([dependency])
 
 489         self.assertEqual(t['depends'], set([dependency]))
 
 491     def test_set_complex_dependency_set(self):
 
 492         # Adds two dependencies to task with no dependencies
 
 493         t = Task(self.tw, description="test task")
 
 494         dependency1 = Task(self.tw, description="needs to be done first")
 
 495         dependency2 = Task(self.tw, description="needs to be done second")
 
 501         t['depends'] = set([dependency1, dependency2])
 
 503         self.assertEqual(t['depends'], set([dependency1, dependency2]))
 
 505     def test_remove_from_dependency_set(self):
 
 506         # Removes dependency from task with two dependencies
 
 507         t = Task(self.tw, description="test task")
 
 508         dependency1 = Task(self.tw, description="needs to be done first")
 
 509         dependency2 = Task(self.tw, description="needs to be done second")
 
 514         t['depends'] = set([dependency1, dependency2])
 
 517         t['depends'].remove(dependency2)
 
 520         self.assertEqual(t['depends'], set([dependency1]))
 
 522     def test_add_to_dependency_set(self):
 
 523         # Adds dependency to task with one dependencies
 
 524         t = Task(self.tw, description="test task")
 
 525         dependency1 = Task(self.tw, description="needs to be done first")
 
 526         dependency2 = Task(self.tw, description="needs to be done second")
 
 531         t['depends'] = set([dependency1])
 
 534         t['depends'].add(dependency2)
 
 537         self.assertEqual(t['depends'], set([dependency1, dependency2]))
 
 539     def test_add_to_empty_dependency_set(self):
 
 540         # Adds dependency to task with one dependencies
 
 541         t = Task(self.tw, description="test task")
 
 542         dependency = Task(self.tw, description="needs to be done first")
 
 546         t['depends'].add(dependency)
 
 549         self.assertEqual(t['depends'], set([dependency]))
 
 551     def test_simple_dependency_set_save_repeatedly(self):
 
 552         # Adds only one dependency to task with no dependencies
 
 553         t = Task(self.tw, description="test task")
 
 554         dependency = Task(self.tw, description="needs to be done first")
 
 557         t['depends'] = set([dependency])
 
 560         # We taint the task, but keep depends intact
 
 561         t['description'] = "test task modified"
 
 564         self.assertEqual(t['depends'], set([dependency]))
 
 566         # We taint the task, but assign the same set to the depends
 
 567         t['depends'] = set([dependency])
 
 568         t['description'] = "test task modified again"
 
 571         self.assertEqual(t['depends'], set([dependency]))
 
 573     def test_compare_different_tasks(self):
 
 574         # Negative: compare two different tasks
 
 575         t1 = Task(self.tw, description="test task")
 
 576         t2 = Task(self.tw, description="test task")
 
 581         self.assertEqual(t1 == t2, False)
 
 583     def test_compare_same_task_object(self):
 
 584         # Compare Task object wit itself
 
 585         t = Task(self.tw, description="test task")
 
 588         self.assertEqual(t == t, True)
 
 590     def test_compare_same_task(self):
 
 591         # Compare the same task using two different objects
 
 592         t1 = Task(self.tw, description="test task")
 
 595         t2 = self.tw.tasks.get(uuid=t1['uuid'])
 
 596         self.assertEqual(t1 == t2, True)
 
 598     def test_compare_unsaved_tasks(self):
 
 599         # t1 and t2 are unsaved tasks, considered to be unequal
 
 600         # despite the content of data
 
 601         t1 = Task(self.tw, description="test task")
 
 602         t2 = Task(self.tw, description="test task")
 
 604         self.assertEqual(t1 == t2, False)
 
 606     def test_hash_unsaved_tasks(self):
 
 607         # Considered equal, it's the same object
 
 608         t1 = Task(self.tw, description="test task")
 
 610         self.assertEqual(hash(t1) == hash(t2), True)
 
 612     def test_hash_same_task(self):
 
 613         # Compare the hash of the task using two different objects
 
 614         t1 = Task(self.tw, description="test task")
 
 617         t2 = self.tw.tasks.get(uuid=t1['uuid'])
 
 618         self.assertEqual(t1.__hash__(), t2.__hash__())
 
 620     def test_hash_unequal_unsaved_tasks(self):
 
 621         # Compare the hash of the task using two different objects
 
 622         t1 = Task(self.tw, description="test task 1")
 
 623         t2 = Task(self.tw, description="test task 2")
 
 625         self.assertNotEqual(t1.__hash__(), t2.__hash__())
 
 627     def test_hash_unequal_saved_tasks(self):
 
 628         # Compare the hash of the task using two different objects
 
 629         t1 = Task(self.tw, description="test task 1")
 
 630         t2 = Task(self.tw, description="test task 2")
 
 635         self.assertNotEqual(t1.__hash__(), t2.__hash__())
 
 637     def test_adding_task_with_priority(self):
 
 638         t = Task(self.tw, description="test task", priority="M")
 
 641     def test_removing_priority_with_none(self):
 
 642         t = Task(self.tw, description="test task", priority="L")
 
 645         # Remove the priority mark
 
 649         # Assert that priority is not there after saving
 
 650         self.assertEqual(t['priority'], None)
 
 652     def test_adding_task_with_due_time(self):
 
 653         t = Task(self.tw, description="test task", due=datetime.datetime.now())
 
 656     def test_removing_due_time_with_none(self):
 
 657         t = Task(self.tw, description="test task", due=datetime.datetime.now())
 
 660         # Remove the due timestamp
 
 664         # Assert that due timestamp is no longer there
 
 665         self.assertEqual(t['due'], None)
 
 667     def test_modified_fields_new_task(self):
 
 670         # This should be empty with new task
 
 671         self.assertEqual(set(t._modified_fields), set())
 
 674         t['description'] = "test task"
 
 675         self.assertEqual(set(t._modified_fields), set(['description']))
 
 677         t['due'] = datetime.datetime(2014, 2, 14, 14, 14, 14)  # <3
 
 678         self.assertEqual(set(t._modified_fields), set(['description', 'due']))
 
 680         t['project'] = "test project"
 
 681         self.assertEqual(set(t._modified_fields),
 
 682                          set(['description', 'due', 'project']))
 
 684         # List of modified fields should clear out when saved
 
 686         self.assertEqual(set(t._modified_fields), set())
 
 688         # Reassigning the fields with the same values now should not produce
 
 690         t['description'] = "test task"
 
 691         t['due'] = datetime.datetime(2014, 2, 14, 14, 14, 14)  # <3
 
 692         t['project'] = "test project"
 
 693         self.assertEqual(set(t._modified_fields), set())
 
 695     def test_modified_fields_loaded_task(self):
 
 699         t['description'] = "test task"
 
 700         t['due'] = datetime.datetime(2014, 2, 14, 14, 14, 14)  # <3
 
 701         t['project'] = "test project"
 
 703         dependency = Task(self.tw, description="dependency")
 
 705         t['depends'] = set([dependency])
 
 707         # List of modified fields should clear out when saved
 
 709         self.assertEqual(set(t._modified_fields), set())
 
 711         # Get the task by using a filter by UUID
 
 712         self.tw.tasks.get(uuid=t['uuid'])
 
 714         # Reassigning the fields with the same values now should not produce
 
 716         t['description'] = "test task"
 
 717         t['due'] = datetime.datetime(2014, 2, 14, 14, 14, 14)  # <3
 
 718         t['project'] = "test project"
 
 719         t['depends'] = set([dependency])
 
 720         self.assertEqual(set(t._modified_fields), set())
 
 722     def test_modified_fields_not_affected_by_reading(self):
 
 725         for field in TASK_STANDARD_ATTRS:
 
 728         self.assertEqual(set(t._modified_fields), set())
 
 730     def test_setting_read_only_attrs_through_init(self):
 
 731         # Test that we are unable to set readonly attrs through __init__
 
 732         for readonly_key in Task.read_only_fields:
 
 733             kwargs = {'description': 'test task', readonly_key: 'value'}
 
 734             self.assertRaises(RuntimeError,
 
 735                               lambda: Task(self.tw, **kwargs))
 
 737     def test_setting_read_only_attrs_through_setitem(self):
 
 738         # Test that we are unable to set readonly attrs through __init__
 
 739         for readonly_key in Task.read_only_fields:
 
 740             t = Task(self.tw, description='test task')
 
 741             self.assertRaises(RuntimeError,
 
 742                               lambda: t.__setitem__(readonly_key, 'value'))
 
 744     def test_saving_unmodified_task(self):
 
 745         t = Task(self.tw, description="test task")
 
 749     def test_adding_tag_by_appending(self):
 
 750         t = Task(self.tw, description="test task", tags=['test1'])
 
 752         t['tags'].add('test2')
 
 754         self.assertEqual(t['tags'], set(['test1', 'test2']))
 
 756     def test_adding_tag_twice(self):
 
 757         t = Task(self.tw, description="test task", tags=['test1'])
 
 759         t['tags'].add('test2')
 
 760         t['tags'].add('test2')
 
 762         self.assertEqual(t['tags'], set(['test1', 'test2']))
 
 764     def test_adding_tag_by_appending_empty(self):
 
 765         t = Task(self.tw, description="test task")
 
 767         t['tags'].add('test')
 
 769         self.assertEqual(t['tags'], set(['test']))
 
 771     def test_serializers_returning_empty_string_for_none(self):
 
 772         # Test that any serializer returns '' when passed None
 
 774         serializers = [getattr(t, serializer_name) for serializer_name in
 
 775                        filter(lambda x: x.startswith('serialize_'), dir(t))]
 
 776         for serializer in serializers:
 
 777             self.assertEqual(serializer(None), '')
 
 779     def test_deserializer_returning_empty_value_for_empty_string(self):
 
 780         # Test that any deserializer returns empty value when passed ''
 
 782         deserializers = [getattr(t, deserializer_name) for deserializer_name in
 
 783                          filter(lambda x: x.startswith('deserialize_'), dir(t))]
 
 784         for deserializer in deserializers:
 
 785             self.assertTrue(deserializer('') in (None, [], set()))
 
 787     def test_normalizers_handling_none(self):
 
 788         # Test that any normalizer can handle None as a valid value
 
 791         for key in TASK_STANDARD_ATTRS:
 
 792             t._normalize(key, None)
 
 794     def test_recurrent_task_generation(self):
 
 795         today = datetime.date.today()
 
 796         t = Task(self.tw, description="brush teeth",
 
 797                  due=today, recur="daily")
 
 799         self.assertEqual(len(self.tw.tasks.pending()), 2)
 
 801     def test_spawned_task_parent(self):
 
 802         today = datetime.date.today()
 
 803         t = Task(self.tw, description="brush teeth",
 
 804                  due=today, recur="daily")
 
 807         spawned = self.tw.tasks.pending().get(due=today)
 
 808         assert spawned['parent'] == t
 
 810     def test_modify_number_of_tasks_at_once(self):
 
 811         for i in range(1, 100):
 
 812             Task(self.tw, description="test task %d" % i, tags=['test']).save()
 
 814         self.tw.execute_command(['+test', 'mod', 'unified', 'description'])
 
 816     def test_return_all_from_executed_command(self):
 
 817         Task(self.tw, description="test task", tags=['test']).save()
 
 818         out, err, rc = self.tw.execute_command(['count'], return_all=True)
 
 819         self.assertEqual(rc, 0)
 
 821     def test_return_all_from_failed_executed_command(self):
 
 822         Task(self.tw, description="test task", tags=['test']).save()
 
 823         out, err, rc = self.tw.execute_command(['countinvalid'],
 
 826         self.assertNotEqual(rc, 0)
 
 829 class TaskFromHookTest(TasklibTest):
 
 831     input_add_data = six.StringIO(
 
 832         '{"description":"Buy some milk",'
 
 833         '"entry":"20141118T050231Z",'
 
 834         '"status":"pending",'
 
 835         '"start":"20141119T152233Z",'
 
 836         '"uuid":"a360fc44-315c-4366-b70c-ea7e7520b749"}')
 
 838     input_add_data_recurring = six.StringIO(
 
 839         '{"description":"Mow the lawn",'
 
 840         '"entry":"20160210T224304Z",'
 
 841         '"parent":"62da6227-519c-42c2-915d-dccada926ad7",'
 
 843         '"status":"pending",'
 
 844         '"uuid":"81305335-0237-49ff-8e87-b3cdc2369cec"}')
 
 846     input_modify_data = six.StringIO(
 
 847         input_add_data.getvalue() + '\n' +
 
 848         '{"description":"Buy some milk finally",'
 
 849         '"entry":"20141118T050231Z",'
 
 850         '"status":"completed",'
 
 851         '"uuid":"a360fc44-315c-4366-b70c-ea7e7520b749"}')
 
 853     exported_raw_data = (
 
 855         '"due":"20150101T232323Z",'
 
 856         '"description":"test task"}')
 
 858     def test_setting_up_from_add_hook_input(self):
 
 859         t = Task.from_input(input_file=self.input_add_data, backend=self.tw)
 
 860         self.assertEqual(t['description'], "Buy some milk")
 
 861         self.assertEqual(t.pending, True)
 
 863     def test_setting_up_from_add_hook_input_recurring(self):
 
 864         t = Task.from_input(input_file=self.input_add_data_recurring,
 
 866         self.assertEqual(t['description'], "Mow the lawn")
 
 867         self.assertEqual(t.pending, True)
 
 869     def test_setting_up_from_modified_hook_input(self):
 
 870         t = Task.from_input(input_file=self.input_modify_data, modify=True,
 
 872         self.assertEqual(t['description'], "Buy some milk finally")
 
 873         self.assertEqual(t.pending, False)
 
 874         self.assertEqual(t.completed, True)
 
 876         self.assertEqual(t._original_data['status'], "pending")
 
 877         self.assertEqual(t._original_data['description'], "Buy some milk")
 
 878         self.assertEqual(set(t._modified_fields),
 
 879                          set(['status', 'description', 'start']))
 
 881     def test_export_data(self):
 
 882         t = Task(self.tw, description="test task",
 
 884                  due=pytz.utc.localize(datetime.datetime(2015, 1, 1,
 
 887         # Check that the output is a permutation of:
 
 888         # {"project":"Home","description":"test task","due":"20150101232323Z"}
 
 889         allowed_segments = self.exported_raw_data[1:-1].split(',')
 
 891             '{' + ','.join(segments) + '}'
 
 892             for segments in itertools.permutations(allowed_segments)
 
 895         self.assertTrue(any(t.export_data() == expected
 
 896                             for expected in allowed_output))
 
 899 class TimezoneAwareDatetimeTest(TasklibTest):
 
 902         super(TimezoneAwareDatetimeTest, self).setUp()
 
 903         self.zone = local_zone
 
 904         self.localdate_naive = datetime.datetime(2015, 2, 2)
 
 905         self.localtime_naive = datetime.datetime(2015, 2, 2, 0, 0, 0)
 
 906         self.localtime_aware = self.zone.localize(self.localtime_naive)
 
 907         self.utctime_aware = self.localtime_aware.astimezone(pytz.utc)
 
 909     def test_timezone_naive_datetime_setitem(self):
 
 910         t = Task(self.tw, description="test task")
 
 911         t['due'] = self.localtime_naive
 
 912         self.assertEqual(t['due'], self.localtime_aware)
 
 914     def test_timezone_naive_datetime_using_init(self):
 
 915         t = Task(self.tw, description="test task", due=self.localtime_naive)
 
 916         self.assertEqual(t['due'], self.localtime_aware)
 
 918     def test_filter_by_naive_datetime(self):
 
 919         t = Task(self.tw, description="task1", due=self.localtime_naive)
 
 921         matching_tasks = self.tw.tasks.filter(due=self.localtime_naive)
 
 922         self.assertEqual(len(matching_tasks), 1)
 
 924     def test_serialize_naive_datetime(self):
 
 925         t = Task(self.tw, description="task1", due=self.localtime_naive)
 
 926         self.assertEqual(json.loads(t.export_data())['due'],
 
 927                          self.utctime_aware.strftime(DATE_FORMAT))
 
 929     def test_timezone_naive_date_setitem(self):
 
 930         t = Task(self.tw, description="test task")
 
 931         t['due'] = self.localdate_naive
 
 932         self.assertEqual(t['due'], self.localtime_aware)
 
 934     def test_timezone_naive_date_using_init(self):
 
 935         t = Task(self.tw, description="test task", due=self.localdate_naive)
 
 936         self.assertEqual(t['due'], self.localtime_aware)
 
 938     def test_filter_by_naive_date(self):
 
 939         t = Task(self.tw, description="task1", due=self.localdate_naive)
 
 941         matching_tasks = self.tw.tasks.filter(due=self.localdate_naive)
 
 942         self.assertEqual(len(matching_tasks), 1)
 
 944     def test_serialize_naive_date(self):
 
 945         t = Task(self.tw, description="task1", due=self.localdate_naive)
 
 946         self.assertEqual(json.loads(t.export_data())['due'],
 
 947                          self.utctime_aware.strftime(DATE_FORMAT))
 
 949     def test_timezone_aware_datetime_setitem(self):
 
 950         t = Task(self.tw, description="test task")
 
 951         t['due'] = self.localtime_aware
 
 952         self.assertEqual(t['due'], self.localtime_aware)
 
 954     def test_timezone_aware_datetime_using_init(self):
 
 955         t = Task(self.tw, description="test task", due=self.localtime_aware)
 
 956         self.assertEqual(t['due'], self.localtime_aware)
 
 958     def test_filter_by_aware_datetime(self):
 
 959         t = Task(self.tw, description="task1", due=self.localtime_aware)
 
 961         matching_tasks = self.tw.tasks.filter(due=self.localtime_aware)
 
 962         self.assertEqual(len(matching_tasks), 1)
 
 964     def test_serialize_aware_datetime(self):
 
 965         t = Task(self.tw, description="task1", due=self.localtime_aware)
 
 966         self.assertEqual(json.loads(t.export_data())['due'],
 
 967                          self.utctime_aware.strftime(DATE_FORMAT))
 
 970 class DatetimeStringTest(TasklibTest):
 
 972     def test_simple_now_conversion(self):
 
 973         if self.tw.version < six.text_type('2.4.0'):
 
 974             # Python2.6 does not support SkipTest. As a workaround
 
 975             # mark the test as passed by exiting.
 
 976             if getattr(unittest, 'SkipTest', None) is not None:
 
 977                 raise unittest.SkipTest()
 
 981         t = Task(self.tw, description="test task", due="now")
 
 982         now = local_zone.localize(datetime.datetime.now())
 
 984         # Assert that both times are not more than 5 seconds apart
 
 985         if sys.version_info < (2, 7):
 
 986             self.assertTrue(total_seconds_2_6(now - t['due']) < 5)
 
 987             self.assertTrue(total_seconds_2_6(t['due'] - now) < 5)
 
 989             self.assertTrue((now - t['due']).total_seconds() < 5)
 
 990             self.assertTrue((t['due'] - now).total_seconds() < 5)
 
 992     def test_simple_eoy_conversion(self):
 
 993         if self.tw.version < six.text_type('2.4.0'):
 
 994             # Python2.6 does not support SkipTest. As a workaround
 
 995             # mark the test as passed by exiting.
 
 996             if getattr(unittest, 'SkipTest', None) is not None:
 
 997                 raise unittest.SkipTest()
 
1001         t = Task(self.tw, description="test task", due="eoy")
 
1002         now = local_zone.localize(datetime.datetime.now())
 
1003         eoy = local_zone.localize(datetime.datetime(
 
1011         self.assertEqual(eoy, t['due'])
 
1013     def test_complex_eoy_conversion(self):
 
1014         if self.tw.version < six.text_type('2.4.0'):
 
1015             # Python2.6 does not support SkipTest. As a workaround
 
1016             # mark the test as passed by exiting.
 
1017             if getattr(unittest, 'SkipTest', None) is not None:
 
1018                 raise unittest.SkipTest()
 
1022         t = Task(self.tw, description="test task", due="eoy - 4 months")
 
1023         now = local_zone.localize(datetime.datetime.now())
 
1024         due_date = local_zone.localize(datetime.datetime(
 
1031             )) - datetime.timedelta(0, 4 * 30 * 86400)
 
1032         self.assertEqual(due_date, t['due'])
 
1034     def test_filtering_with_string_datetime(self):
 
1035         if self.tw.version < six.text_type('2.4.0'):
 
1036             # Python2.6 does not support SkipTest. As a workaround
 
1037             # mark the test as passed by exiting.
 
1038             if getattr(unittest, 'SkipTest', None) is not None:
 
1039                 raise unittest.SkipTest()
 
1043         t = Task(self.tw, description="test task",
 
1044                  due=datetime.datetime.now() - datetime.timedelta(0, 2))
 
1046         self.assertEqual(len(self.tw.tasks.filter(due__before="now")), 1)
 
1049 class AnnotationTest(TasklibTest):
 
1052         super(AnnotationTest, self).setUp()
 
1053         Task(self.tw, description="test task").save()
 
1055     def test_adding_annotation(self):
 
1056         task = self.tw.tasks.get()
 
1057         task.add_annotation('test annotation')
 
1058         self.assertEqual(len(task['annotations']), 1)
 
1059         ann = task['annotations'][0]
 
1060         self.assertEqual(ann['description'], 'test annotation')
 
1062     def test_removing_annotation(self):
 
1063         task = self.tw.tasks.get()
 
1064         task.add_annotation('test annotation')
 
1065         ann = task['annotations'][0]
 
1067         self.assertEqual(len(task['annotations']), 0)
 
1069     def test_removing_annotation_by_description(self):
 
1070         task = self.tw.tasks.get()
 
1071         task.add_annotation('test annotation')
 
1072         task.remove_annotation('test annotation')
 
1073         self.assertEqual(len(task['annotations']), 0)
 
1075     def test_removing_annotation_by_obj(self):
 
1076         task = self.tw.tasks.get()
 
1077         task.add_annotation('test annotation')
 
1078         ann = task['annotations'][0]
 
1079         task.remove_annotation(ann)
 
1080         self.assertEqual(len(task['annotations']), 0)
 
1082     def test_annotation_after_modification(self):
 
1083         task = self.tw.tasks.get()
 
1084         task['project'] = 'test'
 
1085         task.add_annotation('I should really do this task')
 
1086         self.assertEqual(task['project'], 'test')
 
1088         self.assertEqual(task['project'], 'test')
 
1090     def test_serialize_annotations(self):
 
1091         # Test that serializing annotations is possible
 
1092         t = Task(self.tw, description="test")
 
1095         t.add_annotation("annotation1")
 
1096         t.add_annotation("annotation2")
 
1098         data = t._serialize('annotations', t._data['annotations'])
 
1100         self.assertEqual(len(data), 2)
 
1101         self.assertEqual(type(data[0]), dict)
 
1102         self.assertEqual(type(data[1]), dict)
 
1104         self.assertEqual(data[0]['description'], "annotation1")
 
1105         self.assertEqual(data[1]['description'], "annotation2")
 
1108 class UnicodeTest(TasklibTest):
 
1110     def test_unicode_task(self):
 
1111         Task(self.tw, description=six.u("†åßk")).save()
 
1114     def test_filter_by_unicode_task(self):
 
1115         Task(self.tw, description=six.u("†åßk")).save()
 
1116         tasks = self.tw.tasks.filter(description=six.u("†åßk"))
 
1117         self.assertEqual(len(tasks), 1)
 
1119     def test_non_unicode_task(self):
 
1120         Task(self.tw, description="test task").save()
 
1124 class ReadOnlyDictViewTest(unittest.TestCase):
 
1127         self.sample = dict(sample_list=[1, 2, 3], sample_dict={'key': 'value'})
 
1128         self.original_sample = copy.deepcopy(self.sample)
 
1129         self.view = ReadOnlyDictView(self.sample)
 
1131     def test_readonlydictview_getitem(self):
 
1132         sample_list = self.view['sample_list']
 
1133         self.assertEqual(sample_list, self.sample['sample_list'])
 
1135         # Assert that modification changed only copied value
 
1136         sample_list.append(4)
 
1137         self.assertNotEqual(sample_list, self.sample['sample_list'])
 
1139         # Assert that viewed dict is not changed
 
1140         self.assertEqual(self.sample, self.original_sample)
 
1142     def test_readonlydictview_contains(self):
 
1143         self.assertEqual('sample_list' in self.view,
 
1144                          'sample_list' in self.sample)
 
1145         self.assertEqual('sample_dict' in self.view,
 
1146                          'sample_dict' in self.sample)
 
1147         self.assertEqual('key' in self.view, 'key' in self.sample)
 
1149         # Assert that viewed dict is not changed
 
1150         self.assertEqual(self.sample, self.original_sample)
 
1152     def test_readonlydictview_iter(self):
 
1153         self.assertEqual(list(key for key in self.view),
 
1154                          list(key for key in self.sample))
 
1156         # Assert the view is correct after modification
 
1157         self.sample['new'] = 'value'
 
1158         self.assertEqual(list(key for key in self.view),
 
1159                          list(key for key in self.sample))
 
1161     def test_readonlydictview_len(self):
 
1162         self.assertEqual(len(self.view), len(self.sample))
 
1164         # Assert the view is correct after modification
 
1165         self.sample['new'] = 'value'
 
1166         self.assertEqual(len(self.view), len(self.sample))
 
1168     def test_readonlydictview_get(self):
 
1169         sample_list = self.view.get('sample_list')
 
1170         self.assertEqual(sample_list, self.sample.get('sample_list'))
 
1172         # Assert that modification changed only copied value
 
1173         sample_list.append(4)
 
1174         self.assertNotEqual(sample_list, self.sample.get('sample_list'))
 
1176         # Assert that viewed dict is not changed
 
1177         self.assertEqual(self.sample, self.original_sample)
 
1179     def test_readonlydict_items(self):
 
1180         view_items = self.view.items()
 
1181         sample_items = list(self.sample.items())
 
1182         self.assertEqual(view_items, sample_items)
 
1184         view_items.append('newkey')
 
1185         self.assertNotEqual(view_items, sample_items)
 
1186         self.assertEqual(self.sample, self.original_sample)
 
1188     def test_readonlydict_values(self):
 
1189         view_values = self.view.values()
 
1190         sample_values = list(self.sample.values())
 
1191         self.assertEqual(view_values, sample_values)
 
1193         view_list_item = list(filter(lambda x: type(x) is list,
 
1195         view_list_item.append(4)
 
1196         self.assertNotEqual(view_values, sample_values)
 
1197         self.assertEqual(self.sample, self.original_sample)
 
1200 class LazyUUIDTaskTest(TasklibTest):
 
1203         super(LazyUUIDTaskTest, self).setUp()
 
1205         self.stored = Task(self.tw, description="this is test task")
 
1208         self.lazy = LazyUUIDTask(self.tw, self.stored['uuid'])
 
1210     def test_uuid_non_conversion(self):
 
1211         assert self.stored['uuid'] == self.lazy['uuid']
 
1212         assert type(self.lazy) is LazyUUIDTask
 
1214     def test_lazy_explicit_conversion(self):
 
1215         assert type(self.lazy) is LazyUUIDTask
 
1217         assert type(self.lazy) is Task
 
1219     def test_conversion_key(self):
 
1220         assert self.stored['description'] == self.lazy['description']
 
1221         assert type(self.lazy) is Task
 
1223     def test_conversion_attribute(self):
 
1224         assert type(self.lazy) is LazyUUIDTask
 
1225         assert self.lazy.completed is False
 
1226         assert type(self.lazy) is Task
 
1228     def test_normal_to_lazy_equality(self):
 
1229         assert self.stored == self.lazy
 
1230         assert not self.stored != self.lazy
 
1231         assert type(self.lazy) is LazyUUIDTask
 
1233     def test_lazy_to_lazy_equality(self):
 
1234         lazy1 = LazyUUIDTask(self.tw, self.stored['uuid'])
 
1235         lazy2 = LazyUUIDTask(self.tw, self.stored['uuid'])
 
1237         assert lazy1 == lazy2
 
1238         assert not lazy1 != lazy2
 
1239         assert type(lazy1) is LazyUUIDTask
 
1240         assert type(lazy2) is LazyUUIDTask
 
1242     def test_normal_to_lazy_inequality(self):
 
1243         # Create a different UUID by changing the last letter
 
1244         wrong_uuid = self.stored['uuid']
 
1245         wrong_uuid = wrong_uuid[:-1] + ('a' if wrong_uuid[-1] != 'a' else 'b')
 
1247         wrong_lazy = LazyUUIDTask(self.tw, wrong_uuid)
 
1249         assert not self.stored == wrong_lazy
 
1250         assert self.stored != wrong_lazy
 
1251         assert type(wrong_lazy) is LazyUUIDTask
 
1253     def test_lazy_to_lazy_inequality(self):
 
1254         # Create a different UUID by changing the last letter
 
1255         wrong_uuid = self.stored['uuid']
 
1256         wrong_uuid = wrong_uuid[:-1] + ('a' if wrong_uuid[-1] != 'a' else 'b')
 
1258         lazy1 = LazyUUIDTask(self.tw, self.stored['uuid'])
 
1259         lazy2 = LazyUUIDTask(self.tw, wrong_uuid)
 
1261         assert not lazy1 == lazy2
 
1262         assert lazy1 != lazy2
 
1263         assert type(lazy1) is LazyUUIDTask
 
1264         assert type(lazy2) is LazyUUIDTask
 
1266     def test_lazy_in_queryset(self):
 
1267         tasks = self.tw.tasks.filter(uuid=self.stored['uuid'])
 
1269         assert self.lazy in tasks
 
1270         assert type(self.lazy) is LazyUUIDTask
 
1272     def test_lazy_saved(self):
 
1273         assert self.lazy.saved is True
 
1275     def test_lazy_modified(self):
 
1276         assert self.lazy.modified is False
 
1278     def test_lazy_modified_fields(self):
 
1279         assert self.lazy._modified_fields == set()
 
1282 class LazyUUIDTaskSetTest(TasklibTest):
 
1285         super(LazyUUIDTaskSetTest, self).setUp()
 
1287         self.task1 = Task(self.tw, description="task 1")
 
1288         self.task2 = Task(self.tw, description="task 2")
 
1289         self.task3 = Task(self.tw, description="task 3")
 
1301         self.lazy = LazyUUIDTaskSet(self.tw, self.uuids)
 
1303     def test_length(self):
 
1304         assert len(self.lazy) == 3
 
1305         assert type(self.lazy) is LazyUUIDTaskSet
 
1307     def test_contains(self):
 
1308         assert self.task1 in self.lazy
 
1309         assert self.task2 in self.lazy
 
1310         assert self.task3 in self.lazy
 
1311         assert type(self.lazy) is LazyUUIDTaskSet
 
1313     def test_eq_lazy(self):
 
1314         new_lazy = LazyUUIDTaskSet(self.tw, self.uuids)
 
1315         assert self.lazy == new_lazy
 
1316         assert not self.lazy != new_lazy
 
1317         assert type(self.lazy) is LazyUUIDTaskSet
 
1319     def test_eq_real(self):
 
1320         assert self.lazy == self.tw.tasks.all()
 
1321         assert self.tw.tasks.all() == self.lazy
 
1322         assert not self.lazy != self.tw.tasks.all()
 
1324         assert type(self.lazy) is LazyUUIDTaskSet
 
1326     def test_union(self):
 
1327         taskset = set([self.task1])
 
1328         lazyset = LazyUUIDTaskSet(
 
1330             (self.task2['uuid'], self.task3['uuid'])
 
1333         assert taskset | lazyset == self.lazy
 
1334         assert lazyset | taskset == self.lazy
 
1335         assert taskset.union(lazyset) == self.lazy
 
1336         assert lazyset.union(taskset) == self.lazy
 
1339         assert lazyset == self.lazy
 
1341     def test_difference(self):
 
1342         taskset = set([self.task1, self.task2])
 
1343         lazyset = LazyUUIDTaskSet(
 
1345             (self.task2['uuid'], self.task3['uuid'])
 
1348         assert taskset - lazyset == set([self.task1])
 
1349         assert lazyset - taskset == set([self.task3])
 
1350         assert taskset.difference(lazyset) == set([self.task1])
 
1351         assert lazyset.difference(taskset) == set([self.task3])
 
1354         assert lazyset == set([self.task3])
 
1356     def test_symmetric_difference(self):
 
1357         taskset = set([self.task1, self.task2])
 
1358         lazyset = LazyUUIDTaskSet(
 
1360             (self.task2['uuid'], self.task3['uuid'])
 
1363         assert taskset ^ lazyset == set([self.task1, self.task3])
 
1364         assert lazyset ^ taskset == set([self.task1, self.task3])
 
1365         assert taskset.symmetric_difference(lazyset) == set([self.task1,
 
1367         assert lazyset.symmetric_difference(taskset) == set([self.task1,
 
1371         assert lazyset == set([self.task1, self.task3])
 
1373     def test_intersection(self):
 
1374         taskset = set([self.task1, self.task2])
 
1375         lazyset = LazyUUIDTaskSet(
 
1377             (self.task2['uuid'], self.task3['uuid'])
 
1380         assert taskset & lazyset == set([self.task2])
 
1381         assert lazyset & taskset == set([self.task2])
 
1382         assert taskset.intersection(lazyset) == set([self.task2])
 
1383         assert lazyset.intersection(taskset) == set([self.task2])
 
1386         assert lazyset == set([self.task2])
 
1389 class TaskWarriorBackendTest(TasklibTest):
 
1391     def test_config(self):
 
1392         assert self.tw.config['nag'] == "You have more urgent tasks."
 
1393         assert self.tw.config['default.command'] == "next"
 
1394         assert self.tw.config['dependency.indicator'] == "D"