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

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