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

d498f099c28a3678fec396bb4cdce16cdba93124
[etc/taskwarrior.git] / tasklib / tests.py
1 # coding=utf-8
2
3 import datetime
4 import itertools
5 import six
6 import shutil
7 import tempfile
8 import unittest
9
10 from .task import TaskWarrior, Task
11
12 # http://taskwarrior.org/docs/design/task.html , Section: The Attributes
13 TASK_STANDARD_ATTRS = (
14     'status',
15     'uuid',
16     'entry',
17     'description',
18     'start',
19     'end',
20     'due',
21     'until',
22     'wait',
23     'modified',
24     'scheduled',
25     'recur',
26     'mask',
27     'imask',
28     'parent',
29     'project',
30     'priority',
31     'depends',
32     'tags',
33     'annotation',
34 )
35
36 class TasklibTest(unittest.TestCase):
37
38     def setUp(self):
39         self.tmp = tempfile.mkdtemp(dir='.')
40         self.tw = TaskWarrior(data_location=self.tmp)
41
42     def tearDown(self):
43         shutil.rmtree(self.tmp)
44
45
46 class TaskFilterTest(TasklibTest):
47
48     def test_all_empty(self):
49         self.assertEqual(len(self.tw.tasks.all()), 0)
50
51     def test_all_non_empty(self):
52         Task(self.tw, description="test task").save()
53         self.assertEqual(len(self.tw.tasks.all()), 1)
54         self.assertEqual(self.tw.tasks.all()[0]['description'], 'test task')
55         self.assertEqual(self.tw.tasks.all()[0]['status'], 'pending')
56
57     def test_pending_non_empty(self):
58         Task(self.tw, description="test task").save()
59         self.assertEqual(len(self.tw.tasks.pending()), 1)
60         self.assertEqual(self.tw.tasks.pending()[0]['description'],
61                          'test task')
62         self.assertEqual(self.tw.tasks.pending()[0]['status'], 'pending')
63
64     def test_completed_empty(self):
65         Task(self.tw, description="test task").save()
66         self.assertEqual(len(self.tw.tasks.completed()), 0)
67
68     def test_completed_non_empty(self):
69         Task(self.tw, description="test task").save()
70         self.assertEqual(len(self.tw.tasks.completed()), 0)
71         self.tw.tasks.all()[0].done()
72         self.assertEqual(len(self.tw.tasks.completed()), 1)
73
74     def test_filtering_by_attribute(self):
75         Task(self.tw, description="no priority task").save()
76         Task(self.tw, priority="H", description="high priority task").save()
77         self.assertEqual(len(self.tw.tasks.all()), 2)
78
79         # Assert that the correct number of tasks is returned
80         self.assertEqual(len(self.tw.tasks.filter(priority="H")), 1)
81
82         # Assert that the correct tasks are returned
83         high_priority_task = self.tw.tasks.get(priority="H")
84         self.assertEqual(high_priority_task['description'], "high priority task")
85
86     def test_filtering_by_empty_attribute(self):
87         Task(self.tw, description="no priority task").save()
88         Task(self.tw, priority="H", description="high priority task").save()
89         self.assertEqual(len(self.tw.tasks.all()), 2)
90
91         # Assert that the correct number of tasks is returned
92         self.assertEqual(len(self.tw.tasks.filter(priority=None)), 1)
93
94         # Assert that the correct tasks are returned
95         no_priority_task = self.tw.tasks.get(priority=None)
96         self.assertEqual(no_priority_task['description'], "no priority task")
97
98     def test_filter_for_task_with_space_in_descripition(self):
99         task = Task(self.tw, description="test task")
100         task.save()
101
102         filtered_task = self.tw.tasks.get(description="test task")
103         self.assertEqual(filtered_task['description'], "test task")
104
105     def test_filter_for_task_without_space_in_descripition(self):
106         task = Task(self.tw, description="test")
107         task.save()
108
109         filtered_task = self.tw.tasks.get(description="test")
110         self.assertEqual(filtered_task['description'], "test")
111
112     def test_filter_for_task_with_space_in_project(self):
113         task = Task(self.tw, description="test", project="random project")
114         task.save()
115
116         filtered_task = self.tw.tasks.get(project="random project")
117         self.assertEqual(filtered_task['project'], "random project")
118
119     def test_filter_for_task_without_space_in_project(self):
120         task = Task(self.tw, description="test", project="random")
121         task.save()
122
123         filtered_task = self.tw.tasks.get(project="random")
124         self.assertEqual(filtered_task['project'], "random")
125
126
127 class TaskTest(TasklibTest):
128
129     def test_create_unsaved_task(self):
130         # Make sure a new task is not saved unless explicitly called for
131         t = Task(self.tw, description="test task")
132         self.assertEqual(len(self.tw.tasks.all()), 0)
133
134     # TODO: once python 2.6 compatiblity is over, use context managers here
135     #       and in all subsequent tests for assertRaises
136
137     def test_delete_unsaved_task(self):
138         t = Task(self.tw, description="test task")
139         self.assertRaises(Task.NotSaved, t.delete)
140
141     def test_complete_unsaved_task(self):
142         t = Task(self.tw, description="test task")
143         self.assertRaises(Task.NotSaved, t.done)
144
145     def test_refresh_unsaved_task(self):
146         t = Task(self.tw, description="test task")
147         self.assertRaises(Task.NotSaved, t.refresh)
148
149     def test_delete_deleted_task(self):
150         t = Task(self.tw, description="test task")
151         t.save()
152         t.delete()
153
154         self.assertRaises(Task.DeletedTask, t.delete)
155
156     def test_complete_completed_task(self):
157         t = Task(self.tw, description="test task")
158         t.save()
159         t.done()
160
161         self.assertRaises(Task.CompletedTask, t.done)
162
163     def test_complete_deleted_task(self):
164         t = Task(self.tw, description="test task")
165         t.save()
166         t.delete()
167
168         self.assertRaises(Task.DeletedTask, t.done)
169
170     def test_modify_simple_attribute_without_space(self):
171         t = Task(self.tw, description="test")
172         t.save()
173
174         self.assertEquals(t['description'], "test")
175
176         t['description'] = "test-modified"
177         t.save()
178
179         self.assertEquals(t['description'], "test-modified")
180
181     def test_modify_simple_attribute_with_space(self):
182         # Space can pose problems with parsing
183         t = Task(self.tw, description="test task")
184         t.save()
185
186         self.assertEquals(t['description'], "test task")
187
188         t['description'] = "test task modified"
189         t.save()
190
191         self.assertEquals(t['description'], "test task modified")
192
193     def test_empty_dependency_set_of_unsaved_task(self):
194         t = Task(self.tw, description="test task")
195         self.assertEqual(t['depends'], set())
196
197     def test_empty_dependency_set_of_saved_task(self):
198         t = Task(self.tw, description="test task")
199         t.save()
200         self.assertEqual(t['depends'], set())
201
202     def test_set_unsaved_task_as_dependency(self):
203         # Adds only one dependency to task with no dependencies
204         t = Task(self.tw, description="test task")
205         dependency = Task(self.tw, description="needs to be done first")
206
207         # We only save the parent task, dependency task is unsaved
208         t.save()
209         t['depends'] = set([dependency])
210
211         self.assertRaises(Task.NotSaved, t.save)
212
213     def test_set_simple_dependency_set(self):
214         # Adds only one dependency to task with no dependencies
215         t = Task(self.tw, description="test task")
216         dependency = Task(self.tw, description="needs to be done first")
217
218         t.save()
219         dependency.save()
220
221         t['depends'] = set([dependency])
222
223         self.assertEqual(t['depends'], set([dependency]))
224
225     def test_set_complex_dependency_set(self):
226         # Adds two dependencies to task with no dependencies
227         t = Task(self.tw, description="test task")
228         dependency1 = Task(self.tw, description="needs to be done first")
229         dependency2 = Task(self.tw, description="needs to be done second")
230
231         t.save()
232         dependency1.save()
233         dependency2.save()
234
235         t['depends'] = set([dependency1, dependency2])
236
237         self.assertEqual(t['depends'], set([dependency1, dependency2]))
238
239     def test_remove_from_dependency_set(self):
240         # Removes dependency from task with two dependencies
241         t = Task(self.tw, description="test task")
242         dependency1 = Task(self.tw, description="needs to be done first")
243         dependency2 = Task(self.tw, description="needs to be done second")
244
245         dependency1.save()
246         dependency2.save()
247
248         t['depends'] = set([dependency1, dependency2])
249         t.save()
250
251         t['depends'].remove(dependency2)
252         t.save()
253
254         self.assertEqual(t['depends'], set([dependency1]))
255
256     def test_add_to_dependency_set(self):
257         # Adds dependency to task with one dependencies
258         t = Task(self.tw, description="test task")
259         dependency1 = Task(self.tw, description="needs to be done first")
260         dependency2 = Task(self.tw, description="needs to be done second")
261
262         dependency1.save()
263         dependency2.save()
264
265         t['depends'] = set([dependency1])
266         t.save()
267
268         t['depends'].add(dependency2)
269         t.save()
270
271         self.assertEqual(t['depends'], set([dependency1, dependency2]))
272
273     def test_add_to_empty_dependency_set(self):
274         # Adds dependency to task with one dependencies
275         t = Task(self.tw, description="test task")
276         dependency = Task(self.tw, description="needs to be done first")
277
278         dependency.save()
279
280         t['depends'].add(dependency)
281         t.save()
282
283         self.assertEqual(t['depends'], set([dependency]))
284
285     def test_simple_dependency_set_save_repeatedly(self):
286         # Adds only one dependency to task with no dependencies
287         t = Task(self.tw, description="test task")
288         dependency = Task(self.tw, description="needs to be done first")
289         dependency.save()
290
291         t['depends'] = set([dependency])
292         t.save()
293
294         # We taint the task, but keep depends intact
295         t['description'] = "test task modified"
296         t.save()
297
298         self.assertEqual(t['depends'], set([dependency]))
299
300         # We taint the task, but assign the same set to the depends
301         t['depends'] = set([dependency])
302         t['description'] = "test task modified again"
303         t.save()
304
305         self.assertEqual(t['depends'], set([dependency]))
306
307     def test_compare_different_tasks(self):
308         # Negative: compare two different tasks
309         t1 = Task(self.tw, description="test task")
310         t2 = Task(self.tw, description="test task")
311
312         t1.save()
313         t2.save()
314
315         self.assertEqual(t1 == t2, False)
316
317     def test_compare_same_task_object(self):
318         # Compare Task object wit itself
319         t = Task(self.tw, description="test task")
320         t.save()
321
322         self.assertEqual(t == t, True)
323
324     def test_compare_same_task(self):
325         # Compare the same task using two different objects
326         t1 = Task(self.tw, description="test task")
327         t1.save()
328
329         t2 = self.tw.tasks.get(uuid=t1['uuid'])
330         self.assertEqual(t1 == t2, True)
331
332     def test_compare_unsaved_tasks(self):
333         # t1 and t2 are unsaved tasks, considered to be unequal
334         # despite the content of data
335         t1 = Task(self.tw, description="test task")
336         t2 = Task(self.tw, description="test task")
337
338         self.assertEqual(t1 == t2, False)
339
340     def test_hash_unsaved_tasks(self):
341         # Considered equal, it's the same object
342         t1 = Task(self.tw, description="test task")
343         t2 = t1
344         self.assertEqual(hash(t1) == hash(t2), True)
345
346     def test_hash_same_task(self):
347         # Compare the hash of the task using two different objects
348         t1 = Task(self.tw, description="test task")
349         t1.save()
350
351         t2 = self.tw.tasks.get(uuid=t1['uuid'])
352         self.assertEqual(t1.__hash__(), t2.__hash__())
353
354     def test_adding_task_with_priority(self):
355         t = Task(self.tw, description="test task", priority="M")
356         t.save()
357
358     def test_removing_priority_with_none(self):
359         t = Task(self.tw, description="test task", priority="L")
360         t.save()
361
362         # Remove the priority mark
363         t['priority'] = None
364         t.save()
365
366         # Assert that priority is not there after saving
367         self.assertEqual(t['priority'], None)
368
369     def test_adding_task_with_due_time(self):
370         t = Task(self.tw, description="test task", due=datetime.datetime.now())
371         t.save()
372
373     def test_removing_due_time_with_none(self):
374         t = Task(self.tw, description="test task", due=datetime.datetime.now())
375         t.save()
376
377         # Remove the due timestamp
378         t['due'] = None
379         t.save()
380
381         # Assert that due timestamp is no longer there
382         self.assertEqual(t['due'], None)
383
384     def test_modified_fields_new_task(self):
385         t = Task(self.tw)
386
387         # This should be empty with new task
388         self.assertEqual(set(t._modified_fields), set())
389
390         # Modify the task
391         t['description'] = "test task"
392         self.assertEqual(set(t._modified_fields), set(['description']))
393
394         t['due'] = datetime.datetime(2014, 2, 14, 14, 14, 14)  # <3
395         self.assertEqual(set(t._modified_fields), set(['description', 'due']))
396
397         t['project'] = "test project"
398         self.assertEqual(set(t._modified_fields),
399                          set(['description', 'due', 'project']))
400
401         # List of modified fields should clear out when saved
402         t.save()
403         self.assertEqual(set(t._modified_fields), set())
404
405         # Reassigning the fields with the same values now should not produce
406         # modified fields
407         t['description'] = "test task"
408         t['due'] = datetime.datetime(2014, 2, 14, 14, 14, 14)  # <3
409         t['project'] = "test project"
410         self.assertEqual(set(t._modified_fields), set())
411
412     def test_modified_fields_loaded_task(self):
413         t = Task(self.tw)
414
415         # Modify the task
416         t['description'] = "test task"
417         t['due'] = datetime.datetime(2014, 2, 14, 14, 14, 14)  # <3
418         t['project'] = "test project"
419
420         dependency = Task(self.tw, description="dependency")
421         dependency.save()
422         t['depends'] = set([dependency])
423
424         # List of modified fields should clear out when saved
425         t.save()
426         self.assertEqual(set(t._modified_fields), set())
427
428         # Get the task by using a filter by UUID
429         t2 = self.tw.tasks.get(uuid=t['uuid'])
430
431         # Reassigning the fields with the same values now should not produce
432         # modified fields
433         t['description'] = "test task"
434         t['due'] = datetime.datetime(2014, 2, 14, 14, 14, 14)  # <3
435         t['project'] = "test project"
436         t['depends'] = set([dependency])
437         self.assertEqual(set(t._modified_fields), set())
438
439     def test_modified_fields_not_affected_by_reading(self):
440         t = Task(self.tw)
441
442         for field in TASK_STANDARD_ATTRS:
443             value = t[field]
444
445         self.assertEqual(set(t._modified_fields), set())
446
447     def test_setting_read_only_attrs_through_init(self):
448         # Test that we are unable to set readonly attrs through __init__
449         for readonly_key in Task.read_only_fields:
450             kwargs = {'description': 'test task', readonly_key: 'value'}
451             self.assertRaises(RuntimeError,
452                               lambda: Task(self.tw, **kwargs))
453
454     def test_setting_read_only_attrs_through_setitem(self):
455         # Test that we are unable to set readonly attrs through __init__
456         for readonly_key in Task.read_only_fields:
457             t = Task(self.tw, description='test task')
458             self.assertRaises(RuntimeError,
459                               lambda: t.__setitem__(readonly_key, 'value'))
460
461     def test_saving_unmodified_task(self):
462         t = Task(self.tw, description="test task")
463         t.save()
464         t.save()
465
466     def test_adding_tag_by_appending(self):
467         t = Task(self.tw, description="test task", tags=['test1'])
468         t.save()
469         t['tags'].append('test2')
470         t.save()
471         self.assertEqual(t['tags'], ['test1', 'test2'])
472
473     def test_adding_tag_by_appending_empty(self):
474         t = Task(self.tw, description="test task")
475         t.save()
476         t['tags'].append('test')
477         t.save()
478         self.assertEqual(t['tags'], ['test'])
479
480
481 class TaskFromHookTest(TasklibTest):
482
483     input_add_data = six.StringIO(
484         '{"description":"Buy some milk",'
485         '"entry":"20141118T050231Z",'
486         '"status":"pending",'
487         '"uuid":"a360fc44-315c-4366-b70c-ea7e7520b749"}')
488
489     input_modify_data = six.StringIO(input_add_data.getvalue() + '\n' +
490         '{"description":"Buy some milk finally",'
491         '"entry":"20141118T050231Z",'
492         '"status":"completed",'
493         '"uuid":"a360fc44-315c-4366-b70c-ea7e7520b749"}')
494
495     exported_raw_data = (
496         '{"project":"Home",'
497          '"due":"20150101T232323Z",'
498          '"description":"test task"}')
499
500     def test_setting_up_from_add_hook_input(self):
501         t = Task.from_input(input_file=self.input_add_data)
502         self.assertEqual(t['description'], "Buy some milk")
503         self.assertEqual(t.pending, True)
504
505     def test_setting_up_from_modified_hook_input(self):
506         t = Task.from_input(input_file=self.input_modify_data, modify=True)
507         self.assertEqual(t['description'], "Buy some milk finally")
508         self.assertEqual(t.pending, False)
509         self.assertEqual(t.completed, True)
510
511         self.assertEqual(t._original_data['status'], "pending")
512         self.assertEqual(t._original_data['description'], "Buy some milk")
513         self.assertEqual(set(t._modified_fields),
514                          set(['status', 'description']))
515
516     def test_export_data(self):
517         t = Task(self.tw, description="test task",
518             project="Home", due=datetime.datetime(2015,1,1,23,23,23))
519
520         # Check that the output is a permutation of:
521         # {"project":"Home","description":"test task","due":"20150101232323Z"}
522         allowed_segments = self.exported_raw_data[1:-1].split(',')
523         allowed_output = [
524             '{' + ','.join(segments) + '}'
525             for segments in itertools.permutations(allowed_segments)
526         ]
527
528         self.assertTrue(any(t.export_data() == expected
529                             for expected in allowed_output))
530
531
532 class AnnotationTest(TasklibTest):
533
534     def setUp(self):
535         super(AnnotationTest, self).setUp()
536         Task(self.tw, description="test task").save()
537
538     def test_adding_annotation(self):
539         task = self.tw.tasks.get()
540         task.add_annotation('test annotation')
541         self.assertEqual(len(task['annotations']), 1)
542         ann = task['annotations'][0]
543         self.assertEqual(ann['description'], 'test annotation')
544
545     def test_removing_annotation(self):
546         task = self.tw.tasks.get()
547         task.add_annotation('test annotation')
548         ann = task['annotations'][0]
549         ann.remove()
550         self.assertEqual(len(task['annotations']), 0)
551
552     def test_removing_annotation_by_description(self):
553         task = self.tw.tasks.get()
554         task.add_annotation('test annotation')
555         task.remove_annotation('test annotation')
556         self.assertEqual(len(task['annotations']), 0)
557
558     def test_removing_annotation_by_obj(self):
559         task = self.tw.tasks.get()
560         task.add_annotation('test annotation')
561         ann = task['annotations'][0]
562         task.remove_annotation(ann)
563         self.assertEqual(len(task['annotations']), 0)
564
565     def test_annotation_after_modification(self):
566          task = self.tw.tasks.get()
567          task['project'] = 'test'
568          task.add_annotation('I should really do this task')
569          self.assertEqual(task['project'], 'test')
570          task.save()
571          self.assertEqual(task['project'], 'test')
572
573
574 class UnicodeTest(TasklibTest):
575
576     def test_unicode_task(self):
577         Task(self.tw, description="†åßk").save()
578         self.tw.tasks.get()
579
580     def test_non_unicode_task(self):
581         Task(self.tw, description="test task").save()
582         self.tw.tasks.get()