Added tests for Python API library

This commit is contained in:
sneakypete81 2012-09-04 22:08:48 +01:00
parent 854c4fd605
commit ba5be69bec
10 changed files with 507 additions and 1 deletions

3
.gitignore vendored
View file

@ -1,5 +1,6 @@
*~
*.pyc
build
dist
*.egg-info
tests/tokens.py

61
tests/README.markdown Normal file
View file

@ -0,0 +1,61 @@
Tests for the Open Photo API / Python Library
=======================
#### OpenPhoto, a photo service for the masses
----------------------------------------
<a name="requirements"></a>
### Requirements
A computer, Python 2.7 and an empty OpenPhoto instance.
---------------------------------------
<a name="setup"></a>
### 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!!!**
---------------------------------------
<a name="running"></a>
### 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
---------------------------------------
<a name="test_details"></a>
### 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

0
tests/__init__.py Normal file
View file

88
tests/test_albums.py Normal file
View file

@ -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)

128
tests/test_base.py Normal file
View file

@ -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 = \"<test_url>\"\n"
" consumer_key = \"<test_consumer_key>\"\n"
" consumer_secret = \"<test_consumer_secret>\"\n"
" token = \"<test_token>\"\n"
" token_secret = \"<test_token_secret>\"\n"
" host = \"<hostname>\"\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()

BIN
tests/test_photo1.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
tests/test_photo2.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 854 B

BIN
tests/test_photo3.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 579 B

157
tests/test_photos.py Normal file
View file

@ -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("<form", html.lower())
# And the Photo object directly
html = self.photos[0].edit()
self.assertIn("<form", html.lower())
def test_upload_duplicate(self):
""" Ensure that duplicate photos are rejected """
# Attempt to upload a duplicate
with self.assertRaises(openphoto.OpenPhotoDuplicateError):
self.client.photo.upload_encoded("tests/test_photo1.jpg",
title=self.TEST_TITLE)
# Check there are still three photos
self.photos = self.client.photos.list()
self.assertEqual(len(self.photos), 3)
def test_update(self):
""" Update a photo by editing the title """
title = u"\xfcmlaut" # umlauted umlaut
# Get a photo and check that it doesn't have the magic title
photo = self.photos[0]
self.assertNotEqual(photo.title, title)
# Add the title to a photo using the OpenPhoto class
ret_val = self.client.photo.update(photo, title=title)
# Check that it's there
self.photos = self.client.photos.list()
photo = self.photos[0]
self.assertEqual(photo.title, title)
# Check that the return value was correct
self.assertEqual(ret_val.pathOriginal, photo.pathOriginal)
# Revert the title using the Photo object directly
photo.update(title=self.TEST_TITLE)
# Check that it's gone back
self.photos = self.client.photos.list()
self.assertEqual(self.photos[0].title, self.TEST_TITLE)
def test_update_multiple(self):
""" Update multiple photos by adding tags """
tag_id = "update_photo_tag"
# Get a couple of photos
photos = self.photos[:2]
# Add the tag using a list of photo objects
self.client.photos.update(photos, tagsAdd=tag_id)
# Check that it's there
for photo in self.client.photos.list()[:2]:
self.assertIn(tag_id, photo.tags)
# Remove the tags using a list of photo ids
self.client.photos.update([photo.id for photo in photos],
tagsRemove=tag_id)
def test_view(self):
""" Test photo view """
# Check that our magic sizes aren't present
photo = self.photos[0]
self.assertFalse(hasattr(photo, "path9x9"))
self.assertFalse(hasattr(photo, "path19x19"))
# View at a particular size using the OpenPhoto class
photo = self.client.photo.view(photo, returnSizes="9x9")
self.assertTrue(hasattr(photo, "path9x9"))
# View at a particular size using the Photo object directly
photo.view(returnSizes="19x19")
self.assertTrue(hasattr(photo, "path19x19"))
def test_next_previous(self):
""" Test the next/previous links of the middle photo """
next_prev = self.client.photo.next_previous(self.photos[1])
self.assertEqual(next_prev["previous"].id, self.photos[0].id)
self.assertEqual(next_prev["next"].id, self.photos[2].id)
# Do the same using the Photo object directly
next_prev = self.photos[1].next_previous()
self.assertEqual(next_prev["previous"].id, self.photos[0].id)
self.assertEqual(next_prev["next"].id, self.photos[2].id)
def test_replace(self):
""" If photo.replace gets implemented, write a test! """
with self.assertRaises(openphoto.NotImplementedError):
self.client.photo.replace(None, None)
def test_replace_encoded(self):
""" If photo.replace_encoded gets implemented, write a test! """
with self.assertRaises(openphoto.NotImplementedError):
self.client.photo.replace_encoded(None, None)
def test_upload(self):
""" If photo.upload gets implemented, write a test! """
with self.assertRaises(openphoto.NotImplementedError):
self.client.photo.upload(None)
def test_dynamic_url(self):
""" If photo.dynamic_url gets implemented, write a test! """
with self.assertRaises(openphoto.NotImplementedError):
self.client.photo.dynamic_url(None)
def test_transform(self):
""" If photo.transform gets implemented, write a test! """
with self.assertRaises(openphoto.NotImplementedError):
self.client.photo.transform(None)

