]> 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:

7c8740098ccd5654abd8a0baef4d08cf71d09266
[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 tempfile
11 import unittest
12
13 from .task import TaskWarrior, Task, ReadOnlyDictView, local_zone, DATE_FORMAT
14
15 # http://taskwarrior.org/docs/design/task.html , Section: The Attributes
16 TASK_STANDARD_ATTRS = (
17     'status',
18     'uuid',
19     'entry',
20     'description',
21     'start',
22     'end',
23     'due',
24     'until',
25     'wait',
26     'modified',
27     'scheduled',
28     'recur',
29     'mask',
30     'imask',
31     'parent',
32     'project',
33     'priority',
34     'depends',
35     'tags',
36     'annotations',
37 )
38
39 class TasklibTest(unittest.TestCase):
40
41     def setUp(self):
42         self.tmp = tempfile.mkdtemp(dir='.')
43         self.tw = TaskWarrior(data_location=self.tmp)
44
45     def tearDown(self):
46         shutil.rmtree(self.tmp)
47
48
49 class TaskFilterTest(TasklibTest):
50
51     def test_all_empty(self):
52         self.assertEqual(len(self.tw.tasks.all()), 0)
53
54     def test_all_non_empty(self):
55         Task(self.tw, description="test task").save()
56         self.assertEqual(len(self.tw.tasks.all()), 1)
57         self.assertEqual(self.tw.tasks.all()[0]['description'], 'test task')
58         self.assertEqual(self.tw.tasks.all()[0]['status'], 'pending')
59
60     def test_pending_non_empty(self):
61         Task(self.tw, description="test task").save()
62         self.assertEqual(len(self.tw.tasks.pending()), 1)
63         self.assertEqual(self.tw.tasks.pending()[0]['description'],
64                          'test task')
65         self.assertEqual(self.tw.tasks.pending()[0]['status'], 'pending')
66
67     def test_completed_empty(self):
68         Task(self.tw, description="test task").save()
69         self.assertEqual(len(self.tw.tasks.completed()), 0)
70
71     def test_completed_non_empty(self):
72         Task(self.tw, description="test task").save()
73         self.assertEqual(len(self.tw.tasks.completed()), 0)
74         self.tw.tasks.all()[0].done()
75         self.assertEqual(len(self.tw.tasks.completed()), 1)
76
77     def test_filtering_by_attribute(self):
78         Task(self.tw, description="no priority task").save()
79         Task(self.tw, priority="H", description="high priority task").save()
80         self.assertEqual(len(self.tw.tasks.all()), 2)
81
82         # Assert that the correct number of tasks is returned
83         self.assertEqual(len(self.tw.tasks.filter(priority="H")), 1)
84
85         # Assert that the correct tasks are returned
86         high_priority_task = self.tw.tasks.get(priority="H")
87         self.assertEqual(high_priority_task['description'], "high priority task")
88
89     def test_filtering_by_empty_attribute(self):
90         Task(self.tw, description="no priority task").save()
91         Task(self.tw, priority="H", description="high priority task").save()
92         self.assertEqual(len(self.tw.tasks.all()), 2)
93
94         # Assert that the correct number of tasks is returned
95         self.assertEqual(len(self.tw.tasks.filter(priority=None)), 1)
96
97         # Assert that the correct tasks are returned
98         no_priority_task = self.tw.tasks.get(priority=None)
99         self.assertEqual(no_priority_task['description'], "no priority task")
100
101     def test_filter_for_task_with_space_in_descripition(self):
102         task = Task(self.tw, description="test task")
103         task.save()
104
105         filtered_task = self.tw.tasks.get(description="test task")
106         self.assertEqual(filtered_task['description'], "test task")
107
108     def test_filter_for_task_without_space_in_descripition(self):
109         task = Task(self.tw, description="test")
110         task.save()
111
112         filtered_task = self.tw.tasks.get(description="test")
113         self.assertEqual(filtered_task['description'], "test")
114
115     def test_filter_for_task_with_space_in_project(self):
116         task = Task(self.tw, description="test", project="random project")
117         task.save()
118
119         filtered_task = self.tw.tasks.get(project="random project")
120         self.assertEqual(filtered_task['project'], "random project")
121
122     def test_filter_for_task_without_space_in_project(self):
123         task = Task(self.tw, description="test", project="random")
124         task.save()
125
126         filtered_task = self.tw.tasks.get(project="random")
127         self.assertEqual(filtered_task['project'], "random")
128
129     def test_filter_with_empty_uuid(self):
130         self.assertRaises(ValueError, lambda: self.tw.tasks.get(uuid=''))
131
132     def test_filter_dummy_by_status(self):
133         t = Task(self.tw, description="test")
134         t.save()
135
136         tasks = self.tw.tasks.filter(status=t['status'])
137         self.assertEqual(list(tasks), [t])
138
139     def test_filter_dummy_by_uuid(self):
140         t = Task(self.tw, description="test")
141         t.save()
142
143         tasks = self.tw.tasks.filter(uuid=t['uuid'])
144         self.assertEqual(list(tasks), [t])
145
146     def test_filter_dummy_by_entry(self):
147         t = Task(self.tw, description="test")
148         t.save()
149
150         tasks = self.tw.tasks.filter(entry=t['entry'])
151         self.assertEqual(list(tasks), [t])
152
153     def test_filter_dummy_by_description(self):
154         t = Task(self.tw, description="test")
155         t.save()
156
157         tasks = self.tw.tasks.filter(description=t['description'])
158         self.assertEqual(list(tasks), [t])
159
160     def test_filter_dummy_by_start(self):
161         t = Task(self.tw, description="test")
162         t.save()
163         t.start()
164
165         tasks = self.tw.tasks.filter(start=t['start'])
166         self.assertEqual(list(tasks), [t])
167
168     def test_filter_dummy_by_end(self):
169         t = Task(self.tw, description="test")
170         t.save()
171         t.done()
172
173         tasks = self.tw.tasks.filter(end=t['end'])
174         self.assertEqual(list(tasks), [t])
175
176     def test_filter_dummy_by_due(self):
177         t = Task(self.tw, description="test", due=datetime.datetime.now())
178         t.save()
179
180         tasks = self.tw.tasks.filter(due=t['due'])
181         self.assertEqual(list(tasks), [t])
182
183     def test_filter_dummy_by_until(self):
184         t = Task(self.tw, description="test")
185         t.save()
186
187         tasks = self.tw.tasks.filter(until=t['until'])
188         self.assertEqual(list(tasks), [t])
189
190     def test_filter_dummy_by_modified(self):
191         t = Task(self.tw, description="test")
192         t.save()
193
194         tasks = self.tw.tasks.filter(modified=t['modified'])
195         self.assertEqual(list(tasks), [t])
196
197     def test_filter_dummy_by_scheduled(self):
198         t = Task(self.tw, description="test")
199         t.save()
200
201         tasks = self.tw.tasks.filter(scheduled=t['scheduled'])
202         self.assertEqual(list(tasks), [t])
203
204     def test_filter_dummy_by_tags(self):
205         t = Task(self.tw, description="test", tags=["home"])
206         t.save()
207
208         tasks = self.tw.tasks.filter(tags=t['tags'])
209         self.assertEqual(list(tasks), [t])
210
211     def test_filter_dummy_by_projects(self):
212         t = Task(self.tw, description="test", project="random")
213         t.save()
214
215         tasks = self.tw.tasks.filter(project=t['project'])
216         self.assertEqual(list(tasks), [t])
217
218     def test_filter_by_priority(self):
219         t = Task(self.tw, description="test", priority="H")
220         t.save()
221
222         tasks = self.tw.tasks.filter(priority=t['priority'])
223         self.assertEqual(list(tasks), [t])
224
225
226 class TaskTest(TasklibTest):
227
228     def test_create_unsaved_task(self):
229         # Make sure a new task is not saved unless explicitly called for
230         t = Task(self.tw, description="test task")
231         self.assertEqual(len(self.tw.tasks.all()), 0)
232
233     # TODO: once python 2.6 compatiblity is over, use context managers here
234     #       and in all subsequent tests for assertRaises
235
236     def test_delete_unsaved_task(self):
237         t = Task(self.tw, description="test task")
238         self.assertRaises(Task.NotSaved, t.delete)
239
240     def test_complete_unsaved_task(self):
241         t = Task(self.tw, description="test task")
242         self.assertRaises(Task.NotSaved, t.done)
243
244     def test_refresh_unsaved_task(self):
245         t = Task(self.tw, description="test task")
246         self.assertRaises(Task.NotSaved, t.refresh)
247
248     def test_start_unsaved_task(self):
249         t = Task(self.tw, description="test task")
250         self.assertRaises(Task.NotSaved, t.start)
251
252     def test_delete_deleted_task(self):
253         t = Task(self.tw, description="test task")
254         t.save()
255         t.delete()
256
257         self.assertRaises(Task.DeletedTask, t.delete)
258
259     def test_complete_completed_task(self):
260         t = Task(self.tw, description="test task")
261         t.save()
262         t.done()
263
264         self.assertRaises(Task.CompletedTask, t.done)
265
266     def test_start_completed_task(self):
267         t = Task(self.tw, description="test task")
268         t.save()
269         t.done()
270
271         self.assertRaises(Task.CompletedTask, t.start)
272
273     def test_complete_deleted_task(self):
274         t = Task(self.tw, description="test task")
275         t.save()
276         t.delete()
277
278         self.assertRaises(Task.DeletedTask, t.done)
279
280     def test_start_completed_task(self):
281         t = Task(self.tw, description="test task")
282         t.save()
283         t.done()
284
285         self.assertRaises(Task.CompletedTask, t.start)
286
287     def test_starting_task(self):
288         t = Task(self.tw, description="test task")
289         now = t.datetime_normalizer(datetime.datetime.now())
290         t.save()
291         t.start()
292
293         self.assertTrue(now.replace(microsecond=0) <= t['start'])
294         self.assertEqual(t['status'], 'pending')
295
296     def test_completing_task(self):
297         t = Task(self.tw, description="test task")
298         now = t.datetime_normalizer(datetime.datetime.now())
299         t.save()
300         t.done()
301
302         self.assertTrue(now.replace(microsecond=0) <= t['end'])
303         self.assertEqual(t['status'], 'completed')
304
305     def test_deleting_task(self):
306         t = Task(self.tw, description="test task")
307         now = t.datetime_normalizer(datetime.datetime.now())
308         t.save()
309         t.delete()
310
311         self.assertTrue(now.replace(microsecond=0) <= t['end'])
312         self.assertEqual(t['status'], 'deleted')
313
314     def test_modify_simple_attribute_without_space(self):
315         t = Task(self.tw, description="test")
316         t.save()
317
318         self.assertEquals(t['description'], "test")
319
320         t['description'] = "test-modified"
321         t.save()
322
323         self.assertEquals(t['description'], "test-modified")
324
325     def test_modify_simple_attribute_with_space(self):
326         # Space can pose problems with parsing
327         t = Task(self.tw, description="test task")
328         t.save()
329
330         self.assertEquals(t['description'], "test task")
331
332         t['description'] = "test task modified"
333         t.save()
334
335         self.assertEquals(t['description'], "test task modified")
336
337     def test_empty_dependency_set_of_unsaved_task(self):
338         t = Task(self.tw, description="test task")
339         self.assertEqual(t['depends'], set())
340
341     def test_empty_dependency_set_of_saved_task(self):
342         t = Task(self.tw, description="test task")
343         t.save()
344         self.assertEqual(t['depends'], set())
345
346     def test_set_unsaved_task_as_dependency(self):
347         # Adds only one dependency to task with no dependencies
348         t = Task(self.tw, description="test task")
349         dependency = Task(self.tw, description="needs to be done first")
350
351         # We only save the parent task, dependency task is unsaved
352         t.save()
353         t['depends'] = set([dependency])
354
355         self.assertRaises(Task.NotSaved, t.save)
356
357     def test_set_simple_dependency_set(self):
358         # Adds only one dependency to task with no dependencies
359         t = Task(self.tw, description="test task")
360         dependency = Task(self.tw, description="needs to be done first")
361
362         t.save()
363         dependency.save()
364
365         t['depends'] = set([dependency])
366
367         self.assertEqual(t['depends'], set([dependency]))
368
369     def test_set_complex_dependency_set(self):
370         # Adds two dependencies to task with no dependencies
371         t = Task(self.tw, description="test task")
372         dependency1 = Task(self.tw, description="needs to be done first")
373         dependency2 = Task(self.tw, description="needs to be done second")
374
375         t.save()
376         dependency1.save()
377         dependency2.save()
378
379         t['depends'] = set([dependency1, dependency2])
380
381         self.assertEqual(t['depends'], set([dependency1, dependency2]))
382
383     def test_remove_from_dependency_set(self):
384         # Removes dependency from task with two dependencies
385         t = Task(self.tw, description="test task")
386         dependency1 = Task(self.tw, description="needs to be done first")
387         dependency2 = Task(self.tw, description="needs to be done second")
388
389         dependency1.save()
390         dependency2.save()
391
392         t['depends'] = set([dependency1, dependency2])
393         t.save()
394
395         t['depends'].remove(dependency2)
396         t.save()
397
398         self.assertEqual(t['depends'], set([dependency1]))
399
400     def test_add_to_dependency_set(self):
401         # Adds dependency to task with one dependencies
402         t = Task(self.tw, description="test task")
403         dependency1 = Task(self.tw, description="needs to be done first")
404         dependency2 = Task(self.tw, description="needs to be done second")
405
406         dependency1.save()
407         dependency2.save()
408
409         t['depends'] = set([dependency1])
410         t.save()
411
412         t['depends'].add(dependency2)
413         t.save()
414
415         self.assertEqual(t['depends'], set([dependency1, dependency2]))
416
417     def test_add_to_empty_dependency_set(self):
418         # Adds dependency to task with one dependencies
419         t = Task(self.tw, description="test task")
420         dependency = Task(self.tw, description="needs to be done first")
421
422         dependency.save()
423
424         t['depends'].add(dependency)
425         t.save()
426
427         self.assertEqual(t['depends'], set([dependency]))
428
429     def test_simple_dependency_set_save_repeatedly(self):
430         # Adds only one dependency to task with no dependencies
431         t = Task(self.tw, description="test task")
432         dependency = Task(self.tw, description="needs to be done first")
433         dependency.save()
434
435         t['depends'] = set([dependency])
436         t.save()
437
438         # We taint the task, but keep depends intact
439         t['description'] = "test task modified"
440         t.save()
441
442         self.assertEqual(t['depends'], set([dependency]))
443
444         # We taint the task, but assign the same set to the depends
445         t['depends'] = set([dependency])
446         t['description'] = "test task modified again"
447         t.save()
448
449         self.assertEqual(t['depends'], set([dependency]))
450
451     def test_compare_different_tasks(self):
452         # Negative: compare two different tasks
453         t1 = Task(self.tw, description="test task")
454         t2 = Task(self.tw, description="test task")
455
456         t1.save()
457         t2.save()
458
459         self.assertEqual(t1 == t2, False)
460
461     def test_compare_same_task_object(self):
462         # Compare Task object wit itself
463         t = Task(self.tw, description="test task")
464         t.save()
465
466         self.assertEqual(t == t, True)
467
468     def test_compare_same_task(self):
469         # Compare the same task using two different objects
470         t1 = Task(self.tw, description="test task")
471         t1.save()
472
473         t2 = self.tw.tasks.get(uuid=t1['uuid'])
474         self.assertEqual(t1 == t2, True)
475
476     def test_compare_unsaved_tasks(self):
477         # t1 and t2 are unsaved tasks, considered to be unequal
478         # despite the content of data
479         t1 = Task(self.tw, description="test task")
480         t2 = Task(self.tw, description="test task")
481
482         self.assertEqual(t1 == t2, False)
483
484     def test_hash_unsaved_tasks(self):
485         # Considered equal, it's the same object
486         t1 = Task(self.tw, description="test task")
487         t2 = t1
488         self.assertEqual(hash(t1) == hash(t2), True)
489
490     def test_hash_same_task(self):
491         # Compare the hash of the task using two different objects
492         t1 = Task(self.tw, description="test task")
493         t1.save()
494
495         t2 = self.tw.tasks.get(uuid=t1['uuid'])
496         self.assertEqual(t1.__hash__(), t2.__hash__())
497
498     def test_adding_task_with_priority(self):
499         t = Task(self.tw, description="test task", priority="M")
500         t.save()
501
502     def test_removing_priority_with_none(self):
503         t = Task(self.tw, description="test task", priority="L")
504         t.save()
505
506         # Remove the priority mark
507         t['priority'] = None
508         t.save()
509
510         # Assert that priority is not there after saving
511         self.assertEqual(t['priority'], None)
512
513     def test_adding_task_with_due_time(self):
514         t = Task(self.tw, description="test task", due=datetime.datetime.now())
515         t.save()
516
517     def test_removing_due_time_with_none(self):
518         t = Task(self.tw, description="test task", due=datetime.datetime.now())
519         t.save()
520
521         # Remove the due timestamp
522         t['due'] = None
523         t.save()
524
525         # Assert that due timestamp is no longer there
526         self.assertEqual(t['due'], None)
527
528     def test_modified_fields_new_task(self):
529         t = Task(self.tw)
530
531         # This should be empty with new task
532         self.assertEqual(set(t._modified_fields), set())
533
534         # Modify the task
535         t['description'] = "test task"
536         self.assertEqual(set(t._modified_fields), set(['description']))
537
538         t['due'] = datetime.datetime(2014, 2, 14, 14, 14, 14)  # <3
539         self.assertEqual(set(t._modified_fields), set(['description', 'due']))
540
541         t['project'] = "test project"
542         self.assertEqual(set(t._modified_fields),
543                          set(['description', 'due', 'project']))
544
545         # List of modified fields should clear out when saved
546         t.save()
547         self.assertEqual(set(t._modified_fields), set())
548
549         # Reassigning the fields with the same values now should not produce
550         # modified fields
551         t['description'] = "test task"
552         t['due'] = datetime.datetime(2014, 2, 14, 14, 14, 14)  # <3
553         t['project'] = "test project"
554         self.assertEqual(set(t._modified_fields), set())
555
556     def test_modified_fields_loaded_task(self):
557         t = Task(self.tw)
558
559         # Modify the task
560         t['description'] = "test task"
561         t['due'] = datetime.datetime(2014, 2, 14, 14, 14, 14)  # <3
562         t['project'] = "test project"
563
564         dependency = Task(self.tw, description="dependency")
565         dependency.save()
566         t['depends'] = set([dependency])
567
568         # List of modified fields should clear out when saved
569         t.save()
570         self.assertEqual(set(t._modified_fields), set())
571
572         # Get the task by using a filter by UUID
573         t2 = self.tw.tasks.get(uuid=t['uuid'])
574
575         # Reassigning the fields with the same values now should not produce
576         # modified fields
577         t['description'] = "test task"
578         t['due'] = datetime.datetime(2014, 2, 14, 14, 14, 14)  # <3
579         t['project'] = "test project"
580         t['depends'] = set([dependency])
581         self.assertEqual(set(t._modified_fields), set())
582
583     def test_modified_fields_not_affected_by_reading(self):
584         t = Task(self.tw)
585
586         for field in TASK_STANDARD_ATTRS:
587             value = t[field]
588
589         self.assertEqual(set(t._modified_fields), set())
590
591     def test_setting_read_only_attrs_through_init(self):
592         # Test that we are unable to set readonly attrs through __init__
593         for readonly_key in Task.read_only_fields:
594             kwargs = {'description': 'test task', readonly_key: 'value'}
595             self.assertRaises(RuntimeError,
596                               lambda: Task(self.tw, **kwargs))
597
598     def test_setting_read_only_attrs_through_setitem(self):
599         # Test that we are unable to set readonly attrs through __init__
600         for readonly_key in Task.read_only_fields:
601             t = Task(self.tw, description='test task')
602             self.assertRaises(RuntimeError,
603                               lambda: t.__setitem__(readonly_key, 'value'))
604
605     def test_saving_unmodified_task(self):
606         t = Task(self.tw, description="test task")
607         t.save()
608         t.save()
609
610     def test_adding_tag_by_appending(self):
611         t = Task(self.tw, description="test task", tags=['test1'])
612         t.save()
613         t['tags'].append('test2')
614         t.save()
615         self.assertEqual(t['tags'], ['test1', 'test2'])
616
617     def test_adding_tag_by_appending_empty(self):
618         t = Task(self.tw, description="test task")
619         t.save()
620         t['tags'].append('test')
621         t.save()
622         self.assertEqual(t['tags'], ['test'])
623
624     def test_serializers_returning_empty_string_for_none(self):
625         # Test that any serializer returns '' when passed None
626         t = Task(self.tw)
627         serializers = [getattr(t, serializer_name) for serializer_name in
628                        filter(lambda x: x.startswith('serialize_'), dir(t))]
629         for serializer in serializers:
630             self.assertEqual(serializer(None), '')
631
632     def test_deserializer_returning_empty_value_for_empty_string(self):
633         # Test that any deserializer returns empty value when passed ''
634         t = Task(self.tw)
635         deserializers = [getattr(t, deserializer_name) for deserializer_name in
636                         filter(lambda x: x.startswith('deserialize_'), dir(t))]
637         for deserializer in deserializers:
638             self.assertTrue(deserializer('') in (None, [], set()))
639
640     def test_normalizers_handling_none(self):
641         # Test that any normalizer can handle None as a valid value
642         t = Task(self.tw)
643
644         for key in TASK_STANDARD_ATTRS:
645             t._normalize(key, None)
646
647     def test_recurrent_task_generation(self):
648         today = datetime.date.today()
649         t = Task(self.tw, description="brush teeth",
650                  due=today, recur="daily")
651         t.save()
652         self.assertEqual(len(self.tw.tasks.pending()), 2)
653
654 class TaskFromHookTest(TasklibTest):
655
656     input_add_data = six.StringIO(
657         '{"description":"Buy some milk",'
658         '"entry":"20141118T050231Z",'
659         '"status":"pending",'
660         '"uuid":"a360fc44-315c-4366-b70c-ea7e7520b749"}')
661
662     input_modify_data = six.StringIO(input_add_data.getvalue() + '\n' +
663         '{"description":"Buy some milk finally",'
664         '"entry":"20141118T050231Z",'
665         '"status":"completed",'
666         '"uuid":"a360fc44-315c-4366-b70c-ea7e7520b749"}')
667
668     exported_raw_data = (
669         '{"project":"Home",'
670          '"due":"20150101T232323Z",'
671          '"description":"test task"}')
672
673     def test_setting_up_from_add_hook_input(self):
674         t = Task.from_input(input_file=self.input_add_data, warrior=self.tw)
675         self.assertEqual(t['description'], "Buy some milk")
676         self.assertEqual(t.pending, True)
677
678     def test_setting_up_from_modified_hook_input(self):
679         t = Task.from_input(input_file=self.input_modify_data, modify=True,
680                             warrior=self.tw)
681         self.assertEqual(t['description'], "Buy some milk finally")
682         self.assertEqual(t.pending, False)
683         self.assertEqual(t.completed, True)
684
685         self.assertEqual(t._original_data['status'], "pending")
686         self.assertEqual(t._original_data['description'], "Buy some milk")
687         self.assertEqual(set(t._modified_fields),
688                          set(['status', 'description']))
689
690     def test_export_data(self):
691         t = Task(self.tw, description="test task",
692             project="Home",
693             due=pytz.utc.localize(datetime.datetime(2015,1,1,23,23,23)))
694
695         # Check that the output is a permutation of:
696         # {"project":"Home","description":"test task","due":"20150101232323Z"}
697         allowed_segments = self.exported_raw_data[1:-1].split(',')
698         allowed_output = [
699             '{' + ','.join(segments) + '}'
700             for segments in itertools.permutations(allowed_segments)
701         ]
702
703         self.assertTrue(any(t.export_data() == expected
704                             for expected in allowed_output))
705
706 class TimezoneAwareDatetimeTest(TasklibTest):
707
708     def setUp(self):
709         super(TimezoneAwareDatetimeTest, self).setUp()
710         self.zone = local_zone
711         self.localdate_naive = datetime.datetime(2015,2,2)
712         self.localtime_naive = datetime.datetime(2015,2,2,0,0,0)
713         self.localtime_aware = self.zone.localize(self.localtime_naive)
714         self.utctime_aware = self.localtime_aware.astimezone(pytz.utc)
715
716     def test_timezone_naive_datetime_setitem(self):
717         t = Task(self.tw, description="test task")
718         t['due'] = self.localtime_naive
719         self.assertEqual(t['due'], self.localtime_aware)
720
721     def test_timezone_naive_datetime_using_init(self):
722         t = Task(self.tw, description="test task", due=self.localtime_naive)
723         self.assertEqual(t['due'], self.localtime_aware)
724
725     def test_filter_by_naive_datetime(self):
726         t = Task(self.tw, description="task1", due=self.localtime_naive)
727         t.save()
728         matching_tasks = self.tw.tasks.filter(due=self.localtime_naive)
729         self.assertEqual(len(matching_tasks), 1)
730
731     def test_serialize_naive_datetime(self):
732         t = Task(self.tw, description="task1", due=self.localtime_naive)
733         self.assertEqual(json.loads(t.export_data())['due'], 
734                          self.utctime_aware.strftime(DATE_FORMAT))
735
736     def test_timezone_naive_date_setitem(self):
737         t = Task(self.tw, description="test task")
738         t['due'] = self.localdate_naive
739         self.assertEqual(t['due'], self.localtime_aware)
740
741     def test_timezone_naive_date_using_init(self):
742         t = Task(self.tw, description="test task", due=self.localdate_naive)
743         self.assertEqual(t['due'], self.localtime_aware)
744
745     def test_filter_by_naive_date(self):
746         t = Task(self.tw, description="task1", due=self.localdate_naive)
747         t.save()
748         matching_tasks = self.tw.tasks.filter(due=self.localdate_naive)
749         self.assertEqual(len(matching_tasks), 1)
750
751     def test_serialize_naive_date(self):
752         t = Task(self.tw, description="task1", due=self.localdate_naive)
753         self.assertEqual(json.loads(t.export_data())['due'], 
754                          self.utctime_aware.strftime(DATE_FORMAT))
755
756     def test_timezone_aware_datetime_setitem(self):
757         t = Task(self.tw, description="test task")
758         t['due'] = self.localtime_aware
759         self.assertEqual(t['due'], self.localtime_aware)
760
761     def test_timezone_aware_datetime_using_init(self):
762         t = Task(self.tw, description="test task", due=self.localtime_aware)
763         self.assertEqual(t['due'], self.localtime_aware)
764
765     def test_filter_by_aware_datetime(self):
766         t = Task(self.tw, description="task1", due=self.localtime_aware)
767         t.save()
768         matching_tasks = self.tw.tasks.filter(due=self.localtime_aware)
769         self.assertEqual(len(matching_tasks), 1)
770
771     def test_serialize_aware_datetime(self):
772         t = Task(self.tw, description="task1", due=self.localtime_aware)
773         self.assertEqual(json.loads(t.export_data())['due'], 
774                          self.utctime_aware.strftime(DATE_FORMAT))
775
776 class AnnotationTest(TasklibTest):
777
778     def setUp(self):
779         super(AnnotationTest, self).setUp()
780         Task(self.tw, description="test task").save()
781
782     def test_adding_annotation(self):
783         task = self.tw.tasks.get()
784         task.add_annotation('test annotation')
785         self.assertEqual(len(task['annotations']), 1)
786         ann = task['annotations'][0]
787         self.assertEqual(ann['description'], 'test annotation')
788
789     def test_removing_annotation(self):
790         task = self.tw.tasks.get()
791         task.add_annotation('test annotation')
792         ann = task['annotations'][0]
793         ann.remove()
794         self.assertEqual(len(task['annotations']), 0)
795
796     def test_removing_annotation_by_description(self):
797         task = self.tw.tasks.get()
798         task.add_annotation('test annotation')
799         task.remove_annotation('test annotation')
800         self.assertEqual(len(task['annotations']), 0)
801
802     def test_removing_annotation_by_obj(self):
803         task = self.tw.tasks.get()
804         task.add_annotation('test annotation')
805         ann = task['annotations'][0]
806         task.remove_annotation(ann)
807         self.assertEqual(len(task['annotations']), 0)
808
809     def test_annotation_after_modification(self):
810          task = self.tw.tasks.get()
811          task['project'] = 'test'
812          task.add_annotation('I should really do this task')
813          self.assertEqual(task['project'], 'test')
814          task.save()
815          self.assertEqual(task['project'], 'test')
816
817     def test_serialize_annotations(self):
818         # Test that serializing annotations is possible
819         t = Task(self.tw, description="test")
820         t.save()
821
822         t.add_annotation("annotation1")
823         t.add_annotation("annotation2")
824
825         data = t._serialize('annotations', t._data['annotations'])
826
827         self.assertEqual(len(data), 2)
828         self.assertEqual(type(data[0]), dict)
829         self.assertEqual(type(data[1]), dict)
830
831         self.assertEqual(data[0]['description'], "annotation1")
832         self.assertEqual(data[1]['description'], "annotation2")
833
834
835 class UnicodeTest(TasklibTest):
836
837     def test_unicode_task(self):
838         Task(self.tw, description="†åßk").save()
839         self.tw.tasks.get()
840
841     def test_non_unicode_task(self):
842         Task(self.tw, description="test task").save()
843         self.tw.tasks.get()
844
845 class ReadOnlyDictViewTest(unittest.TestCase):
846
847     def setUp(self):
848         self.sample = dict(l=[1,2,3], d={'k':'v'})
849         self.original_sample = copy.deepcopy(self.sample)
850         self.view = ReadOnlyDictView(self.sample)
851
852     def test_readonlydictview_getitem(self):
853         l = self.view['l']
854         self.assertEqual(l, self.sample['l'])
855
856         # Assert that modification changed only copied value
857         l.append(4)
858         self.assertNotEqual(l, self.sample['l'])
859
860         # Assert that viewed dict is not changed
861         self.assertEqual(self.sample, self.original_sample)
862
863     def test_readonlydictview_contains(self):
864         self.assertEqual('l' in self.view, 'l' in self.sample)
865         self.assertEqual('d' in self.view, 'd' in self.sample)
866         self.assertEqual('k' in self.view, 'k' in self.sample)
867
868         # Assert that viewed dict is not changed
869         self.assertEqual(self.sample, self.original_sample)
870
871     def test_readonlydictview_iter(self):
872         self.assertEqual(list(k for k in self.view),
873                          list(k for k in self.sample))
874
875         # Assert the view is correct after modification
876         self.sample['new'] = 'value'
877         self.assertEqual(list(k for k in self.view),
878                          list(k for k in self.sample))
879
880     def test_readonlydictview_len(self):
881         self.assertEqual(len(self.view), len(self.sample))
882
883         # Assert the view is correct after modification
884         self.sample['new'] = 'value'
885         self.assertEqual(len(self.view), len(self.sample))
886
887     def test_readonlydictview_get(self):
888         l = self.view.get('l')
889         self.assertEqual(l, self.sample.get('l'))
890
891         # Assert that modification changed only copied value
892         l.append(4)
893         self.assertNotEqual(l, self.sample.get('l'))
894
895         # Assert that viewed dict is not changed
896         self.assertEqual(self.sample, self.original_sample)
897
898     def test_readonlydict_items(self):
899         view_items = self.view.items()
900         sample_items = list(self.sample.items())
901         self.assertEqual(view_items, sample_items)
902
903         view_items.append('newkey')
904         self.assertNotEqual(view_items, sample_items)
905         self.assertEqual(self.sample, self.original_sample)
906
907     def test_readonlydict_values(self):
908         view_values = self.view.values()
909         sample_values = list(self.sample.values())
910         self.assertEqual(view_values, sample_values)
911
912         view_list_item = list(filter(lambda x: type(x) is list,
913                                      view_values))[0]
914         view_list_item.append(4)
915         self.assertNotEqual(view_values, sample_values)
916         self.assertEqual(self.sample, self.original_sample)