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

tests: Add dummy tests for filtering
[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_delete_deleted_task(self):
249         t = Task(self.tw, description="test task")
250         t.save()
251         t.delete()
252
253         self.assertRaises(Task.DeletedTask, t.delete)
254
255     def test_complete_completed_task(self):
256         t = Task(self.tw, description="test task")
257         t.save()
258         t.done()
259
260         self.assertRaises(Task.CompletedTask, t.done)
261
262     def test_complete_deleted_task(self):
263         t = Task(self.tw, description="test task")
264         t.save()
265         t.delete()
266
267         self.assertRaises(Task.DeletedTask, t.done)
268
269     def test_modify_simple_attribute_without_space(self):
270         t = Task(self.tw, description="test")
271         t.save()
272
273         self.assertEquals(t['description'], "test")
274
275         t['description'] = "test-modified"
276         t.save()
277
278         self.assertEquals(t['description'], "test-modified")
279
280     def test_modify_simple_attribute_with_space(self):
281         # Space can pose problems with parsing
282         t = Task(self.tw, description="test task")
283         t.save()
284
285         self.assertEquals(t['description'], "test task")
286
287         t['description'] = "test task modified"
288         t.save()
289
290         self.assertEquals(t['description'], "test task modified")
291
292     def test_empty_dependency_set_of_unsaved_task(self):
293         t = Task(self.tw, description="test task")
294         self.assertEqual(t['depends'], set())
295
296     def test_empty_dependency_set_of_saved_task(self):
297         t = Task(self.tw, description="test task")
298         t.save()
299         self.assertEqual(t['depends'], set())
300
301     def test_set_unsaved_task_as_dependency(self):
302         # Adds only one dependency to task with no dependencies
303         t = Task(self.tw, description="test task")
304         dependency = Task(self.tw, description="needs to be done first")
305
306         # We only save the parent task, dependency task is unsaved
307         t.save()
308         t['depends'] = set([dependency])
309
310         self.assertRaises(Task.NotSaved, t.save)
311
312     def test_set_simple_dependency_set(self):
313         # Adds only one dependency to task with no dependencies
314         t = Task(self.tw, description="test task")
315         dependency = Task(self.tw, description="needs to be done first")
316
317         t.save()
318         dependency.save()
319
320         t['depends'] = set([dependency])
321
322         self.assertEqual(t['depends'], set([dependency]))
323
324     def test_set_complex_dependency_set(self):
325         # Adds two dependencies to task with no dependencies
326         t = Task(self.tw, description="test task")
327         dependency1 = Task(self.tw, description="needs to be done first")
328         dependency2 = Task(self.tw, description="needs to be done second")
329
330         t.save()
331         dependency1.save()
332         dependency2.save()
333
334         t['depends'] = set([dependency1, dependency2])
335
336         self.assertEqual(t['depends'], set([dependency1, dependency2]))
337
338     def test_remove_from_dependency_set(self):
339         # Removes dependency from task with two dependencies
340         t = Task(self.tw, description="test task")
341         dependency1 = Task(self.tw, description="needs to be done first")
342         dependency2 = Task(self.tw, description="needs to be done second")
343
344         dependency1.save()
345         dependency2.save()
346
347         t['depends'] = set([dependency1, dependency2])
348         t.save()
349
350         t['depends'].remove(dependency2)
351         t.save()
352
353         self.assertEqual(t['depends'], set([dependency1]))
354
355     def test_add_to_dependency_set(self):
356         # Adds dependency to task with one dependencies
357         t = Task(self.tw, description="test task")
358         dependency1 = Task(self.tw, description="needs to be done first")
359         dependency2 = Task(self.tw, description="needs to be done second")
360
361         dependency1.save()
362         dependency2.save()
363
364         t['depends'] = set([dependency1])
365         t.save()
366
367         t['depends'].add(dependency2)
368         t.save()
369
370         self.assertEqual(t['depends'], set([dependency1, dependency2]))
371
372     def test_add_to_empty_dependency_set(self):
373         # Adds dependency to task with one dependencies
374         t = Task(self.tw, description="test task")
375         dependency = Task(self.tw, description="needs to be done first")
376
377         dependency.save()
378
379         t['depends'].add(dependency)
380         t.save()
381
382         self.assertEqual(t['depends'], set([dependency]))
383
384     def test_simple_dependency_set_save_repeatedly(self):
385         # Adds only one dependency to task with no dependencies
386         t = Task(self.tw, description="test task")
387         dependency = Task(self.tw, description="needs to be done first")
388         dependency.save()
389
390         t['depends'] = set([dependency])
391         t.save()
392
393         # We taint the task, but keep depends intact
394         t['description'] = "test task modified"
395         t.save()
396
397         self.assertEqual(t['depends'], set([dependency]))
398
399         # We taint the task, but assign the same set to the depends
400         t['depends'] = set([dependency])
401         t['description'] = "test task modified again"
402         t.save()
403
404         self.assertEqual(t['depends'], set([dependency]))
405
406     def test_compare_different_tasks(self):
407         # Negative: compare two different tasks
408         t1 = Task(self.tw, description="test task")
409         t2 = Task(self.tw, description="test task")
410
411         t1.save()
412         t2.save()
413
414         self.assertEqual(t1 == t2, False)
415
416     def test_compare_same_task_object(self):
417         # Compare Task object wit itself
418         t = Task(self.tw, description="test task")
419         t.save()
420
421         self.assertEqual(t == t, True)
422
423     def test_compare_same_task(self):
424         # Compare the same task using two different objects
425         t1 = Task(self.tw, description="test task")
426         t1.save()
427
428         t2 = self.tw.tasks.get(uuid=t1['uuid'])
429         self.assertEqual(t1 == t2, True)
430
431     def test_compare_unsaved_tasks(self):
432         # t1 and t2 are unsaved tasks, considered to be unequal
433         # despite the content of data
434         t1 = Task(self.tw, description="test task")
435         t2 = Task(self.tw, description="test task")
436
437         self.assertEqual(t1 == t2, False)
438
439     def test_hash_unsaved_tasks(self):
440         # Considered equal, it's the same object
441         t1 = Task(self.tw, description="test task")
442         t2 = t1
443         self.assertEqual(hash(t1) == hash(t2), True)
444
445     def test_hash_same_task(self):
446         # Compare the hash of the task using two different objects
447         t1 = Task(self.tw, description="test task")
448         t1.save()
449
450         t2 = self.tw.tasks.get(uuid=t1['uuid'])
451         self.assertEqual(t1.__hash__(), t2.__hash__())
452
453     def test_adding_task_with_priority(self):
454         t = Task(self.tw, description="test task", priority="M")
455         t.save()
456
457     def test_removing_priority_with_none(self):
458         t = Task(self.tw, description="test task", priority="L")
459         t.save()
460
461         # Remove the priority mark
462         t['priority'] = None
463         t.save()
464
465         # Assert that priority is not there after saving
466         self.assertEqual(t['priority'], None)
467
468     def test_adding_task_with_due_time(self):
469         t = Task(self.tw, description="test task", due=datetime.datetime.now())
470         t.save()
471
472     def test_removing_due_time_with_none(self):
473         t = Task(self.tw, description="test task", due=datetime.datetime.now())
474         t.save()
475
476         # Remove the due timestamp
477         t['due'] = None
478         t.save()
479
480         # Assert that due timestamp is no longer there
481         self.assertEqual(t['due'], None)
482
483     def test_modified_fields_new_task(self):
484         t = Task(self.tw)
485
486         # This should be empty with new task
487         self.assertEqual(set(t._modified_fields), set())
488
489         # Modify the task
490         t['description'] = "test task"
491         self.assertEqual(set(t._modified_fields), set(['description']))
492
493         t['due'] = datetime.datetime(2014, 2, 14, 14, 14, 14)  # <3
494         self.assertEqual(set(t._modified_fields), set(['description', 'due']))
495
496         t['project'] = "test project"
497         self.assertEqual(set(t._modified_fields),
498                          set(['description', 'due', 'project']))
499
500         # List of modified fields should clear out when saved
501         t.save()
502         self.assertEqual(set(t._modified_fields), set())
503
504         # Reassigning the fields with the same values now should not produce
505         # modified fields
506         t['description'] = "test task"
507         t['due'] = datetime.datetime(2014, 2, 14, 14, 14, 14)  # <3
508         t['project'] = "test project"
509         self.assertEqual(set(t._modified_fields), set())
510
511     def test_modified_fields_loaded_task(self):
512         t = Task(self.tw)
513
514         # Modify the task
515         t['description'] = "test task"
516         t['due'] = datetime.datetime(2014, 2, 14, 14, 14, 14)  # <3
517         t['project'] = "test project"
518
519         dependency = Task(self.tw, description="dependency")
520         dependency.save()
521         t['depends'] = set([dependency])
522
523         # List of modified fields should clear out when saved
524         t.save()
525         self.assertEqual(set(t._modified_fields), set())
526
527         # Get the task by using a filter by UUID
528         t2 = self.tw.tasks.get(uuid=t['uuid'])
529
530         # Reassigning the fields with the same values now should not produce
531         # modified fields
532         t['description'] = "test task"
533         t['due'] = datetime.datetime(2014, 2, 14, 14, 14, 14)  # <3
534         t['project'] = "test project"
535         t['depends'] = set([dependency])
536         self.assertEqual(set(t._modified_fields), set())
537
538     def test_modified_fields_not_affected_by_reading(self):
539         t = Task(self.tw)
540
541         for field in TASK_STANDARD_ATTRS:
542             value = t[field]
543
544         self.assertEqual(set(t._modified_fields), set())
545
546     def test_setting_read_only_attrs_through_init(self):
547         # Test that we are unable to set readonly attrs through __init__
548         for readonly_key in Task.read_only_fields:
549             kwargs = {'description': 'test task', readonly_key: 'value'}
550             self.assertRaises(RuntimeError,
551                               lambda: Task(self.tw, **kwargs))
552
553     def test_setting_read_only_attrs_through_setitem(self):
554         # Test that we are unable to set readonly attrs through __init__
555         for readonly_key in Task.read_only_fields:
556             t = Task(self.tw, description='test task')
557             self.assertRaises(RuntimeError,
558                               lambda: t.__setitem__(readonly_key, 'value'))
559
560     def test_saving_unmodified_task(self):
561         t = Task(self.tw, description="test task")
562         t.save()
563         t.save()
564
565     def test_adding_tag_by_appending(self):
566         t = Task(self.tw, description="test task", tags=['test1'])
567         t.save()
568         t['tags'].append('test2')
569         t.save()
570         self.assertEqual(t['tags'], ['test1', 'test2'])
571
572     def test_adding_tag_by_appending_empty(self):
573         t = Task(self.tw, description="test task")
574         t.save()
575         t['tags'].append('test')
576         t.save()
577         self.assertEqual(t['tags'], ['test'])
578
579     def test_serializers_returning_empty_string_for_none(self):
580         # Test that any serializer returns '' when passed None
581         t = Task(self.tw)
582         serializers = [getattr(t, serializer_name) for serializer_name in
583                        filter(lambda x: x.startswith('serialize_'), dir(t))]
584         for serializer in serializers:
585             self.assertEqual(serializer(None), '')
586
587     def test_deserializer_returning_empty_value_for_empty_string(self):
588         # Test that any deserializer returns empty value when passed ''
589         t = Task(self.tw)
590         deserializers = [getattr(t, deserializer_name) for deserializer_name in
591                         filter(lambda x: x.startswith('deserialize_'), dir(t))]
592         for deserializer in deserializers:
593             self.assertTrue(deserializer('') in (None, [], set()))
594
595     def test_normalizers_handling_none(self):
596         # Test that any normalizer can handle None as a valid value
597         t = Task(self.tw)
598
599         for key in TASK_STANDARD_ATTRS:
600             t._normalize(key, None)
601
602     def test_recurrent_task_generation(self):
603         today = datetime.date.today()
604         t = Task(self.tw, description="brush teeth",
605                  due=today, recur="daily")
606         t.save()
607         self.assertEqual(len(self.tw.tasks.pending()), 2)
608
609 class TaskFromHookTest(TasklibTest):
610
611     input_add_data = six.StringIO(
612         '{"description":"Buy some milk",'
613         '"entry":"20141118T050231Z",'
614         '"status":"pending",'
615         '"uuid":"a360fc44-315c-4366-b70c-ea7e7520b749"}')
616
617     input_modify_data = six.StringIO(input_add_data.getvalue() + '\n' +
618         '{"description":"Buy some milk finally",'
619         '"entry":"20141118T050231Z",'
620         '"status":"completed",'
621         '"uuid":"a360fc44-315c-4366-b70c-ea7e7520b749"}')
622
623     exported_raw_data = (
624         '{"project":"Home",'
625          '"due":"20150101T232323Z",'
626          '"description":"test task"}')
627
628     def test_setting_up_from_add_hook_input(self):
629         t = Task.from_input(input_file=self.input_add_data, warrior=self.tw)
630         self.assertEqual(t['description'], "Buy some milk")
631         self.assertEqual(t.pending, True)
632
633     def test_setting_up_from_modified_hook_input(self):
634         t = Task.from_input(input_file=self.input_modify_data, modify=True,
635                             warrior=self.tw)
636         self.assertEqual(t['description'], "Buy some milk finally")
637         self.assertEqual(t.pending, False)
638         self.assertEqual(t.completed, True)
639
640         self.assertEqual(t._original_data['status'], "pending")
641         self.assertEqual(t._original_data['description'], "Buy some milk")
642         self.assertEqual(set(t._modified_fields),
643                          set(['status', 'description']))
644
645     def test_export_data(self):
646         t = Task(self.tw, description="test task",
647             project="Home",
648             due=pytz.utc.localize(datetime.datetime(2015,1,1,23,23,23)))
649
650         # Check that the output is a permutation of:
651         # {"project":"Home","description":"test task","due":"20150101232323Z"}
652         allowed_segments = self.exported_raw_data[1:-1].split(',')
653         allowed_output = [
654             '{' + ','.join(segments) + '}'
655             for segments in itertools.permutations(allowed_segments)
656         ]
657
658         self.assertTrue(any(t.export_data() == expected
659                             for expected in allowed_output))
660
661 class TimezoneAwareDatetimeTest(TasklibTest):
662
663     def setUp(self):
664         super(TimezoneAwareDatetimeTest, self).setUp()
665         self.zone = local_zone
666         self.localdate_naive = datetime.datetime(2015,2,2)
667         self.localtime_naive = datetime.datetime(2015,2,2,0,0,0)
668         self.localtime_aware = self.zone.localize(self.localtime_naive)
669         self.utctime_aware = self.localtime_aware.astimezone(pytz.utc)
670
671     def test_timezone_naive_datetime_setitem(self):
672         t = Task(self.tw, description="test task")
673         t['due'] = self.localtime_naive
674         self.assertEqual(t['due'], self.localtime_aware)
675
676     def test_timezone_naive_datetime_using_init(self):
677         t = Task(self.tw, description="test task", due=self.localtime_naive)
678         self.assertEqual(t['due'], self.localtime_aware)
679
680     def test_filter_by_naive_datetime(self):
681         t = Task(self.tw, description="task1", due=self.localtime_naive)
682         t.save()
683         matching_tasks = self.tw.tasks.filter(due=self.localtime_naive)
684         self.assertEqual(len(matching_tasks), 1)
685
686     def test_serialize_naive_datetime(self):
687         t = Task(self.tw, description="task1", due=self.localtime_naive)
688         self.assertEqual(json.loads(t.export_data())['due'], 
689                          self.utctime_aware.strftime(DATE_FORMAT))
690
691     def test_timezone_naive_date_setitem(self):
692         t = Task(self.tw, description="test task")
693         t['due'] = self.localdate_naive
694         self.assertEqual(t['due'], self.localtime_aware)
695
696     def test_timezone_naive_date_using_init(self):
697         t = Task(self.tw, description="test task", due=self.localdate_naive)
698         self.assertEqual(t['due'], self.localtime_aware)
699
700     def test_filter_by_naive_date(self):
701         t = Task(self.tw, description="task1", due=self.localdate_naive)
702         t.save()
703         matching_tasks = self.tw.tasks.filter(due=self.localdate_naive)
704         self.assertEqual(len(matching_tasks), 1)
705
706     def test_serialize_naive_date(self):
707         t = Task(self.tw, description="task1", due=self.localdate_naive)
708         self.assertEqual(json.loads(t.export_data())['due'], 
709                          self.utctime_aware.strftime(DATE_FORMAT))
710
711     def test_timezone_aware_datetime_setitem(self):
712         t = Task(self.tw, description="test task")
713         t['due'] = self.localtime_aware
714         self.assertEqual(t['due'], self.localtime_aware)
715
716     def test_timezone_aware_datetime_using_init(self):
717         t = Task(self.tw, description="test task", due=self.localtime_aware)
718         self.assertEqual(t['due'], self.localtime_aware)
719
720     def test_filter_by_aware_datetime(self):
721         t = Task(self.tw, description="task1", due=self.localtime_aware)
722         t.save()
723         matching_tasks = self.tw.tasks.filter(due=self.localtime_aware)
724         self.assertEqual(len(matching_tasks), 1)
725
726     def test_serialize_aware_datetime(self):
727         t = Task(self.tw, description="task1", due=self.localtime_aware)
728         self.assertEqual(json.loads(t.export_data())['due'], 
729                          self.utctime_aware.strftime(DATE_FORMAT))
730
731 class AnnotationTest(TasklibTest):
732
733     def setUp(self):
734         super(AnnotationTest, self).setUp()
735         Task(self.tw, description="test task").save()
736
737     def test_adding_annotation(self):
738         task = self.tw.tasks.get()
739         task.add_annotation('test annotation')
740         self.assertEqual(len(task['annotations']), 1)
741         ann = task['annotations'][0]
742         self.assertEqual(ann['description'], 'test annotation')
743
744     def test_removing_annotation(self):
745         task = self.tw.tasks.get()
746         task.add_annotation('test annotation')
747         ann = task['annotations'][0]
748         ann.remove()
749         self.assertEqual(len(task['annotations']), 0)
750
751     def test_removing_annotation_by_description(self):
752         task = self.tw.tasks.get()
753         task.add_annotation('test annotation')
754         task.remove_annotation('test annotation')
755         self.assertEqual(len(task['annotations']), 0)
756
757     def test_removing_annotation_by_obj(self):
758         task = self.tw.tasks.get()
759         task.add_annotation('test annotation')
760         ann = task['annotations'][0]
761         task.remove_annotation(ann)
762         self.assertEqual(len(task['annotations']), 0)
763
764     def test_annotation_after_modification(self):
765          task = self.tw.tasks.get()
766          task['project'] = 'test'
767          task.add_annotation('I should really do this task')
768          self.assertEqual(task['project'], 'test')
769          task.save()
770          self.assertEqual(task['project'], 'test')
771
772     def test_serialize_annotations(self):
773         # Test that serializing annotations is possible
774         t = Task(self.tw, description="test")
775         t.save()
776
777         t.add_annotation("annotation1")
778         t.add_annotation("annotation2")
779
780         data = t._serialize('annotations', t._data['annotations'])
781
782         self.assertEqual(len(data), 2)
783         self.assertEqual(type(data[0]), dict)
784         self.assertEqual(type(data[1]), dict)
785
786         self.assertEqual(data[0]['description'], "annotation1")
787         self.assertEqual(data[1]['description'], "annotation2")
788
789
790 class UnicodeTest(TasklibTest):
791
792     def test_unicode_task(self):
793         Task(self.tw, description="†åßk").save()
794         self.tw.tasks.get()
795
796     def test_non_unicode_task(self):
797         Task(self.tw, description="test task").save()
798         self.tw.tasks.get()
799
800 class ReadOnlyDictViewTest(unittest.TestCase):
801
802     def setUp(self):
803         self.sample = dict(l=[1,2,3], d={'k':'v'})
804         self.original_sample = copy.deepcopy(self.sample)
805         self.view = ReadOnlyDictView(self.sample)
806
807     def test_readonlydictview_getitem(self):
808         l = self.view['l']
809         self.assertEqual(l, self.sample['l'])
810
811         # Assert that modification changed only copied value
812         l.append(4)
813         self.assertNotEqual(l, self.sample['l'])
814
815         # Assert that viewed dict is not changed
816         self.assertEqual(self.sample, self.original_sample)
817
818     def test_readonlydictview_contains(self):
819         self.assertEqual('l' in self.view, 'l' in self.sample)
820         self.assertEqual('d' in self.view, 'd' in self.sample)
821         self.assertEqual('k' in self.view, 'k' in self.sample)
822
823         # Assert that viewed dict is not changed
824         self.assertEqual(self.sample, self.original_sample)
825
826     def test_readonlydictview_iter(self):
827         self.assertEqual(list(k for k in self.view),
828                          list(k for k in self.sample))
829
830         # Assert the view is correct after modification
831         self.sample['new'] = 'value'
832         self.assertEqual(list(k for k in self.view),
833                          list(k for k in self.sample))
834
835     def test_readonlydictview_len(self):
836         self.assertEqual(len(self.view), len(self.sample))
837
838         # Assert the view is correct after modification
839         self.sample['new'] = 'value'
840         self.assertEqual(len(self.view), len(self.sample))
841
842     def test_readonlydictview_get(self):
843         l = self.view.get('l')
844         self.assertEqual(l, self.sample.get('l'))
845
846         # Assert that modification changed only copied value
847         l.append(4)
848         self.assertNotEqual(l, self.sample.get('l'))
849
850         # Assert that viewed dict is not changed
851         self.assertEqual(self.sample, self.original_sample)
852
853     def test_readonlydict_items(self):
854         view_items = self.view.items()
855         sample_items = list(self.sample.items())
856         self.assertEqual(view_items, sample_items)
857
858         view_items.append('newkey')
859         self.assertNotEqual(view_items, sample_items)
860         self.assertEqual(self.sample, self.original_sample)
861
862     def test_readonlydict_values(self):
863         view_values = self.view.values()
864         sample_values = list(self.sample.values())
865         self.assertEqual(view_values, sample_values)
866
867         view_list_item = list(filter(lambda x: type(x) is list,
868                                      view_values))[0]
869         view_list_item.append(4)
870         self.assertNotEqual(view_values, sample_values)
871         self.assertEqual(self.sample, self.original_sample)