Use list.sort's keyword parameters
[joel/kofoto.git] / src / test / shelftests.py
1 #! /usr/bin/env python
2
3 import gc
4 import os
5 import shutil
6 import sys
7 import threading
8 import unittest
9
10 if __name__ == "__main__":
11     cwd = os.getcwd()
12     libdir = unicode(os.path.realpath(
13         os.path.join(os.path.dirname(sys.argv[0]), "..", "packages")))
14     os.chdir(libdir)
15     sys.path.insert(0, libdir)
16 from kofoto.shelf import \
17     Shelf, \
18     computeImageHash, \
19     makeValidTag, \
20     verifyValidAlbumTag, \
21     verifyValidCategoryTag
22 from kofoto.albumtype import AlbumType
23 from kofoto.imageversiontype import ImageVersionType
24 from kofoto.shelfexceptions import \
25     AlbumDoesNotExistError, \
26     AlbumExistsError, \
27     BadAlbumTagError, \
28     BadCategoryTagError, \
29     CategoriesAlreadyConnectedError, \
30     CategoryDoesNotExistError, \
31     CategoryExistsError, \
32     CategoryLoopError, \
33     CategoryPresentError, \
34     FailedWritingError, \
35     ImageDoesNotExistError, \
36     ImageVersionDoesNotExistError, \
37     ImageVersionExistsError, \
38     ShelfLockedError, \
39     ShelfNotFoundError, \
40     UndeletableAlbumError, \
41     UndeletableAlbumError, \
42     UnsettableChildrenError, \
43     UnsupportedShelfError
44
45 PICDIR = unicode(os.path.realpath(
46     os.path.join("..", "reference_pictures", "working")))
47
48 ######################################################################
49
50 db = "shelf.tmp"
51
52 def removeTmpDb():
53     for x in [db, db + "-journal"]:
54         if os.path.exists(x):
55             os.unlink(x)
56
57 class LockerThread(threading.Thread):
58     def __init__(self, mContinue, ltContinue):
59         threading.Thread.__init__(self)
60         self.mContinue = mContinue
61         self.ltContinue = ltContinue
62
63     def run(self):
64         s = Shelf(db)
65         s.create()
66         s.begin()
67         self.mContinue.set()
68         self.ltContinue.wait()
69         s.rollback()
70
71 ######################################################################
72
73 class TestPublicShelfFunctions(unittest.TestCase):
74     def test_computeImageHash(self):
75         s = computeImageHash(os.path.join(PICDIR, "arlaharen.png"))
76         assert s == "39a1266d2689f53d48b09a5e0ca0af1f"
77         try:
78             computeImageHash("nonexisting")
79         except IOError:
80             pass
81         else:
82             assert False, s
83
84     def test_verifyValidAlbumTag(self):
85         # Valid tags.
86         for x in ("foo", "1foo", "and", "exactly", "not", "or"):
87             try:
88                 verifyValidAlbumTag(x)
89             except:
90                 assert False, x
91         # Invalid tags.
92         for x in (None, 1, 1L, "1" "foo " "@foo"):
93             try:
94                 verifyValidAlbumTag(x)
95             except BadAlbumTagError:
96                 pass
97             else:
98                 assert False, x
99
100     def test_verifyValidCategoryTag(self):
101         # Valid tags.
102         for x in ("foo", "1foo"):
103             try:
104                 verifyValidCategoryTag(x)
105             except:
106                 assert False, x
107         # Invalid tags.
108         for x in (None, 1, 1L, "1" "foo " "@foo",
109                   "and", "exactly", "not", "or"):
110             try:
111                 verifyValidCategoryTag(x)
112             except BadCategoryTagError:
113                 pass
114             else:
115                 assert False, x
116
117     def test_makeValidTag(self):
118         for tag, validTag in [("", "_"),
119                               ("@", "_"),
120                               (" ", "_"),
121                               ("1", "1_"),
122                               ("@foo_", "foo_"),
123                               ("fo@o __", "fo@o__")]:
124             assert makeValidTag(tag) == validTag, (tag, validTag)
125
126 class TestNegativeShelfOpens(unittest.TestCase):
127     def tearDown(self):
128         removeTmpDb()
129
130     def test_NonexistingShelf(self):
131         try:
132             s = Shelf(db)
133             s.begin()
134         except ShelfNotFoundError:
135             pass
136         else:
137             assert False
138         assert not os.path.exists(db)
139
140     def test_BadShelf(self):
141         file(db, "w") # Create empty file.
142         try:
143             s = Shelf(db)
144             s.begin()
145         except UnsupportedShelfError:
146             pass
147         else:
148             assert False
149
150     def test_LockedShelf(self):
151         mContinue = threading.Event()
152         ltContinue = threading.Event()
153         lt = LockerThread(mContinue, ltContinue)
154         lt.start()
155         mContinue.wait()
156         try:
157             try:
158                 s = Shelf(db)
159                 s.begin()
160             except ShelfLockedError:
161                 pass
162             else:
163                 assert False
164         finally:
165             ltContinue.set()
166             lt.join()
167
168 class TestShelfCreation(unittest.TestCase):
169     def tearDown(self):
170         removeTmpDb()
171
172     def test_CreateShelf1(self):
173         s = Shelf(db)
174         s.create()
175         assert os.path.exists(db)
176
177     def test_CreateShelf2(self):
178         file(db, "w") # Create empty file.
179         s = Shelf(db)
180         try:
181             s.create()
182         except FailedWritingError:
183             pass
184         else:
185             assert False
186
187 class TestShelfMemoryLeakage(unittest.TestCase):
188     def tearDown(self):
189         removeTmpDb()
190
191     def test_MemoryLeak1(self):
192         s = Shelf(db)
193         s.create()
194         assert gc.collect() == 0
195
196     def test_MemoryLeak2(self):
197         s = Shelf(db)
198         s.create()
199         s.begin()
200         s.getObject(0)
201         s.rollback()
202         assert gc.collect() == 0
203
204     def test_MemoryLeak3(self):
205         s = Shelf(db)
206         s.create()
207         s.begin()
208         s.getObject(0)
209         s.commit()
210         assert gc.collect() == 0
211
212 class TestShelfTransactions(unittest.TestCase):
213     def tearDown(self):
214         removeTmpDb()
215
216     def test_commit(self):
217         s = Shelf(db)
218         s.create()
219         s.begin()
220         s.createAlbum(u"foo")
221         assert s.getAlbumByTag(u"foo")
222         s.commit()
223         s = Shelf(db)
224         s.begin()
225         assert s.getAlbumByTag(u"foo")
226         s.rollback()
227
228     def test_rollback(self):
229         s = Shelf(db)
230         s.create()
231         s.begin()
232         s.createAlbum(u"foo")
233         s.rollback()
234         s.begin()
235         try:
236             s.getAlbumByTag(u"foo")
237         except AlbumDoesNotExistError:
238             pass
239         else:
240             assert False
241
242     def test_isModified(self):
243         s = Shelf(db)
244         s.create()
245         s.begin()
246         assert not s.isModified()
247         s.createAlbum(u"foo")
248         assert s.isModified()
249         s.rollback()
250         s.begin()
251         assert not s.isModified()
252         s.rollback()
253
254     def test_registerModificationCallback(self):
255         res = [False]
256         def f(x):
257             res[0] = True
258         s = Shelf(db)
259         s.create()
260         s.begin()
261         s.registerModificationCallback(f)
262         assert not res[0]
263         s.createAlbum(u"foo")
264         assert res[0]
265         s.rollback()
266         res[0] = False
267         s.begin()
268         assert not res[0]
269         s.unregisterModificationCallback(f)
270         s.createAlbum(u"foo")
271         assert not res[0]
272         s.rollback()
273
274 class TestShelfFixture(unittest.TestCase):
275     def setUp(self):
276         self.shelf = Shelf(db)
277         self.shelf.create()
278         self.shelf.begin()
279         root = self.shelf.getRootAlbum()
280         alpha = self.shelf.createAlbum(u"alpha")
281         beta = self.shelf.createAlbum(u"beta")
282         children = [beta]
283         for x in os.listdir(PICDIR):
284             loc = os.path.join(PICDIR, x)
285             if not os.path.isfile(loc):
286                 continue
287             image = self.shelf.createImage()
288             imageversion = self.shelf.createImageVersion(
289                 image, loc, ImageVersionType.Original)
290             children.append(image)
291         del children[-1] # The last image becomes orphaned.
292         alpha.setChildren(children)
293         beta.setChildren(list(beta.getChildren()) + [children[-1]])
294         root.setChildren(list(root.getChildren()) + [alpha, beta])
295         self.shelf.createAlbum(u"epsilon", AlbumType.Plain) # Orphaned album.
296         zeta = self.shelf.createAlbum(u"zeta", AlbumType.Search)
297         zeta.setAttribute(u"query", u"a")
298         root.setChildren(list(root.getChildren()) + [zeta])
299
300         cat_a = self.shelf.createCategory(u"a", u"A")
301         cat_b = self.shelf.createCategory(u"b", u"B")
302         cat_c = self.shelf.createCategory(u"c", u"C")
303         cat_d = self.shelf.createCategory(u"d", u"D")
304         cat_a.connectChild(cat_b)
305         cat_a.connectChild(cat_c)
306         cat_b.connectChild(cat_d)
307         cat_c.connectChild(cat_d)
308
309         self.shelf.flushObjectCache()
310         self.shelf.flushCategoryCache()
311
312     def tearDown(self):
313         self.shelf.rollback()
314         removeTmpDb()
315
316 class TestShelfMethods(TestShelfFixture):
317     def test_flushCaches(self):
318         self.shelf.flushCategoryCache()
319         self.shelf.flushObjectCache()
320
321     def test_getStatistics(self):
322         s = self.shelf.getStatistics()
323         assert s["nalbums"] == 6
324         assert s["nimages"] == 11
325         assert s["nimageversions"] == 11
326
327     def test_createdObjects(self):
328         root = self.shelf.getRootAlbum()
329         children = list(root.getChildren())
330         assert len(children) == 4
331         orphans, alpha, beta, zeta = children
332         assert self.shelf.getAlbum(alpha.getId()) == alpha
333         assert self.shelf.getAlbumByTag(u"beta") == beta
334         assert len(list(alpha.getChildren())) == 11
335         assert len(list(beta.getChildren())) == 1
336
337     def test_createdAttributes(self):
338         imageversion = self.shelf.getImageVersionByLocation(
339             os.path.join(PICDIR, "Canon_Digital_IXUS.jpg"))
340         image = imageversion.getImage()
341         assert image.getAttribute(u"captured") == "2002-02-02 22:20:51"
342         assert image.getAttribute(u"cameramake") == "Canon"
343         assert image.getAttribute(u"cameramodel") == "Canon DIGITAL IXUS"
344
345     def test_negativeAlbumCreation(self):
346         try:
347             self.shelf.createAlbum(u"beta")
348         except AlbumExistsError:
349             pass
350         else:
351             assert False
352
353     def test_getAlbum(self):
354         album = self.shelf.getAlbum(0)
355         assert album == self.shelf.getRootAlbum()
356
357     def test_negativeGetAlbum(self):
358         try:
359             self.shelf.getAlbum(12345678)
360         except AlbumDoesNotExistError:
361             pass
362         else:
363             assert False
364
365     def test_getAlbumByTag(self):
366         album = self.shelf.getAlbumByTag(u"root")
367         assert album == self.shelf.getRootAlbum()
368
369     def test_negativeGetAlbumByTag(self):
370         try:
371             self.shelf.getAlbum(u"12345678")
372         except AlbumDoesNotExistError:
373             pass
374         else:
375             assert False
376
377     def test_getRootAlbum(self):
378         root = self.shelf.getRootAlbum()
379         assert root.getId() == 0
380         assert root == self.shelf.getAlbumByTag(u"root")
381
382     def test_getAllAlbums(self):
383         albums = list(self.shelf.getAllAlbums())
384         assert len(albums) == 6
385
386     def test_getAllImageVersions(self):
387         imageversions = list(self.shelf.getAllImageVersions())
388         assert len(imageversions) == 11
389
390     def test_getImageVersionsInDirectory(self):
391         self.shelf.flushImageVersionCache()
392
393         imageversions = list(self.shelf.getImageVersionsInDirectory(u"."))
394         assert len(imageversions) == 0
395
396         # Image versions not in cache.
397         imageversions = list(self.shelf.getImageVersionsInDirectory(PICDIR))
398         assert len(imageversions) == 11
399
400         # Image versions in cache.
401         imageversions = list(self.shelf.getImageVersionsInDirectory(PICDIR))
402         assert len(imageversions) == 11
403
404     def test_deleteAlbum(self):
405         album = self.shelf.getAlbumByTag(u"beta")
406         self.shelf.deleteAlbum(album.getId())
407
408     def test_negativeRootAlbumDeletion(self):
409         root = self.shelf.getRootAlbum()
410         try:
411             self.shelf.deleteAlbum(root.getId())
412         except UndeletableAlbumError:
413             pass
414         else:
415             assert False
416
417     def test_negativeAlbumDeletion(self):
418         try:
419             self.shelf.deleteAlbum(12345678)
420         except AlbumDoesNotExistError:
421             pass
422         else:
423             assert False
424
425     def test_negativeImageCreation(self):
426         try:
427             image = self.shelf.createImage()
428             self.shelf.createImageVersion(
429                 image,
430                 os.path.join(PICDIR, "arlaharen.png"),
431                 ImageVersionType.Original)
432         except ImageVersionExistsError:
433             pass
434         else:
435             assert False
436
437     def test_getImage(self):
438         imageversion = self.shelf.getImageVersionByLocation(
439             os.path.join(PICDIR, "arlaharen.png"))
440         image = imageversion.getImage()
441         assert self.shelf.getImage(image.getId()) == image
442
443     def test_negativeGetImage(self):
444         try:
445             self.shelf.getImage(12345678)
446         except ImageDoesNotExistError:
447             pass
448         else:
449             assert False
450
451     def test_getImageVersion(self):
452         imageversion = self.shelf.getImageVersionByLocation(
453             os.path.join(PICDIR, "arlaharen.png"))
454         assert self.shelf.getImageVersion(imageversion.getId()) == imageversion
455
456     def test_negativeGetImageVersion(self):
457         try:
458             self.shelf.getImageVersion(12345678)
459         except ImageVersionDoesNotExistError:
460             pass
461         else:
462             assert False
463
464     def test_getImageVersionByHash(self):
465         imageversion = self.shelf.getImageVersionByLocation(
466             os.path.join(PICDIR, "arlaharen.png"))
467         assert self.shelf.getImageVersionByHash(
468             imageversion.getHash()) == imageversion
469
470     def test_negativeGetImageVersionByHash(self):
471         try:
472             self.shelf.getImageVersion(u"badhash")
473         except ImageVersionDoesNotExistError:
474             pass
475         else:
476             assert False
477
478     def test_getImageVersionByLocation(self):
479         imageversion1 = self.shelf.getImageVersionByLocation(
480             os.path.join(PICDIR, u"arlaharen.png"))
481         currentDir = os.getcwd()
482         try:
483             os.chdir(PICDIR)
484             imageversion2 = self.shelf.getImageVersionByLocation(
485                 u"arlaharen.png")
486         finally:
487             os.chdir(currentDir)
488         assert imageversion1 == imageversion2
489
490     def test_negativeGetImageVersionByHash(self):
491         try:
492             self.shelf.getImageVersionByLocation(u"/bad/location")
493         except ImageVersionDoesNotExistError:
494             pass
495         else:
496             assert False
497
498     def test_deleteImage(self):
499         imageversion = self.shelf.getImageVersionByLocation(
500             os.path.join(PICDIR, "arlaharen.png"))
501         imageid = imageversion.getImage().getId()
502         self.shelf.deleteImage(imageid)
503         try:
504             self.shelf.getImageVersionByLocation(
505                 os.path.join(PICDIR, "arlaharen.png"))
506         except ImageVersionDoesNotExistError:
507             pass
508         else:
509             assert False
510
511     def test_negativeImageDeletion(self):
512         try:
513             self.shelf.deleteImage(12345678)
514         except ImageDoesNotExistError:
515             pass
516         else:
517             assert False
518
519     def test_deleteImageVersion(self):
520         imageversion = self.shelf.getImageVersionByLocation(
521             os.path.join(PICDIR, "arlaharen.png"))
522         self.shelf.deleteImageVersion(imageversion.getId())
523
524     def test_negativeImageVersionDeletion(self):
525         try:
526             self.shelf.deleteImageVersion(12345678)
527         except ImageVersionDoesNotExistError:
528             pass
529         else:
530             assert False
531
532     def test_getObject(self):
533         rootalbum = self.shelf.getRootAlbum()
534         album = self.shelf.getObject(rootalbum.getId())
535         assert album == rootalbum
536
537     def test_deleteObject(self):
538         albumid = self.shelf.getAlbumByTag(u"beta").getId()
539         imageversion = self.shelf.getImageVersionByLocation(
540             os.path.join(PICDIR, "arlaharen.png"))
541         imageid = imageversion.getImage().getId()
542         self.shelf.deleteObject(albumid)
543         self.shelf.deleteObject(imageid)
544         try:
545             self.shelf.getAlbum(albumid)
546         except AlbumDoesNotExistError:
547             pass
548         else:
549             assert False
550         try:
551             self.shelf.getImage(imageid)
552         except ImageDoesNotExistError:
553             pass
554         else:
555             assert False
556
557     def test_getAllAttributeNames(self):
558         attrnames = sorted(self.shelf.getAllAttributeNames())
559         assert attrnames == [
560             "cameramake", "cameramodel", "captured", "description",
561             "digitalzoom", "exposurebias", "exposureprogram", "exposuretime",
562             "flash", "fnumber", "focallength", "iso", "orientation", "query",
563             "title"
564             ], attrnames
565
566     def test_getCategory(self):
567         category = self.shelf.getCategory(1)
568         assert category.getId() == 1
569
570     def test_getCategoryByTag(self):
571         category = self.shelf.getCategoryByTag(u"a")
572         assert category.getTag() == u"a"
573
574     def test_negativeCreateCategory(self):
575         try:
576             self.shelf.createCategory(u"a", u"Foo")
577         except CategoryExistsError:
578             pass
579         else:
580             assert False
581
582     def test_deleteCategory(self):
583         category = self.shelf.getCategoryByTag(u"a")
584         self.shelf.deleteCategory(category.getId())
585
586     def test_negativeDeleteCategory(self):
587         try:
588             self.shelf.deleteCategory(12345678)
589         except CategoryDoesNotExistError:
590             pass
591         else:
592             assert False
593
594     def test_getRootCategories(self):
595         categories = sorted(
596             self.shelf.getRootCategories(), key=lambda x: x.getTag())
597         cat_a = self.shelf.getCategoryByTag(u"a")
598         cat_events = self.shelf.getCategoryByTag(u"events")
599         cat_locations = self.shelf.getCategoryByTag(u"locations")
600         cat_people = self.shelf.getCategoryByTag(u"people")
601         assert categories == [cat_a, cat_events, cat_locations, cat_people], \
602                categories
603
604 class TestCategory(TestShelfFixture):
605     def test_categoryMethods(self):
606         cat_a = self.shelf.getCategoryByTag(u"a")
607         cat_b = self.shelf.getCategoryByTag(u"b")
608         cat_c = self.shelf.getCategoryByTag(u"c")
609         cat_d = self.shelf.getCategoryByTag(u"d")
610
611         assert self.shelf.getCategory(cat_a.getId()) == cat_a
612         cat_a.setTag(u"foo")
613         assert self.shelf.getCategoryByTag(u"foo") == cat_a
614
615         assert cat_a.getDescription() == "A"
616         cat_a.setDescription(u"foo")
617         assert cat_a.getDescription() == "foo"
618
619         a_children = sorted(cat_a.getChildren(), key=lambda x: x.getId())
620         assert a_children == [cat_b, cat_c]
621         b_children = list(cat_b.getChildren())
622         assert b_children == [cat_d]
623         d_children = list(cat_d.getChildren())
624         assert d_children == []
625
626         a_parents = list(cat_a.getParents())
627         assert a_parents == []
628         b_parents = list(cat_b.getParents())
629         assert b_parents == [cat_a]
630         d_parents = sorted(cat_d.getParents(), key=lambda x: x.getTag())
631         assert d_parents == [cat_b, cat_c]
632
633         assert not cat_a.isChildOf(cat_a)
634         assert cat_b.isChildOf(cat_a)
635         assert cat_c.isChildOf(cat_a)
636         assert not cat_d.isChildOf(cat_a)
637         assert cat_a.isChildOf(cat_a, recursive=True)
638         assert cat_b.isChildOf(cat_a, recursive=True)
639         assert cat_c.isChildOf(cat_a, recursive=True)
640         assert cat_d.isChildOf(cat_a, recursive=True)
641
642         assert not cat_d.isParentOf(cat_d)
643         assert cat_b.isParentOf(cat_d)
644         assert cat_c.isParentOf(cat_d)
645         assert not cat_a.isParentOf(cat_d)
646         assert cat_d.isParentOf(cat_d, recursive=True)
647         assert cat_b.isParentOf(cat_d, recursive=True)
648         assert cat_c.isParentOf(cat_d, recursive=True)
649         assert cat_a.isParentOf(cat_d, recursive=True)
650
651     def test_negativeCategoryConnectChild(self):
652         cat_a = self.shelf.getCategoryByTag(u"a")
653         cat_b = self.shelf.getCategoryByTag(u"b")
654         try:
655             cat_a.connectChild(cat_b)
656         except CategoriesAlreadyConnectedError:
657             pass
658         else:
659             assert False
660         try:
661             cat_b.connectChild(cat_a)
662         except CategoryLoopError:
663             pass
664         else:
665             assert False
666
667     def test_categoryDisconnectChild(self):
668         cat_a = self.shelf.getCategoryByTag(u"a")
669         cat_b = self.shelf.getCategoryByTag(u"b")
670         cat_a.disconnectChild(cat_b)
671         assert not cat_a.isParentOf(cat_b)
672
673     def test_negativeCategoryDisconnectChild(self):
674         cat_a = self.shelf.getCategoryByTag(u"a")
675         cat_d = self.shelf.getCategoryByTag(u"d")
676         cat_a.disconnectChild(cat_d) # No exception.
677
678 class TestObject(TestShelfFixture):
679     def test_getParents(self):
680         root = self.shelf.getRootAlbum()
681         alpha = self.shelf.getAlbumByTag(u"alpha")
682         beta = self.shelf.getAlbumByTag(u"beta")
683         parents = sorted(beta.getParents(), key=lambda x: x.getTag())
684         assert parents == [alpha, root]
685
686     def test_getAttribute(self):
687         orphans = self.shelf.getAlbumByTag(u"orphans")
688         assert orphans.getAttribute(u"title")
689         assert orphans.getAttribute(u"description")
690         orphans.getAttributeMap() # Just populate the cache.
691         assert not orphans.getAttribute(u"nonexisting")
692         assert u"nonexisting" not in orphans.getAttributeMap()
693
694     def test_getAttributeMap(self):
695         orphans = self.shelf.getAlbumByTag(u"orphans")
696         map = orphans.getAttributeMap()
697         assert "description" in map
698         assert "title" in map
699
700     def test_getAttributeNames(self):
701         orphans = self.shelf.getAlbumByTag(u"orphans")
702         names = sorted(orphans.getAttributeNames())
703         assert names == ["description", "title"]
704
705     def test_setAttribute(self):
706         orphans = self.shelf.getAlbumByTag(u"orphans")
707         orphans.setAttribute(u"foo", u"fie") # New.
708         assert orphans.getAttribute(u"foo") == u"fie"
709         assert u"foo" in orphans.getAttributeMap()
710         assert u"foo" in orphans.getAttributeNames()
711         assert orphans.getAttribute(u"title")
712         orphans.setAttribute(u"title", u"gazonk") # Existing
713         assert orphans.getAttribute(u"title") == u"gazonk"
714         assert u"foo" in orphans.getAttributeMap()
715         assert u"foo" in orphans.getAttributeNames()
716
717     def test_deleteAttribute(self):
718         orphans = self.shelf.getAlbumByTag(u"orphans")
719         orphans.deleteAttribute(u"nonexisting") # No exception.
720         assert orphans.getAttribute(u"title")
721         orphans.deleteAttribute(u"title")
722         orphans.getAttributeMap() # Just populate the cache.
723         assert not orphans.getAttribute(u"title")
724         assert u"title" not in orphans.getAttributeMap()
725
726     def test_addCategory(self):
727         orphans = self.shelf.getAlbumByTag(u"orphans")
728         cat_a = self.shelf.getCategoryByTag(u"a")
729         assert list(orphans.getCategories()) == []
730         orphans.addCategory(cat_a)
731         assert list(orphans.getCategories()) == [cat_a]
732         try:
733             orphans.addCategory(cat_a)
734         except CategoryPresentError:
735             pass
736         else:
737             assert False
738         assert list(orphans.getCategories()) == [cat_a]
739
740     def test_removeCategory(self):
741         orphans = self.shelf.getAlbumByTag(u"orphans")
742         assert list(orphans.getCategories()) == []
743         cat_a = self.shelf.getCategoryByTag(u"a")
744         orphans.addCategory(cat_a)
745         assert list(orphans.getCategories()) == [cat_a]
746         orphans.removeCategory(cat_a)
747         assert list(orphans.getCategories()) == []
748
749     def test_deleteCategoryInvalidatesCategoryCache(self):
750         orphans = self.shelf.getAlbumByTag(u"orphans")
751         assert list(orphans.getCategories()) == []
752         cat_a = self.shelf.getCategoryByTag(u"a")
753         orphans.addCategory(cat_a)
754         assert list(orphans.getCategories()) == [cat_a]
755         self.shelf.deleteCategory(cat_a.getId())
756         assert list(orphans.getCategories()) == []
757
758 class TestAlbum(TestShelfFixture):
759     def test_getType(self):
760         alpha = self.shelf.getAlbumByTag(u"alpha")
761         assert alpha.getType() == AlbumType.Plain
762
763     def test_isMutable(self):
764         alpha = self.shelf.getAlbumByTag(u"alpha")
765         assert alpha.isMutable()
766
767     def test_getTag(self):
768         alpha = self.shelf.getAlbumByTag(u"alpha")
769         assert alpha.getTag() == u"alpha"
770
771     def test_setTag(self):
772         alpha = self.shelf.getAlbumByTag(u"alpha")
773         alpha.setTag(u"alfa")
774         assert alpha.getTag() == u"alfa"
775
776     def test_getAlbumParents(self):
777         root = self.shelf.getRootAlbum()
778         alpha = self.shelf.getAlbumByTag(u"alpha")
779         parents = sorted(alpha.getAlbumParents(), key=lambda x: x.getTag())
780         assert parents == [root]
781
782     def test_isAlbum(self):
783         assert self.shelf.getRootAlbum().isAlbum()
784
785 class TestPlainAlbum(TestShelfFixture):
786     def test_getType(self):
787         alpha = self.shelf.getAlbumByTag(u"alpha")
788         assert alpha.getType() == AlbumType.Plain
789
790     def test_isMutable(self):
791         alpha = self.shelf.getAlbumByTag(u"alpha")
792         assert alpha.isMutable()
793
794     def test_getChildren(self):
795         epsilon = self.shelf.getAlbumByTag(u"epsilon")
796         alpha = self.shelf.getAlbumByTag(u"alpha")
797         beta = self.shelf.getAlbumByTag(u"beta")
798         assert list(epsilon.getChildren()) == []
799         alphaChildren = list(alpha.getChildren())
800         assert list(beta.getChildren()) == [alphaChildren[-1]]
801
802     def test_getAlbumChildren(self):
803         alpha = self.shelf.getAlbumByTag(u"alpha")
804         beta = self.shelf.getAlbumByTag(u"beta")
805         epsilon = self.shelf.getAlbumByTag(u"epsilon")
806         alphaAlbumChildren = list(alpha.getAlbumChildren())
807         assert alphaAlbumChildren == [beta]
808         assert list(epsilon.getAlbumChildren()) == []
809
810     def test_setChildren(self):
811         root = self.shelf.getRootAlbum()
812         beta = self.shelf.getAlbumByTag(u"beta")
813         assert list(beta.getChildren()) != []
814         beta.setChildren([beta, root])
815         assert list(beta.getChildren()) == [beta, root]
816         beta.setChildren([]) # Break the cycle.
817         assert list(beta.getChildren()) == []
818
819 class TestImage(TestShelfFixture):
820     def test_isAlbum(self):
821         imageversion = self.shelf.getImageVersionByLocation(
822             os.path.join(PICDIR, "arlaharen.png"))
823         assert not imageversion.getImage().isAlbum()
824
825     def test_getImageVersions(self):
826         imageversion = self.shelf.getImageVersionByLocation(
827             os.path.join(PICDIR, "arlaharen.png"))
828         image = imageversion.getImage()
829         assert list(image.getImageVersions()) == [imageversion]
830         imageversion2 = self.shelf.getImageVersionByLocation(
831             os.path.join(PICDIR, "Canon_Digital_IXUS.jpg"))
832         imageversion2.setImage(image)
833         imageversions = set(image.getImageVersions())
834         assert set(image.getImageVersions()) == imageversions
835         self.shelf.deleteImageVersion(imageversion.getId())
836         self.shelf.deleteImageVersion(imageversion2.getId())
837         assert list(image.getImageVersions()) == []
838
839     def test_getPrimaryVersion(self):
840         imageversion = self.shelf.getImageVersionByLocation(
841             os.path.join(PICDIR, "arlaharen.png"))
842         image = imageversion.getImage()
843         assert image.getPrimaryVersion() == imageversion
844         imageversion2 = self.shelf.getImageVersionByLocation(
845             os.path.join(PICDIR, "Canon_Digital_IXUS.jpg"))
846         imageversion2.setImage(image)
847         assert image.getPrimaryVersion() == imageversion
848         imageversion2.makePrimary()
849         assert image.getPrimaryVersion() == imageversion2
850
851         newImage = self.shelf.createImage()
852         lastImageVersion = list(image.getImageVersions())[-1]
853         lastImageVersion.setImage(newImage)
854         assert image.getPrimaryVersion() != lastImageVersion
855         assert newImage.getPrimaryVersion() == lastImageVersion
856         lastImageVersion.setImage(image)
857
858         self.shelf.deleteImageVersion(imageversion2.getId())
859         assert image.getPrimaryVersion() == imageversion
860         self.shelf.deleteImageVersion(imageversion.getId())
861         assert image.getPrimaryVersion() == None
862
863 class TestImageVersion(TestShelfFixture):
864     def test_getType(self):
865         imageversion = self.shelf.getImageVersionByLocation(
866             os.path.join(PICDIR, "arlaharen.png"))
867         assert imageversion.getType() == ImageVersionType.Original
868         imageversion.setType(ImageVersionType.Important)
869         assert imageversion.getType() == ImageVersionType.Important
870         imageversion.setType(ImageVersionType.Other)
871         assert imageversion.getType() == ImageVersionType.Other
872         imageversion.setType(ImageVersionType.Original)
873         assert imageversion.getType() == ImageVersionType.Original
874
875     # ImageVersion.makePrimary tested in TestImage.test_getPrimaryVersion.
876     # ImageVersion.setImage tested in TestImage.test_getPrimaryVersion.
877     # ImageVersion.setType tested in TestImageVersion.test_getType.
878     # ImageVersion.setComment tested in TestImageVersion.test_getComment.
879
880     def test_getComment(self):
881         imageversion = self.shelf.getImageVersionByLocation(
882             os.path.join(PICDIR, "arlaharen.png"))
883         assert imageversion.getComment() == ""
884         imageversion.setComment(u"a comment")
885         assert imageversion.getComment() == u"a comment"
886
887     def test_getHash(self):
888         imageversion = self.shelf.getImageVersionByLocation(
889             os.path.join(PICDIR, "arlaharen.png"))
890         assert imageversion.getHash() == "39a1266d2689f53d48b09a5e0ca0af1f"
891
892     def test_getLocation(self):
893         location = os.path.join(PICDIR, "arlaharen.png")
894         imageversion = self.shelf.getImageVersionByLocation(location)
895         assert imageversion.getLocation() == os.path.realpath(location)
896
897     def test_getModificationTime(self):
898         imageversion = self.shelf.getImageVersionByLocation(
899             os.path.join(PICDIR, "arlaharen.png"))
900         t = imageversion.getModificationTime()
901         assert isinstance(t, int)
902         assert t > 0
903
904     def test_getSize(self):
905         imageversion = self.shelf.getImageVersionByLocation(
906             os.path.join(PICDIR, "arlaharen.png"))
907         assert imageversion.getSize() == (304, 540)
908
909     def test_contentChanged(self):
910         path = os.path.join(PICDIR, "arlaharen.png")
911         imageversion = self.shelf.getImageVersionByLocation(path)
912         self.shelf.deleteImageVersion(imageversion.getId())
913         newpath = u"tmp.png"
914         try:
915             shutil.copy2(path, newpath)
916             newimage = self.shelf.createImage()
917             newimageversion = self.shelf.createImageVersion(
918                 newimage, newpath, ImageVersionType.Original)
919             oldmtime = imageversion.getModificationTime()
920             f = open(newpath, "a")
921             f.write("foo")
922             f.close()
923             newimageversion.contentChanged()
924             assert newimageversion.getHash() == "b27312d9739c0edfd115f824be244b75"
925             assert newimageversion.getModificationTime() > oldmtime
926         finally:
927             try:
928                 os.unlink(newpath)
929             except OSError:
930                 pass
931
932     def test_locationChanged(self):
933         location = os.path.join(PICDIR, "arlaharen.png")
934         imageversion = self.shelf.getImageVersionByLocation(location)
935         imageversion.locationChanged(u"/foo/../bar")
936         assert imageversion.getLocation() == "/bar"
937
938     def test_importExifTags(self):
939         imageversion = self.shelf.getImageVersionByLocation(
940             os.path.join(PICDIR, "arlaharen.png"))
941         imageversion.importExifTags(True) # TODO: Test more.
942         imageversion.importExifTags(False) # TODO: Test more.
943
944 class TestOrphansAlbum(TestShelfFixture):
945     def test_getType(self):
946         orphans = self.shelf.getAlbumByTag(u"orphans")
947         assert orphans.getType() == AlbumType.Orphans
948
949     def test_isMutable(self):
950         orphans = self.shelf.getAlbumByTag(u"orphans")
951         assert not orphans.isMutable()
952
953     def test_getChildren(self):
954         orphans = self.shelf.getAlbumByTag(u"orphans")
955         assert len(list(orphans.getChildren())) == 2
956
957     def test_getAlbumChildren(self):
958         orphans = self.shelf.getAlbumByTag(u"orphans")
959         epsilon = self.shelf.getAlbumByTag(u"epsilon")
960         assert list(orphans.getAlbumChildren()) == [epsilon]
961
962     def test_setChildren(self):
963         orphans = self.shelf.getAlbumByTag(u"orphans")
964         try:
965             orphans.setChildren([])
966         except UnsettableChildrenError:
967             pass
968         else:
969             assert False
970
971     def test_isAlbum(self):
972         assert self.shelf.getAlbumByTag(u"orphans").isAlbum()
973
974 class TestSearchAlbum(TestShelfFixture):
975     def test_getType(self):
976         zeta = self.shelf.getAlbumByTag(u"zeta")
977         assert zeta.getType() == AlbumType.Search
978
979     def test_isMutable(self):
980         zeta = self.shelf.getAlbumByTag(u"zeta")
981         assert not zeta.isMutable()
982
983     def test_getChildren(self):
984         alpha = self.shelf.getAlbumByTag(u"alpha")
985         image1, image2 = list(alpha.getChildren())[0:2]
986         cat_a = self.shelf.getCategoryByTag(u"a")
987         cat_b = self.shelf.getCategoryByTag(u"b")
988         image1.addCategory(cat_a)
989         image2.addCategory(cat_b)
990         zeta = self.shelf.getAlbumByTag(u"zeta")
991         assert zeta
992         zeta.setAttribute(u"query", u"b")
993         children = zeta.getChildren()
994         assert list(children) == [image2]
995
996     def test_getAlbumChildren(self):
997         alpha = self.shelf.getAlbumByTag(u"alpha")
998         image1, image2 = list(alpha.getChildren())[0:2]
999         cat_a = self.shelf.getCategoryByTag(u"a")
1000         cat_b = self.shelf.getCategoryByTag(u"b")
1001         image1.addCategory(cat_a)
1002         image2.addCategory(cat_b)
1003         zeta = self.shelf.getAlbumByTag(u"zeta")
1004         assert zeta
1005         zeta.setAttribute(u"query", u"b")
1006         children = zeta.getAlbumChildren()
1007         l = list(children)
1008         assert list(children) == []
1009
1010     def test_setChildren(self):
1011         zeta = self.shelf.getAlbumByTag(u"zeta")
1012         try:
1013             zeta.setChildren([])
1014         except UnsettableChildrenError:
1015             pass
1016         else:
1017             assert False
1018
1019     def test_isAlbum(self):
1020         assert self.shelf.getAlbumByTag(u"zeta").isAlbum()
1021
1022 ######################################################################
1023
1024 removeTmpDb()
1025 if __name__ == "__main__":
1026     unittest.main()