71
tests/test_tags.py Normal file
View file

@ -0,0 +1,71 @@
import unittest
import openphoto
import test_base
class TestTags(test_base.TestBase):
@unittest.expectedFailure # Tag create fails - Issue #927
# NOTE: the below has not been tested/debugged, since it fails at the first step
def test_create_delete(self, tag_name="create_tag"):
""" Create a tag then delete it """
# Create a tag
tag = self.client.tag.create(tag_name)
# Check the return value
self.assertEqual(tag.id, tag_name)
# Check that the tag now exists
self.assertIn(tag_name, self.client.tags.list())
# Delete the tag
self.client.tag.delete(tag_name)
# Check that the tag is now gone
self.assertNotIn(tag_name, self.client.tags.list())
# Create and delete using the Tag object directly
tag = self.client.tag.create(tag_name)
tag.delete()
# Check that the tag is now gone
self.assertNotIn(tag_name, self.client.tags.list())
@unittest.expectedFailure # Tag update fails - Issue #927
# NOTE: the below has not been tested/debugged, since it fails at the first step
def test_update(self):
""" Test that a tag can be updated """
# Update the tag using the OpenPhoto class, passing in the tag object
owner = "test1@openphoto.me"
ret_val = self.client.tag.update(self.tags[0], owner=owner)
# Check that the tag is updated
self.tags = self.client.tags.list()
self.assertEqual(self.tags[0].owner, owner)
self.assertEqual(ret_val.owner, owner)
# Update the tag using the OpenPhoto class, passing in the tag id
owner = "test2@openphoto.me"
ret_val = self.client.tag.update(self.TEST_TAG, owner=owner)
# Check that the tag is updated
self.tags = self.client.tags.list()
self.assertEqual(self.tags[0].owner, owner)
self.assertEqual(ret_val.owner, owner)
# Update the tag using the Tag object directly
owner = "test3@openphoto.me"
ret_val = self.tags[0].update(owner=owner)
# Check that the tag is updated
self.tags = self.client.tags.list()
self.assertEqual(self.tags[0].owner, owner)
self.assertEqual(ret_val.owner, owner)
@unittest.expectedFailure # Tag create fails - Issue #927
# NOTE: the below has not been tested/debugged, since it fails at the first step
def test_tag_with_spaces(self):
""" Run test_create_delete using a tag containing spaces """
self.test_create_delete("tag with spaces")
# We mustn't run this test until Issue #919 is resolved,
# since it creates an undeletable tag
@unittest.skip("Tags with double-slashes cannot be deleted - Issue #919")
def test_tag_with_double_slashes(self):
""" Run test_create_delete using a tag containing slashes """
self.test_create_delete("tag/with//slashes")