]> git.madduck.net Git - etc/taskwarrior.git/blob - tasklib/tests.py

madduck's git repository

Every one of the projects in this repository is available at the canonical URL git://git.madduck.net/madduck/pub/<projectpath> — see each project's metadata for the exact URL.

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.

SSH access, as well as push access can be individually arranged.

If you use my repositories frequently, consider adding the following snippet to ~/.gitconfig and using the third clone URL listed for each project:

[url "git://git.madduck.net/madduck/"]
  insteadOf = madduck:

Merge branch 'release/1.1.0' into develop
[etc/taskwarrior.git] / tasklib / tests.py
1 # coding=utf-8
2
3 import copy
4 import datetime
5 import itertools
6 import json
7 import pytz
8 import six
9 import shutil
10 import sys
11 import tempfile
12 import unittest
13
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
18
19 # http://taskwarrior.org/docs/design/task.html , Section: The Attributes
20 TASK_STANDARD_ATTRS = (
21     'status',
22     'uuid',
23     'entry',
24     'description',
25     'start',
26     'end',
27     'due',
28     'until',
29     'wait',
30     'modified',
31     'scheduled',
32     'recur',
33     'mask',
34     'imask',
35     'parent',
36     'project',
37     'priority',
38     'depends',
39     'tags',
40     'annotations',
41 )
42
43 total_seconds_2_6 = lambda x: x.microseconds / 1e6 + x.seconds + x.days * 24 * 3600
44
45
46 class TasklibTest(unittest.TestCase):
47
48     def setUp(self):
49         self.tmp = tempfile.mkdtemp(dir='.')
50         self.tw = TaskWarrior(data_location=self.tmp, taskrc_location='/')
51
52     def tearDown(self):
53         shutil.rmtree(self.tmp)
54
55
56 class TaskFilterTest(TasklibTest):
57
58     def test_all_empty(self):
59         self.assertEqual(len(self.tw.tasks.all()), 0)
60
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')
66
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'],
71                          'test task')
72         self.assertEqual(self.tw.tasks.pending()[0]['status'], 'pending')
73
74     def test_completed_empty(self):
75         Task(self.tw, description="test task").save()
76         self.assertEqual(len(self.tw.tasks.completed()), 0)
77
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)
83
84     def test_deleted_empty(self):
85         Task(self.tw, description="test task").save()
86         self.assertEqual(len(self.tw.tasks.deleted()), 0)
87
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)
93
94     def test_waiting_empty(self):
95         Task(self.tw, description="test task").save()
96         self.assertEqual(len(self.tw.tasks.waiting()), 0)
97
98     def test_waiting_non_empty(self):
99         Task(self.tw, description="test task").save()
100         self.assertEqual(len(self.tw.tasks.waiting()), 0)
101
102         t = self.tw.tasks.all()[0]
103         t['wait'] = datetime.datetime.now() + datetime.timedelta(days=1)
104         t.save()
105
106         self.assertEqual(len(self.tw.tasks.waiting()), 1)
107
108     def test_recurring_empty(self):
109         Task(self.tw, description="test task").save()
110         self.assertEqual(len(self.tw.tasks.recurring()), 0)
111
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)
116
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)
121
122         # Assert that the correct number of tasks is returned
123         self.assertEqual(len(self.tw.tasks.filter(priority="H")), 1)
124
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")
128
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)
133
134         # Assert that the correct number of tasks is returned
135         self.assertEqual(len(self.tw.tasks.filter(priority=None)), 1)
136
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")
140
141     def test_filter_for_task_with_space_in_descripition(self):
142         task = Task(self.tw, description="test task")
143         task.save()
144
145         filtered_task = self.tw.tasks.get(description="test task")
146         self.assertEqual(filtered_task['description'], "test task")
147
148     def test_filter_for_task_without_space_in_descripition(self):
149         task = Task(self.tw, description="test")
150         task.save()
151
152         filtered_task = self.tw.tasks.get(description="test")
153         self.assertEqual(filtered_task['description'], "test")
154
155     def test_filter_for_task_with_space_in_project(self):
156         task = Task(self.tw, description="test", project="random project")
157         task.save()
158
159         filtered_task = self.tw.tasks.get(project="random project")
160         self.assertEqual(filtered_task['project'], "random project")
161
162     def test_filter_for_task_without_space_in_project(self):
163         task = Task(self.tw, description="test", project="random")
164         task.save()
165
166         filtered_task = self.tw.tasks.get(project="random")
167         self.assertEqual(filtered_task['project'], "random")
168
169     def test_filter_with_empty_uuid(self):
170         self.assertRaises(ValueError, lambda: self.tw.tasks.get(uuid=''))
171
172     def test_filter_dummy_by_status(self):
173         t = Task(self.tw, description="test")
174         t.save()
175
176         tasks = self.tw.tasks.filter(status=t['status'])
177         self.assertEqual(list(tasks), [t])
178
179     def test_filter_dummy_by_uuid(self):
180         t = Task(self.tw, description="test")
181         t.save()
182
183         tasks = self.tw.tasks.filter(uuid=t['uuid'])
184         self.assertEqual(list(tasks), [t])
185
186     def test_filter_dummy_by_entry(self):
187         t = Task(self.tw, description="test")
188         t.save()
189
190         tasks = self.tw.tasks.filter(entry=t['entry'])
191         self.assertEqual(list(tasks), [t])
192
193     def test_filter_dummy_by_description(self):
194         t = Task(self.tw, description="test")
195         t.save()
196
197         tasks = self.tw.tasks.filter(description=t['description'])
198         self.assertEqual(list(tasks), [t])
199
200     def test_filter_dummy_by_start(self):
201         t = Task(self.tw, description="test")
202         t.save()
203         t.start()
204
205         tasks = self.tw.tasks.filter(start=t['start'])
206         self.assertEqual(list(tasks), [t])
207
208     def test_filter_dummy_by_end(self):
209         t = Task(self.tw, description="test")
210         t.save()
211         t.done()
212
213         tasks = self.tw.tasks.filter(end=t['end'])
214         self.assertEqual(list(tasks), [t])
215
216     def test_filter_dummy_by_due(self):
217         t = Task(self.tw, description="test", due=datetime.datetime.now())
218         t.save()
219
220         tasks = self.tw.tasks.filter(due=t['due'])
221         self.assertEqual(list(tasks), [t])
222
223     def test_filter_dummy_by_until(self):
224         t = Task(self.tw, description="test")
225         t.save()
226
227         tasks = self.tw.tasks.filter(until=t['until'])
228         self.assertEqual(list(tasks), [t])
229
230     def test_filter_dummy_by_modified(self):
231         # Older TW version does not support bumping modified
232         # on save
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()
238             else:
239                 return
240
241         t = Task(self.tw, description="test")
242         t.save()
243
244         tasks = self.tw.tasks.filter(modified=t['modified'])
245         self.assertEqual(list(tasks), [t])
246
247     def test_filter_dummy_by_scheduled(self):
248         t = Task(self.tw, description="test")
249         t.save()
250
251         tasks = self.tw.tasks.filter(scheduled=t['scheduled'])
252         self.assertEqual(list(tasks), [t])
253
254     def test_filter_dummy_by_tags(self):
255         t = Task(self.tw, description="test", tags=["home"])
256         t.save()
257
258         tasks = self.tw.tasks.filter(tags=t['tags'])
259         self.assertEqual(list(tasks), [t])
260
261     def test_filter_dummy_by_projects(self):
262         t = Task(self.tw, description="test", project="random")
263         t.save()
264
265         tasks = self.tw.tasks.filter(project=t['project'])
266         self.assertEqual(list(tasks), [t])
267
268     def test_filter_by_priority(self):
269         t = Task(self.tw, description="test", priority="H")
270         t.save()
271
272         tasks = self.tw.tasks.filter(priority=t['priority'])
273         self.assertEqual(list(tasks), [t])
274
275
276 class TaskTest(TasklibTest):
277
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)
282
283     # TODO: once python 2.6 compatiblity is over, use context managers here
284     #       and in all subsequent tests for assertRaises
285
286     def test_delete_unsaved_task(self):
287         t = Task(self.tw, description="test task")
288         self.assertRaises(Task.NotSaved, t.delete)
289
290     def test_complete_unsaved_task(self):
291         t = Task(self.tw, description="test task")
292         self.assertRaises(Task.NotSaved, t.done)
293
294     def test_refresh_unsaved_task(self):
295         t = Task(self.tw, description="test task")
296         self.assertRaises(Task.NotSaved, t.refresh)
297
298     def test_start_unsaved_task(self):
299         t = Task(self.tw, description="test task")
300         self.assertRaises(Task.NotSaved, t.start)
301
302     def test_delete_deleted_task(self):
303         t = Task(self.tw, description="test task")
304         t.save()
305         t.delete()
306
307         self.assertRaises(Task.DeletedTask, t.delete)
308
309     def test_complete_completed_task(self):
310         t = Task(self.tw, description="test task")
311         t.save()
312         t.done()
313
314         self.assertRaises(Task.CompletedTask, t.done)
315
316     def test_start_completed_task(self):
317         t = Task(self.tw, description="test task")
318         t.save()
319         t.done()
320
321         self.assertRaises(Task.CompletedTask, t.start)
322
323     def test_add_completed_task(self):
324         t = Task(self.tw, description="test", status="completed",
325                  end=datetime.datetime.now())
326         t.save()
327
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())
333         t1.save()
334         t2.save()
335
336     def test_complete_deleted_task(self):
337         t = Task(self.tw, description="test task")
338         t.save()
339         t.delete()
340
341         self.assertRaises(Task.DeletedTask, t.done)
342
343     def test_starting_task(self):
344         t = Task(self.tw, description="test task")
345         now = t.datetime_normalizer(datetime.datetime.now())
346         t.save()
347         t.start()
348
349         self.assertTrue(now.replace(microsecond=0) <= t['start'])
350         self.assertEqual(t['status'], 'pending')
351
352     def test_completing_task(self):
353         t = Task(self.tw, description="test task")
354         now = t.datetime_normalizer(datetime.datetime.now())
355         t.save()
356         t.done()
357
358         self.assertTrue(now.replace(microsecond=0) <= t['end'])
359         self.assertEqual(t['status'], 'completed')
360
361     def test_deleting_task(self):
362         t = Task(self.tw, description="test task")
363         now = t.datetime_normalizer(datetime.datetime.now())
364         t.save()
365         t.delete()
366
367         self.assertTrue(now.replace(microsecond=0) <= t['end'])
368         self.assertEqual(t['status'], 'deleted')
369
370     def test_started_task_active(self):
371         t = Task(self.tw, description="test task")
372         t.save()
373         t.start()
374         self.assertTrue(t.active)
375
376     def test_unstarted_task_inactive(self):
377         t = Task(self.tw, description="test task")
378         self.assertFalse(t.active)
379         t.save()
380         self.assertFalse(t.active)
381
382     def test_start_active_task(self):
383         t = Task(self.tw, description="test task")
384         t.save()
385         t.start()
386         self.assertRaises(Task.ActiveTask, t.start)
387
388     def test_stop_completed_task(self):
389         t = Task(self.tw, description="test task")
390         t.save()
391         t.start()
392         t.done()
393
394         self.assertRaises(Task.InactiveTask, t.stop)
395
396         t = Task(self.tw, description="test task")
397         t.save()
398         t.done()
399
400         self.assertRaises(Task.InactiveTask, t.stop)
401
402     def test_stop_deleted_task(self):
403         t = Task(self.tw, description="test task")
404         t.save()
405         t.start()
406         t.delete()
407         t.stop()
408
409     def test_stop_inactive_task(self):
410         t = Task(self.tw, description="test task")
411         t.save()
412
413         self.assertRaises(Task.InactiveTask, t.stop)
414
415         t = Task(self.tw, description="test task")
416         t.save()
417         t.start()
418         t.stop()
419
420         self.assertRaises(Task.InactiveTask, t.stop)
421
422     def test_stopping_task(self):
423         t = Task(self.tw, description="test task")
424         now = t.datetime_normalizer(datetime.datetime.now())
425         t.save()
426         t.start()
427         t.stop()
428
429         self.assertEqual(t['end'], None)
430         self.assertEqual(t['status'], 'pending')
431         self.assertFalse(t.active)
432
433     def test_modify_simple_attribute_without_space(self):
434         t = Task(self.tw, description="test")
435         t.save()
436
437         self.assertEquals(t['description'], "test")
438
439         t['description'] = "test-modified"
440         t.save()
441
442         self.assertEquals(t['description'], "test-modified")
443
444     def test_modify_simple_attribute_with_space(self):
445         # Space can pose problems with parsing
446         t = Task(self.tw, description="test task")
447         t.save()
448
449         self.assertEquals(t['description'], "test task")
450
451         t['description'] = "test task modified"
452         t.save()
453
454         self.assertEquals(t['description'], "test task modified")
455
456     def test_empty_dependency_set_of_unsaved_task(self):
457         t = Task(self.tw, description="test task")
458         self.assertEqual(t['depends'], set())
459
460     def test_empty_dependency_set_of_saved_task(self):
461         t = Task(self.tw, description="test task")
462         t.save()
463         self.assertEqual(t['depends'], set())
464
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")
469
470         # We only save the parent task, dependency task is unsaved
471         t.save()
472         t['depends'] = set([dependency])
473
474         self.assertRaises(Task.NotSaved, t.save)
475
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")
480
481         t.save()
482         dependency.save()
483
484         t['depends'] = set([dependency])
485
486         self.assertEqual(t['depends'], set([dependency]))
487
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")
493
494         t.save()
495         dependency1.save()
496         dependency2.save()
497
498         t['depends'] = set([dependency1, dependency2])
499
500         self.assertEqual(t['depends'], set([dependency1, dependency2]))
501
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")
507
508         dependency1.save()
509         dependency2.save()
510
511         t['depends'] = set([dependency1, dependency2])
512         t.save()
513
514         t['depends'].remove(dependency2)
515         t.save()
516
517         self.assertEqual(t['depends'], set([dependency1]))
518
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")
524
525         dependency1.save()
526         dependency2.save()
527
528         t['depends'] = set([dependency1])
529         t.save()
530
531         t['depends'].add(dependency2)
532         t.save()
533
534         self.assertEqual(t['depends'], set([dependency1, dependency2]))
535
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")
540
541         dependency.save()
542
543         t['depends'].add(dependency)
544         t.save()
545
546         self.assertEqual(t['depends'], set([dependency]))
547
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")
552         dependency.save()
553
554         t['depends'] = set([dependency])
555         t.save()
556
557         # We taint the task, but keep depends intact
558         t['description'] = "test task modified"
559         t.save()
560
561         self.assertEqual(t['depends'], set([dependency]))
562
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"
566         t.save()
567
568         self.assertEqual(t['depends'], set([dependency]))
569
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")
574
575         t1.save()
576         t2.save()
577
578         self.assertEqual(t1 == t2, False)
579
580     def test_compare_same_task_object(self):
581         # Compare Task object wit itself
582         t = Task(self.tw, description="test task")
583         t.save()
584
585         self.assertEqual(t == t, True)
586
587     def test_compare_same_task(self):
588         # Compare the same task using two different objects
589         t1 = Task(self.tw, description="test task")
590         t1.save()
591
592         t2 = self.tw.tasks.get(uuid=t1['uuid'])
593         self.assertEqual(t1 == t2, True)
594
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")
600
601         self.assertEqual(t1 == t2, False)
602
603     def test_hash_unsaved_tasks(self):
604         # Considered equal, it's the same object
605         t1 = Task(self.tw, description="test task")
606         t2 = t1
607         self.assertEqual(hash(t1) == hash(t2), True)
608
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")
612         t1.save()
613
614         t2 = self.tw.tasks.get(uuid=t1['uuid'])
615         self.assertEqual(t1.__hash__(), t2.__hash__())
616
617     def test_hash_unequal_unsaved_tasks(self):
618         # Compare the hash of the task using two different objects
619         t1 = Task(self.tw, description="test task 1")
620         t2 = Task(self.tw, description="test task 2")
621
622         self.assertNotEqual(t1.__hash__(), t2.__hash__())
623
624     def test_hash_unequal_saved_tasks(self):
625         # Compare the hash of the task using two different objects
626         t1 = Task(self.tw, description="test task 1")
627         t2 = Task(self.tw, description="test task 2")
628
629         t1.save()
630         t2.save()
631
632         self.assertNotEqual(t1.__hash__(), t2.__hash__())
633
634     def test_adding_task_with_priority(self):
635         t = Task(self.tw, description="test task", priority="M")
636         t.save()
637
638     def test_removing_priority_with_none(self):
639         t = Task(self.tw, description="test task", priority="L")
640         t.save()
641
642         # Remove the priority mark
643         t['priority'] = None
644         t.save()
645
646         # Assert that priority is not there after saving
647         self.assertEqual(t['priority'], None)
648
649     def test_adding_task_with_due_time(self):
650         t = Task(self.tw, description="test task", due=datetime.datetime.now())
651         t.save()
652
653     def test_removing_due_time_with_none(self):
654         t = Task(self.tw, description="test task", due=datetime.datetime.now())
655         t.save()
656
657         # Remove the due timestamp
658         t['due'] = None
659         t.save()
660
661         # Assert that due timestamp is no longer there
662         self.assertEqual(t['due'], None)
663
664     def test_modified_fields_new_task(self):
665         t = Task(self.tw)
666
667         # This should be empty with new task
668         self.assertEqual(set(t._modified_fields), set())
669
670         # Modify the task
671         t['description'] = "test task"
672         self.assertEqual(set(t._modified_fields), set(['description']))
673
674         t['due'] = datetime.datetime(2014, 2, 14, 14, 14, 14)  # <3
675         self.assertEqual(set(t._modified_fields), set(['description', 'due']))
676
677         t['project'] = "test project"
678         self.assertEqual(set(t._modified_fields),
679                          set(['description', 'due', 'project']))
680
681         # List of modified fields should clear out when saved
682         t.save()
683         self.assertEqual(set(t._modified_fields), set())
684
685         # Reassigning the fields with the same values now should not produce
686         # modified fields
687         t['description'] = "test task"
688         t['due'] = datetime.datetime(2014, 2, 14, 14, 14, 14)  # <3
689         t['project'] = "test project"
690         self.assertEqual(set(t._modified_fields), set())
691
692     def test_modified_fields_loaded_task(self):
693         t = Task(self.tw)
694
695         # Modify the task
696         t['description'] = "test task"
697         t['due'] = datetime.datetime(2014, 2, 14, 14, 14, 14)  # <3
698         t['project'] = "test project"
699
700         dependency = Task(self.tw, description="dependency")
701         dependency.save()
702         t['depends'] = set([dependency])
703
704         # List of modified fields should clear out when saved
705         t.save()
706         self.assertEqual(set(t._modified_fields), set())
707
708         # Get the task by using a filter by UUID
709         t2 = self.tw.tasks.get(uuid=t['uuid'])
710
711         # Reassigning the fields with the same values now should not produce
712         # modified fields
713         t['description'] = "test task"
714         t['due'] = datetime.datetime(2014, 2, 14, 14, 14, 14)  # <3
715         t['project'] = "test project"
716         t['depends'] = set([dependency])
717         self.assertEqual(set(t._modified_fields), set())
718
719     def test_modified_fields_not_affected_by_reading(self):
720         t = Task(self.tw)
721
722         for field in TASK_STANDARD_ATTRS:
723             value = t[field]
724
725         self.assertEqual(set(t._modified_fields), set())
726
727     def test_setting_read_only_attrs_through_init(self):
728         # Test that we are unable to set readonly attrs through __init__
729         for readonly_key in Task.read_only_fields:
730             kwargs = {'description': 'test task', readonly_key: 'value'}
731             self.assertRaises(RuntimeError,
732                               lambda: Task(self.tw, **kwargs))
733
734     def test_setting_read_only_attrs_through_setitem(self):
735         # Test that we are unable to set readonly attrs through __init__
736         for readonly_key in Task.read_only_fields:
737             t = Task(self.tw, description='test task')
738             self.assertRaises(RuntimeError,
739                               lambda: t.__setitem__(readonly_key, 'value'))
740
741     def test_saving_unmodified_task(self):
742         t = Task(self.tw, description="test task")
743         t.save()
744         t.save()
745
746     def test_adding_tag_by_appending(self):
747         t = Task(self.tw, description="test task", tags=['test1'])
748         t.save()
749         t['tags'].add('test2')
750         t.save()
751         self.assertEqual(t['tags'], set(['test1', 'test2']))
752
753     def test_adding_tag_twice(self):
754         t = Task(self.tw, description="test task", tags=['test1'])
755         t.save()
756         t['tags'].add('test2')
757         t['tags'].add('test2')
758         t.save()
759         self.assertEqual(t['tags'], set(['test1', 'test2']))
760
761     def test_adding_tag_by_appending_empty(self):
762         t = Task(self.tw, description="test task")
763         t.save()
764         t['tags'].add('test')
765         t.save()
766         self.assertEqual(t['tags'], set(['test']))
767
768     def test_serializers_returning_empty_string_for_none(self):
769         # Test that any serializer returns '' when passed None
770         t = Task(self.tw)
771         serializers = [getattr(t, serializer_name) for serializer_name in
772                        filter(lambda x: x.startswith('serialize_'), dir(t))]
773         for serializer in serializers:
774             self.assertEqual(serializer(None), '')
775
776     def test_deserializer_returning_empty_value_for_empty_string(self):
777         # Test that any deserializer returns empty value when passed ''
778         t = Task(self.tw)
779         deserializers = [getattr(t, deserializer_name) for deserializer_name in
780                         filter(lambda x: x.startswith('deserialize_'), dir(t))]
781         for deserializer in deserializers:
782             self.assertTrue(deserializer('') in (None, [], set()))
783
784     def test_normalizers_handling_none(self):
785         # Test that any normalizer can handle None as a valid value
786         t = Task(self.tw)
787
788         for key in TASK_STANDARD_ATTRS:
789             t._normalize(key, None)
790
791     def test_recurrent_task_generation(self):
792         today = datetime.date.today()
793         t = Task(self.tw, description="brush teeth",
794                  due=today, recur="daily")
795         t.save()
796         self.assertEqual(len(self.tw.tasks.pending()), 2)
797
798     def test_spawned_task_parent(self):
799         today = datetime.date.today()
800         t = Task(self.tw, description="brush teeth",
801                  due=today, recur="daily")
802         t.save()
803
804         spawned = self.tw.tasks.pending().get(due=today)
805         assert spawned['parent'] == t
806
807     def test_modify_number_of_tasks_at_once(self):
808         for i in range(1, 100):
809             Task(self.tw, description="test task %d" % i, tags=['test']).save()
810
811         self.tw.execute_command(['+test', 'mod', 'unified', 'description'])
812
813     def test_return_all_from_executed_command(self):
814         Task(self.tw, description="test task", tags=['test']).save()
815         out, err, rc = self.tw.execute_command(['count'], return_all=True)
816         self.assertEqual(rc, 0)
817
818     def test_return_all_from_failed_executed_command(self):
819         Task(self.tw, description="test task", tags=['test']).save()
820         out, err, rc = self.tw.execute_command(['countinvalid'],
821             return_all=True, allow_failure=False)
822         self.assertNotEqual(rc, 0)
823
824
825 class TaskFromHookTest(TasklibTest):
826
827     input_add_data = six.StringIO(
828         '{"description":"Buy some milk",'
829         '"entry":"20141118T050231Z",'
830         '"status":"pending",'
831         '"start":"20141119T152233Z",'
832         '"uuid":"a360fc44-315c-4366-b70c-ea7e7520b749"}')
833
834     input_add_data_recurring = six.StringIO(
835         '{"description":"Mow the lawn",'
836         '"entry":"20160210T224304Z",'
837         '"parent":"62da6227-519c-42c2-915d-dccada926ad7",'
838         '"recur":"weekly",'
839         '"status":"pending",'
840         '"uuid":"81305335-0237-49ff-8e87-b3cdc2369cec"}')
841
842     input_modify_data = six.StringIO(input_add_data.getvalue() + '\n' +
843         '{"description":"Buy some milk finally",'
844         '"entry":"20141118T050231Z",'
845         '"status":"completed",'
846         '"uuid":"a360fc44-315c-4366-b70c-ea7e7520b749"}')
847
848     exported_raw_data = (
849         '{"project":"Home",'
850          '"due":"20150101T232323Z",'
851          '"description":"test task"}')
852
853     def test_setting_up_from_add_hook_input(self):
854         t = Task.from_input(input_file=self.input_add_data, backend=self.tw)
855         self.assertEqual(t['description'], "Buy some milk")
856         self.assertEqual(t.pending, True)
857
858     def test_setting_up_from_add_hook_input_recurring(self):
859         t = Task.from_input(input_file=self.input_add_data_recurring,
860                             backend=self.tw)
861         self.assertEqual(t['description'], "Mow the lawn")
862         self.assertEqual(t.pending, True)
863
864     def test_setting_up_from_modified_hook_input(self):
865         t = Task.from_input(input_file=self.input_modify_data, modify=True,
866                             backend=self.tw)
867         self.assertEqual(t['description'], "Buy some milk finally")
868         self.assertEqual(t.pending, False)
869         self.assertEqual(t.completed, True)
870
871         self.assertEqual(t._original_data['status'], "pending")
872         self.assertEqual(t._original_data['description'], "Buy some milk")
873         self.assertEqual(set(t._modified_fields),
874                          set(['status', 'description', 'start']))
875
876     def test_export_data(self):
877         t = Task(self.tw, description="test task",
878             project="Home",
879             due=pytz.utc.localize(datetime.datetime(2015,1,1,23,23,23)))
880
881         # Check that the output is a permutation of:
882         # {"project":"Home","description":"test task","due":"20150101232323Z"}
883         allowed_segments = self.exported_raw_data[1:-1].split(',')
884         allowed_output = [
885             '{' + ','.join(segments) + '}'
886             for segments in itertools.permutations(allowed_segments)
887         ]
888
889         self.assertTrue(any(t.export_data() == expected
890                             for expected in allowed_output))
891
892 class TimezoneAwareDatetimeTest(TasklibTest):
893
894     def setUp(self):
895         super(TimezoneAwareDatetimeTest, self).setUp()
896         self.zone = local_zone
897         self.localdate_naive = datetime.datetime(2015,2,2)
898         self.localtime_naive = datetime.datetime(2015,2,2,0,0,0)
899         self.localtime_aware = self.zone.localize(self.localtime_naive)
900         self.utctime_aware = self.localtime_aware.astimezone(pytz.utc)
901
902     def test_timezone_naive_datetime_setitem(self):
903         t = Task(self.tw, description="test task")
904         t['due'] = self.localtime_naive
905         self.assertEqual(t['due'], self.localtime_aware)
906
907     def test_timezone_naive_datetime_using_init(self):
908         t = Task(self.tw, description="test task", due=self.localtime_naive)
909         self.assertEqual(t['due'], self.localtime_aware)
910
911     def test_filter_by_naive_datetime(self):
912         t = Task(self.tw, description="task1", due=self.localtime_naive)
913         t.save()
914         matching_tasks = self.tw.tasks.filter(due=self.localtime_naive)
915         self.assertEqual(len(matching_tasks), 1)
916
917     def test_serialize_naive_datetime(self):
918         t = Task(self.tw, description="task1", due=self.localtime_naive)
919         self.assertEqual(json.loads(t.export_data())['due'],
920                          self.utctime_aware.strftime(DATE_FORMAT))
921
922     def test_timezone_naive_date_setitem(self):
923         t = Task(self.tw, description="test task")
924         t['due'] = self.localdate_naive
925         self.assertEqual(t['due'], self.localtime_aware)
926
927     def test_timezone_naive_date_using_init(self):
928         t = Task(self.tw, description="test task", due=self.localdate_naive)
929         self.assertEqual(t['due'], self.localtime_aware)
930
931     def test_filter_by_naive_date(self):
932         t = Task(self.tw, description="task1", due=self.localdate_naive)
933         t.save()
934         matching_tasks = self.tw.tasks.filter(due=self.localdate_naive)
935         self.assertEqual(len(matching_tasks), 1)
936
937     def test_serialize_naive_date(self):
938         t = Task(self.tw, description="task1", due=self.localdate_naive)
939         self.assertEqual(json.loads(t.export_data())['due'],
940                          self.utctime_aware.strftime(DATE_FORMAT))
941
942     def test_timezone_aware_datetime_setitem(self):
943         t = Task(self.tw, description="test task")
944         t['due'] = self.localtime_aware
945         self.assertEqual(t['due'], self.localtime_aware)
946
947     def test_timezone_aware_datetime_using_init(self):
948         t = Task(self.tw, description="test task", due=self.localtime_aware)
949         self.assertEqual(t['due'], self.localtime_aware)
950
951     def test_filter_by_aware_datetime(self):
952         t = Task(self.tw, description="task1", due=self.localtime_aware)
953         t.save()
954         matching_tasks = self.tw.tasks.filter(due=self.localtime_aware)
955         self.assertEqual(len(matching_tasks), 1)
956
957     def test_serialize_aware_datetime(self):
958         t = Task(self.tw, description="task1", due=self.localtime_aware)
959         self.assertEqual(json.loads(t.export_data())['due'],
960                          self.utctime_aware.strftime(DATE_FORMAT))
961
962 class DatetimeStringTest(TasklibTest):
963
964     def test_simple_now_conversion(self):
965         if self.tw.version < six.text_type('2.4.0'):
966             # Python2.6 does not support SkipTest. As a workaround
967             # mark the test as passed by exiting.
968             if getattr(unittest, 'SkipTest', None) is not None:
969                 raise unittest.SkipTest()
970             else:
971                 return
972
973         t = Task(self.tw, description="test task", due="now")
974         now = local_zone.localize(datetime.datetime.now())
975
976         # Assert that both times are not more than 5 seconds apart
977         if sys.version_info < (2,7):
978             self.assertTrue(total_seconds_2_6(now - t['due']) < 5)
979             self.assertTrue(total_seconds_2_6(t['due'] - now) < 5)
980         else:
981             self.assertTrue((now - t['due']).total_seconds() < 5)
982             self.assertTrue((t['due'] - now).total_seconds() < 5)
983
984     def test_simple_eoy_conversion(self):
985         if self.tw.version < six.text_type('2.4.0'):
986             # Python2.6 does not support SkipTest. As a workaround
987             # mark the test as passed by exiting.
988             if getattr(unittest, 'SkipTest', None) is not None:
989                 raise unittest.SkipTest()
990             else:
991                 return
992
993         t = Task(self.tw, description="test task", due="eoy")
994         now = local_zone.localize(datetime.datetime.now())
995         eoy = local_zone.localize(datetime.datetime(
996             year=now.year,
997             month=12,
998             day=31,
999             hour=23,
1000             minute=59,
1001             second=59
1002             ))
1003         self.assertEqual(eoy, t['due'])
1004
1005     def test_complex_eoy_conversion(self):
1006         if self.tw.version < six.text_type('2.4.0'):
1007             # Python2.6 does not support SkipTest. As a workaround
1008             # mark the test as passed by exiting.
1009             if getattr(unittest, 'SkipTest', None) is not None:
1010                 raise unittest.SkipTest()
1011             else:
1012                 return
1013
1014         t = Task(self.tw, description="test task", due="eoy - 4 months")
1015         now = local_zone.localize(datetime.datetime.now())
1016         due_date = local_zone.localize(datetime.datetime(
1017             year=now.year,
1018             month=12,
1019             day=31,
1020             hour=23,
1021             minute=59,
1022             second=59
1023             )) - datetime.timedelta(0,4 * 30 * 86400)
1024         self.assertEqual(due_date, t['due'])
1025
1026     def test_filtering_with_string_datetime(self):
1027         if self.tw.version < six.text_type('2.4.0'):
1028             # Python2.6 does not support SkipTest. As a workaround
1029             # mark the test as passed by exiting.
1030             if getattr(unittest, 'SkipTest', None) is not None:
1031                 raise unittest.SkipTest()
1032             else:
1033                 return
1034
1035         t = Task(self.tw, description="test task",
1036                  due=datetime.datetime.now() - datetime.timedelta(0,2))
1037         t.save()
1038         self.assertEqual(len(self.tw.tasks.filter(due__before="now")), 1)
1039
1040 class AnnotationTest(TasklibTest):
1041
1042     def setUp(self):
1043         super(AnnotationTest, self).setUp()
1044         Task(self.tw, description="test task").save()
1045
1046     def test_adding_annotation(self):
1047         task = self.tw.tasks.get()
1048         task.add_annotation('test annotation')
1049         self.assertEqual(len(task['annotations']), 1)
1050         ann = task['annotations'][0]
1051         self.assertEqual(ann['description'], 'test annotation')
1052
1053     def test_removing_annotation(self):
1054         task = self.tw.tasks.get()
1055         task.add_annotation('test annotation')
1056         ann = task['annotations'][0]
1057         ann.remove()
1058         self.assertEqual(len(task['annotations']), 0)
1059
1060     def test_removing_annotation_by_description(self):
1061         task = self.tw.tasks.get()
1062         task.add_annotation('test annotation')
1063         task.remove_annotation('test annotation')
1064         self.assertEqual(len(task['annotations']), 0)
1065
1066     def test_removing_annotation_by_obj(self):
1067         task = self.tw.tasks.get()
1068         task.add_annotation('test annotation')
1069         ann = task['annotations'][0]
1070         task.remove_annotation(ann)
1071         self.assertEqual(len(task['annotations']), 0)
1072
1073     def test_annotation_after_modification(self):
1074          task = self.tw.tasks.get()
1075          task['project'] = 'test'
1076          task.add_annotation('I should really do this task')
1077          self.assertEqual(task['project'], 'test')
1078          task.save()
1079          self.assertEqual(task['project'], 'test')
1080
1081     def test_serialize_annotations(self):
1082         # Test that serializing annotations is possible
1083         t = Task(self.tw, description="test")
1084         t.save()
1085
1086         t.add_annotation("annotation1")
1087         t.add_annotation("annotation2")
1088
1089         data = t._serialize('annotations', t._data['annotations'])
1090
1091         self.assertEqual(len(data), 2)
1092         self.assertEqual(type(data[0]), dict)
1093         self.assertEqual(type(data[1]), dict)
1094
1095         self.assertEqual(data[0]['description'], "annotation1")
1096         self.assertEqual(data[1]['description'], "annotation2")
1097
1098
1099 class UnicodeTest(TasklibTest):
1100
1101     def test_unicode_task(self):
1102         Task(self.tw, description=six.u("†åßk")).save()
1103         self.tw.tasks.get()
1104
1105     def test_filter_by_unicode_task(self):
1106         Task(self.tw, description=six.u("†åßk")).save()
1107         tasks = self.tw.tasks.filter(description=six.u("†åßk"))
1108         self.assertEqual(len(tasks), 1)
1109
1110     def test_non_unicode_task(self):
1111         Task(self.tw, description="test task").save()
1112         self.tw.tasks.get()
1113
1114 class ReadOnlyDictViewTest(unittest.TestCase):
1115
1116     def setUp(self):
1117         self.sample = dict(l=[1,2,3], d={'k':'v'})
1118         self.original_sample = copy.deepcopy(self.sample)
1119         self.view = ReadOnlyDictView(self.sample)
1120
1121     def test_readonlydictview_getitem(self):
1122         l = self.view['l']
1123         self.assertEqual(l, self.sample['l'])
1124
1125         # Assert that modification changed only copied value
1126         l.append(4)
1127         self.assertNotEqual(l, self.sample['l'])
1128
1129         # Assert that viewed dict is not changed
1130         self.assertEqual(self.sample, self.original_sample)
1131
1132     def test_readonlydictview_contains(self):
1133         self.assertEqual('l' in self.view, 'l' in self.sample)
1134         self.assertEqual('d' in self.view, 'd' in self.sample)
1135         self.assertEqual('k' in self.view, 'k' in self.sample)
1136
1137         # Assert that viewed dict is not changed
1138         self.assertEqual(self.sample, self.original_sample)
1139
1140     def test_readonlydictview_iter(self):
1141         self.assertEqual(list(k for k in self.view),
1142                          list(k for k in self.sample))
1143
1144         # Assert the view is correct after modification
1145         self.sample['new'] = 'value'
1146         self.assertEqual(list(k for k in self.view),
1147                          list(k for k in self.sample))
1148
1149     def test_readonlydictview_len(self):
1150         self.assertEqual(len(self.view), len(self.sample))
1151
1152         # Assert the view is correct after modification
1153         self.sample['new'] = 'value'
1154         self.assertEqual(len(self.view), len(self.sample))
1155
1156     def test_readonlydictview_get(self):
1157         l = self.view.get('l')
1158         self.assertEqual(l, self.sample.get('l'))
1159
1160         # Assert that modification changed only copied value
1161         l.append(4)
1162         self.assertNotEqual(l, self.sample.get('l'))
1163
1164         # Assert that viewed dict is not changed
1165         self.assertEqual(self.sample, self.original_sample)
1166
1167     def test_readonlydict_items(self):
1168         view_items = self.view.items()
1169         sample_items = list(self.sample.items())
1170         self.assertEqual(view_items, sample_items)
1171
1172         view_items.append('newkey')
1173         self.assertNotEqual(view_items, sample_items)
1174         self.assertEqual(self.sample, self.original_sample)
1175
1176     def test_readonlydict_values(self):
1177         view_values = self.view.values()
1178         sample_values = list(self.sample.values())
1179         self.assertEqual(view_values, sample_values)
1180
1181         view_list_item = list(filter(lambda x: type(x) is list,
1182                                      view_values))[0]
1183         view_list_item.append(4)
1184         self.assertNotEqual(view_values, sample_values)
1185         self.assertEqual(self.sample, self.original_sample)
1186
1187
1188 class LazyUUIDTaskTest(TasklibTest):
1189
1190     def setUp(self):
1191         super(LazyUUIDTaskTest, self).setUp()
1192
1193         self.stored = Task(self.tw, description="this is test task")
1194         self.stored.save()
1195
1196         self.lazy = LazyUUIDTask(self.tw, self.stored['uuid'])
1197
1198     def test_uuid_non_conversion(self):
1199         assert self.stored['uuid'] == self.lazy['uuid']
1200         assert type(self.lazy) is LazyUUIDTask
1201
1202     def test_lazy_explicit_conversion(self):
1203         assert type(self.lazy) is LazyUUIDTask
1204         self.lazy.replace()
1205         assert type(self.lazy) is Task
1206
1207     def test_conversion_key(self):
1208         assert self.stored['description'] == self.lazy['description']
1209         assert type(self.lazy) is Task
1210
1211     def test_conversion_attribute(self):
1212         assert type(self.lazy) is LazyUUIDTask
1213         assert self.lazy.completed is False
1214         assert type(self.lazy) is Task
1215
1216     def test_normal_to_lazy_equality(self):
1217         assert self.stored == self.lazy
1218         assert not self.stored != self.lazy
1219         assert type(self.lazy) is LazyUUIDTask
1220
1221     def test_lazy_to_lazy_equality(self):
1222         lazy1 = LazyUUIDTask(self.tw, self.stored['uuid'])
1223         lazy2 = LazyUUIDTask(self.tw, self.stored['uuid'])
1224
1225         assert lazy1 == lazy2
1226         assert not lazy1 != lazy2
1227         assert type(lazy1) is LazyUUIDTask
1228         assert type(lazy2) is LazyUUIDTask
1229
1230     def test_normal_to_lazy_inequality(self):
1231         # Create a different UUID by changing the last letter
1232         wrong_uuid = self.stored['uuid']
1233         wrong_uuid = wrong_uuid[:-1] + ('a' if wrong_uuid[-1] != 'a' else 'b')
1234
1235         wrong_lazy = LazyUUIDTask(self.tw, wrong_uuid)
1236
1237         assert not self.stored == wrong_lazy
1238         assert self.stored != wrong_lazy
1239         assert type(wrong_lazy) is LazyUUIDTask
1240
1241     def test_lazy_to_lazy_inequality(self):
1242         # Create a different UUID by changing the last letter
1243         wrong_uuid = self.stored['uuid']
1244         wrong_uuid = wrong_uuid[:-1] + ('a' if wrong_uuid[-1] != 'a' else 'b')
1245
1246         lazy1 = LazyUUIDTask(self.tw, self.stored['uuid'])
1247         lazy2 = LazyUUIDTask(self.tw, wrong_uuid)
1248
1249         assert not lazy1 == lazy2
1250         assert lazy1 != lazy2
1251         assert type(lazy1) is LazyUUIDTask
1252         assert type(lazy2) is LazyUUIDTask
1253
1254     def test_lazy_in_queryset(self):
1255         tasks = self.tw.tasks.filter(uuid=self.stored['uuid'])
1256
1257         assert self.lazy in tasks
1258         assert type(self.lazy) is LazyUUIDTask
1259
1260     def test_lazy_saved(self):
1261         assert self.lazy.saved is True
1262
1263     def test_lazy_modified(self):
1264         assert self.lazy.modified is False
1265
1266     def test_lazy_modified_fields(self):
1267         assert self.lazy._modified_fields == set()
1268
1269
1270 class LazyUUIDTaskSetTest(TasklibTest):
1271
1272     def setUp(self):
1273         super(LazyUUIDTaskSetTest, self).setUp()
1274
1275         self.task1 = Task(self.tw, description="task 1")
1276         self.task2 = Task(self.tw, description="task 2")
1277         self.task3 = Task(self.tw, description="task 3")
1278
1279         self.task1.save()
1280         self.task2.save()
1281         self.task3.save()
1282
1283         self.uuids = (
1284             self.task1['uuid'],
1285             self.task2['uuid'],
1286             self.task3['uuid']
1287         )
1288
1289         self.lazy = LazyUUIDTaskSet(self.tw, self.uuids)
1290
1291     def test_length(self):
1292         assert len(self.lazy) == 3
1293         assert type(self.lazy) is LazyUUIDTaskSet
1294
1295     def test_contains(self):
1296         assert self.task1 in self.lazy
1297         assert self.task2 in self.lazy
1298         assert self.task3 in self.lazy
1299         assert type(self.lazy) is LazyUUIDTaskSet
1300
1301     def test_eq_lazy(self):
1302         new_lazy = LazyUUIDTaskSet(self.tw, self.uuids)
1303         assert self.lazy == new_lazy
1304         assert not self.lazy != new_lazy
1305         assert type(self.lazy) is LazyUUIDTaskSet
1306
1307     def test_eq_real(self):
1308         assert self.lazy == self.tw.tasks.all()
1309         assert self.tw.tasks.all() == self.lazy
1310         assert not self.lazy != self.tw.tasks.all()
1311
1312         assert type(self.lazy) is LazyUUIDTaskSet
1313
1314     def test_union(self):
1315         taskset = set([self.task1])
1316         lazyset = LazyUUIDTaskSet(
1317             self.tw,
1318             (self.task2['uuid'], self.task3['uuid'])
1319         )
1320
1321         assert taskset | lazyset == self.lazy
1322         assert lazyset | taskset == self.lazy
1323         assert taskset.union(lazyset) == self.lazy
1324         assert lazyset.union(taskset) == self.lazy
1325
1326         lazyset |= taskset
1327         assert lazyset == self.lazy
1328
1329     def test_difference(self):
1330         taskset = set([self.task1, self.task2])
1331         lazyset = LazyUUIDTaskSet(
1332             self.tw,
1333             (self.task2['uuid'], self.task3['uuid'])
1334         )
1335
1336         assert taskset - lazyset == set([self.task1])
1337         assert lazyset - taskset == set([self.task3])
1338         assert taskset.difference(lazyset) == set([self.task1])
1339         assert lazyset.difference(taskset) == set([self.task3])
1340
1341         lazyset -= taskset
1342         assert lazyset == set([self.task3])
1343
1344     def test_symmetric_difference(self):
1345         taskset = set([self.task1, self.task2])
1346         lazyset = LazyUUIDTaskSet(
1347             self.tw,
1348             (self.task2['uuid'], self.task3['uuid'])
1349         )
1350
1351         assert taskset ^ lazyset == set([self.task1, self.task3])
1352         assert lazyset ^ taskset == set([self.task1, self.task3])
1353         assert taskset.symmetric_difference(lazyset) == set([self.task1, self.task3])
1354         assert lazyset.symmetric_difference(taskset) == set([self.task1, self.task3])
1355
1356         lazyset ^= taskset
1357         assert lazyset == set([self.task1, self.task3])
1358
1359     def test_intersection(self):
1360         taskset = set([self.task1, self.task2])
1361         lazyset = LazyUUIDTaskSet(
1362             self.tw,
1363             (self.task2['uuid'], self.task3['uuid'])
1364         )
1365
1366         assert taskset & lazyset == set([self.task2])
1367         assert lazyset & taskset == set([self.task2])
1368         assert taskset.intersection(lazyset) == set([self.task2])
1369         assert lazyset.intersection(taskset) == set([self.task2])
1370
1371         lazyset &= taskset
1372         assert lazyset == set([self.task2])
1373
1374
1375 class TaskWarriorBackendTest(TasklibTest):
1376
1377     def test_config(self):
1378         assert self.tw.config['nag'] == "You have more urgent tasks."
1379         assert self.tw.config['default.command'] == "next"
1380         assert self.tw.config['dependency.indicator'] == "D"