diff --git a/.gitignore b/.gitignore index 8f8a3ff..1dab14d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ +*~ *.pyc build dist *.egg-info - +tests/tokens.py diff --git a/tests/README.markdown b/tests/README.markdown new file mode 100644 index 0000000..00a21df --- /dev/null +++ b/tests/README.markdown @@ -0,0 +1,61 @@ +Tests for the Open Photo API / Python Library +======================= +#### OpenPhoto, a photo service for the masses + +---------------------------------------- + +### Requirements +A computer, Python 2.7 and an empty OpenPhoto instance. + +--------------------------------------- + +### Setting up + +Create a tests/tokens.py file containing the following: + + # tests/token.py + consumer_key = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + consumer_secret = "xxxxxxxxxx" + token = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + token_secret = "xxxxxxxxxx" + host = "your_hostname" + +Make sure this is an empty test server, **not a production OpenPhoto server!!!** + +--------------------------------------- + +### Running the tests + + cd /path/to/openphoto-python + python -m unittest discover -c + +The "-c" lets you stop the tests gracefully with \[CTRL\]-c. + +The easiest way to run a subset of the tests is with nose: + + cd /path/to/openphoto-python + nosetests -v -s tests/test_albums.py:TestAlbums.test_view + +--------------------------------------- + +### Test Details + +These tests are intended to verify the Python library. They don't provide comprehensive testing of the OpenPhoto API, there are PHP unit tests for that. + +Each test class is run as follows: + +**SetUpClass:** + +Check that the server is empty + +**SetUp:** + +Ensure there are: + + * Three test photos + * A single test tag applied to each + * A single album containing all three photos + +**TearDownClass:** + +Remove all photos, tags and albums diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_albums.py b/tests/test_albums.py new file mode 100644 index 0000000..0c5fe11 --- /dev/null +++ b/tests/test_albums.py @@ -0,0 +1,88 @@ +import unittest +import openphoto +import test_base + +class TestAlbums(test_base.TestBase): + + def test_create_delete(self): + """ Create an album then delete it """ + album_name = "create_delete_album" + album = self.client.album.create(album_name, visible=True) + + # Check the return value + self.assertEqual(album.name, album_name) + # Check that the album now exists + self.assertIn(album_name, [a.name for a in self.client.albums.list()]) + + # Delete the album + self.client.album.delete(album.id) + # Check that the album is now gone + self.assertNotIn(album_name, [a.name for a in self.client.albums.list()]) + + # Create it again, and delete it using the Album object + album = self.client.album.create(album_name, visible=True) + album.delete() + # Check that the album is now gone + self.assertNotIn(album_name, [a.name for a in self.client.albums.list()]) + + def test_update(self): + """ Test that an album can be updated """ + # Update the album using the OpenPhoto class, passing in the album object + new_name = "New Name" + self.client.album.update(self.albums[0], name=new_name) + + # Check that the album is updated + self.albums = self.client.albums.list() + self.assertEqual(self.albums[0].name, new_name) + + # Update the album using the OpenPhoto class, passing in the album id + new_name = "Another New Name" + self.client.album.update(self.albums[0].id, name=new_name) + + # Check that the album is updated + self.albums = self.client.albums.list() + self.assertEqual(self.albums[0].name, new_name) + + # Update the album using the Album object directly + self.albums[0].update(name=self.TEST_ALBUM) + + # Check that the album is updated + self.albums = self.client.albums.list() + self.assertEqual(self.albums[0].name, self.TEST_ALBUM) + + def test_view(self): + """ Test the album view """ + album = self.albums[0] + self.assertFalse(hasattr(album, "photos")) + + # Get the photos in the album using the Album object directly + album.view() + # Make sure all photos are in the album + for photo in self.photos: + self.assertIn(photo.id, [p.id for p in album.photos]) + + @unittest.expectedFailure # Private albums are not visible - issue #929 + def test_private(self): + """ Test that private albums can be created, and are visible """ + # Create and check that the album now exists + album = self.client.album.create(album_name, visible=False) + self.assertIn(album_name, self.client.albums.list()) + + # Delete and check that the album is now gone + album.delete() + self.assertNotIn(album_name, self.client.albums.list()) + + def test_form(self): + """ If album.form gets implemented, write a test! """ + with self.assertRaises(openphoto.NotImplementedError): + self.client.album.form(None) + + def test_add_photos(self): + """ If album.add_photos gets implemented, write a test! """ + with self.assertRaises(openphoto.NotImplementedError): + self.client.album.add_photos(None, None) + + def test_remove_photos(self): + """ If album.remove_photos gets implemented, write a test! """ + with self.assertRaises(openphoto.NotImplementedError): + self.client.album.remove_photos(None, None) diff --git a/tests/test_base.py b/tests/test_base.py new file mode 100644 index 0000000..8e9b980 --- /dev/null +++ b/tests/test_base.py @@ -0,0 +1,128 @@ +import unittest +import openphoto + +try: + import tokens +except ImportError: + print ("********************************************************************\n" + "You need to create a 'tokens.py' file containing the following:\n\n" + " host = \"\"\n" + " consumer_key = \"\"\n" + " consumer_secret = \"\"\n" + " token = \"\"\n" + " token_secret = \"\"\n" + " host = \"\"\n\n" + "WARNING: Don't use a production OpenPhoto instance for this!\n" + "********************************************************************\n") + raise + +class TestBase(unittest.TestCase): + TEST_TITLE = "Test Image - delete me!" + TEST_TAG = "test_tag" + TEST_ALBUM = "test_album" + MAXIMUM_TEST_PHOTOS = 4 # Never have more the 4 photos on the test server + + def __init__(self, *args, **kwds): + unittest.TestCase.__init__(self, *args, **kwds) + self.photos = [] + + @classmethod + def setUpClass(cls): + """ Ensure there is nothing on the server before running any tests """ + cls.client = openphoto.OpenPhoto(tokens.host, + tokens.consumer_key, tokens.consumer_secret, + tokens.token, tokens.token_secret) + + if cls.client.photos.list() != []: + raise ValueError("The test server (%s) contains photos. " + "Please delete them before running the tests" + % tokens.host) + + if cls.client.tags.list() != []: + raise ValueError("The test server (%s) contains tags. " + "Please delete them before running the tests" + % tokens.host) + + if cls.client.albums.list() != []: + raise ValueError("The test server (%s) contains albums. " + "Please delete them before running the tests" + % tokens.host) + + @classmethod + def tearDownClass(cls): + """ Once all tests have finished, delete all photos, tags and albums""" + cls._delete_all() + + def setUp(self): + """ + Ensure the three test photos are present before each test. + Give them each a tag. + Put them into an album. + """ + self.photos = self.client.photos.list() + if len(self.photos) != 3: + print "[Regenerating Photos]" + if len(self.photos) > 0: + self._delete_all() + self._create_test_photos() + self.photos = self.client.photos.list() + + self.tags = self.client.tags.list() + if (len(self.tags) != 1 or + self.tags[0].id != self.TEST_TAG or + self.tags[0].count != "3"): + print "[Regenerating Tags]" + self._delete_all() + self._create_test_photos() + self.photos = self.client.photos.list() + self.tags = self.client.tags.list() + if len(self.tags) != 1: + print "Tags: %s" % self.tags + raise Exception("Tag creation failed") + + self.albums = self.client.albums.list() + if (len(self.albums) != 1 or + self.albums[0].name != self.TEST_ALBUM or + self.albums[0].count != "3"): + print "[Regenerating Albums]" + self._delete_all() + self._create_test_photos() + self.photos = self.client.photos.list() + self.tags = self.client.tags.list() + self.albums = self.client.albums.list() + if len(self.albums) != 1: + print "Albums: %s" % self.albums + raise Exception("Album creation failed") + + @classmethod + def _create_test_photos(cls): + """ Upload three test photos """ + album = cls.client.album.create(cls.TEST_ALBUM, visible=True) + photos = [ + cls.client.photo.upload_encoded("tests/test_photo1.jpg", + title=cls.TEST_TITLE, + tags=cls.TEST_TAG), + cls.client.photo.upload_encoded("tests/test_photo2.jpg", + title=cls.TEST_TITLE, + tags=cls.TEST_TAG), + cls.client.photo.upload_encoded("tests/test_photo3.jpg", + title=cls.TEST_TITLE, + tags=cls.TEST_TAG), + ] + # Remove the auto-generated month/year tags + tags_to_remove = [p for p in photos[0].tags if p != cls.TEST_TAG] + for photo in photos: + photo.update(tagsRemove=tags_to_remove, albums=album.id) + + @classmethod + def _delete_all(cls): + photos = cls.client.photos.list() + if len(photos) > cls.MAXIMUM_TEST_PHOTOS: + raise ValueError("There too many photos on the test server - must always be less than %d." + % cls.MAXIMUM_TEST_PHOTOS) + for photo in photos: + photo.delete() + for tag in cls.client.tags.list(): + tag.delete() + for album in cls.client.albums.list(): + album.delete() diff --git a/tests/test_photo1.jpg b/tests/test_photo1.jpg new file mode 100644 index 0000000..799c86b Binary files /dev/null and b/tests/test_photo1.jpg differ diff --git a/tests/test_photo2.jpg b/tests/test_photo2.jpg new file mode 100644 index 0000000..103f87f Binary files /dev/null and b/tests/test_photo2.jpg differ diff --git a/tests/test_photo3.jpg b/tests/test_photo3.jpg new file mode 100644 index 0000000..e3f708e Binary files /dev/null and b/tests/test_photo3.jpg differ diff --git a/tests/test_photos.py b/tests/test_photos.py new file mode 100644 index 0000000..68e9cac --- /dev/null +++ b/tests/test_photos.py @@ -0,0 +1,157 @@ +import unittest +import openphoto +import test_base + +class TestPhotos(test_base.TestBase): + def test_delete_upload(self): + """ Test photo deletion and upload """ + # Delete one photo using the OpenPhoto class, passing in the id + self.client.photo.delete(self.photos[0].id) + # Delete one photo using the OpenPhoto class, passing in the object + self.client.photo.delete(self.photos[1]) + # And another using the Photo object directly + self.photos[2].delete() + + # Check that they're gone + self.assertEqual(self.client.photos.list(), []) + + # Re-upload the photos + ret_val = self.client.photo.upload_encoded("tests/test_photo1.jpg", + title=self.TEST_TITLE) + self.client.photo.upload_encoded("tests/test_photo2.jpg", + title=self.TEST_TITLE) + self.client.photo.upload_encoded("tests/test_photo3.jpg", + title=self.TEST_TITLE) + + # Check there are now three photos + self.photos = self.client.photos.list() + self.assertEqual(len(self.photos), 3) + + # Check that the upload return value was correct + pathOriginals = [photo.pathOriginal for photo in self.photos] + self.assertIn(ret_val.pathOriginal, pathOriginals) + + # Delete all photos in one go + self.client.photos.delete(self.photos) + + # Check they're gone + self.photos = self.client.photos.list() + self.assertEqual(len(self.photos), 0) + + # Regenerate the original test photos + self._delete_all() + self._create_test_photos() + + def test_edit(self): + """ Check that the edit request returns an HTML form """ + # Test using the OpenPhoto class + html = self.client.photo.edit(self.photos[0]) + self.assertIn("