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