Merge branch 'unit_tests' into development

This commit is contained in:
sneakypete81 2013-06-30 17:10:07 +01:00
commit 64d762d709
29 changed files with 1491 additions and 158 deletions

View file

@ -4,15 +4,7 @@ install:
- pip install tox --use-mirrors
- .travis/install_pylint
env:
global:
- secure: "CQNSUBhVyedtCbioKkoueZsBFw0H+EnrPPNQIO+v3ieniPhFQbCoaCOA6rZG\n1MH1oIz5GSB1hv48jLCSSDppYOX1nKlLUFAepm9h7HSv2MaBzENKcp3ipBLP\nn8QEhVCkeWVnTkRB+IWrQHiW+8vHZ1iaERjlX9cMav7rBzzvK9g="
- secure: "e5xYBGGzn6x06hmofDJ+tuS8iAVPuFNGqizR8cA6+2W4rSQEbh7NcKKeAvB5\n8qlmBonupo0wttkewh2hpnxvaXV7uS4C0Qt/h087Bu4cPkJMENWq++CrDo6G\nwjkAu6x6YDkzuMuxa5BTWU9hAQVX1jq+cjYOmORhw/v5FFukN44="
- secure: "aU95NQmiY2ytyGRywEQvblN1YinIHpe/L9jnYlxazhfdHr+WXZd5aXC4Ze/U\nqlsHR+PGjycPHUCykJ/W5KU68tAX9r3PQgaQlfWd1cT89paY4givtoHiTz+f\nGu2I3BexskJ58NcUEDp6MEJqEuIXiQYUpoQ+6rNzvpe427xt6R0="
- secure: "ilNFM41mePkXMpvK/6T7s3vsQCN36XoiHnR7Fxrnpur9sXOfwB8A1Kw7CpbM\n5rxc2QNj7SPrT2K49QE8fUKHIl88a2MqCf+ujy9mG7WgKdxYazIxrhyHCNKO\nZ47r38kijW92GnSX4KTDeORfouZgR21BpDTfoCvspiWzWzG/fYE="
- secure: "YdUPDO7sTUTG2EwUlrxwOWKhlGXJiIK+RBWDspqvM8UQV4CQjzIsRX8urUIN\nSpSjJOfbIw25S+AsLpEBye8OJMncm/16Xp7PL5tlkNmRC12mPVG8f+wpOkrW\nt8v+2Cv/prYDn0tjoqnV1f5Nv5cEW6kAkG19UQ4QBgQzirtrs9Y="
script: .travis/run_travis
script: tox
after_script:
# Run Pylint

View file

@ -1,19 +0,0 @@
#!/bin/bash
# Create a config file containing the test host's secrets
CONFIG_DIR=~/.config/openphoto
CONFIG_FILE=$CONFIG_DIR/test
mkdir ~/.config
mkdir $CONFIG_DIR
echo "host = $OP_HOST" >> $CONFIG_FILE
echo "consumerKey = $OP_CONSUMER_KEY" >> $CONFIG_FILE
echo "consumerSecret = $OP_CONSUMER_SECRET" >> $CONFIG_FILE
echo "token = $OP_TOKEN" >> $CONFIG_FILE
echo "tokenSecret = $OP_TOKEN_SECRET" >> $CONFIG_FILE
# Run the tests
tox -e py27

View file

@ -81,6 +81,8 @@ def main(args=sys.argv[1:]):
params, files = extract_files(params)
result = client.post(options.endpoint, process_response=False,
files=files, **params)
for f in files:
files[f].close()
if options.verbose:
print("==========\nMethod: %s\nHost: %s\nEndpoint: %s" %

View file

@ -14,12 +14,8 @@ from openphoto.config import Config
if sys.version < '3':
TEXT_TYPE = unicode
# requests_oauth needs to decode to ascii for Python2
OAUTH_DECODING = "utf-8"
else:
TEXT_TYPE = str
# requests_oauth needs to use (unicode) strings for Python3
OAUTH_DECODING = None
DUPLICATE_RESPONSE = {"code": 409,
"message": "This photo already exists"}
@ -76,8 +72,7 @@ class OpenPhotoHttp:
auth = requests_oauthlib.OAuth1(self.config.consumer_key,
self.config.consumer_secret,
self.config.token,
self.config.token_secret,
decoding=OAUTH_DECODING)
self.config.token_secret)
else:
auth = None
@ -122,8 +117,7 @@ class OpenPhotoHttp:
auth = requests_oauthlib.OAuth1(self.config.consumer_key,
self.config.consumer_secret,
self.config.token,
self.config.token_secret,
decoding=OAUTH_DECODING)
self.config.token_secret)
with requests.Session() as session:
if files:
# Need to pass parameters as URL query, so they get OAuth signed

View file

@ -1,10 +1,9 @@
#!/bin/bash
#
# Simple script to run all tests with multiple test servers
# across all supported Python versions
# Simple script to run all functional tests with multiple test servers
#
# Default test server running latest self-hosted site
# Test server running latest self-hosted site
tput setaf 3
echo
@ -12,7 +11,7 @@ echo "Testing latest self-hosted site..."
tput sgr0
export OPENPHOTO_TEST_CONFIG=test
unset OPENPHOTO_TEST_SERVER_API
tox $@
python -m unittest discover --catch tests/functional
# Test server running APIv1 OpenPhoto instance
tput setaf 3
@ -21,7 +20,7 @@ echo "Testing APIv1 self-hosted site..."
tput sgr0
export OPENPHOTO_TEST_CONFIG=test-apiv1
export OPENPHOTO_TEST_SERVER_API=1
tox $@
python -m unittest discover --catch tests/functional
# Test account on hosted trovebox.com site
tput setaf 3
@ -30,5 +29,5 @@ echo "Testing latest hosted site..."
tput sgr0
export OPENPHOTO_TEST_CONFIG=test-hosted
unset OPENPHOTO_TEST_SERVER_API
tox $@
python -m unittest discover --catch tests/functional

View file

@ -1,101 +1,34 @@
Tests for the Open Photo API / Python Library
OpenPhoto/Trovebox Python Testing
=======================
#### OpenPhoto, a photo service for the masses
###Unit Tests
The unit tests mock out all HTTP requests, and verify that the various
components of the library are operating correctly.
They run very quickly and don't require any external test hosts.
<a name="requirements"></a>
#### Requirements
* mock >= 1.0.0
* httpretty >= 0.6.1
* tox (optional)
#### Running the Unit Tests
python -m unittest discover tests/unit
To run the unit tests against all supported Python versions, use ```tox```:
tox
----------------------------------------
<a name="requirements"></a>
### Requirements
A computer, Python and an empty OpenPhoto test host.
---------------------------------------
<a name="setup"></a>
### Setting up
###Functional Tests
Create a ``~/.config/openphoto/test`` config file containing the following:
The functional tests check that the openphoto-python library interoperates
correctly with a real OpenPhoto/Trovebox server.
# ~/.config/openphoto/test
host = your.host.com
consumerKey = your_consumer_key
consumerSecret = your_consumer_secret
token = your_access_token
tokenSecret = your_access_token_secret
They are slow to run and rely on a stable HTTP connection to a test server.
Make sure this is an empty test server, **not a production OpenPhoto server!!!**
You can specify an alternate test config file with the following environment variable:
export OPENPHOTO_TEST_CONFIG=test2
---------------------------------------
<a name="running"></a>
### Running the tests
The following instructions are for Python 2.7. You can adapt them for earlier
Python versions using the ``unittest2`` package.
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 the ``nose`` package:
cd /path/to/openphoto-python
nosetests -v -s --nologcapture tests/test_albums.py:TestAlbums.test_view
All HTTP requests and responses are recorded in the file ``tests.log``.
You can enable more verbose output to stdout with the following environment variable:
export OPENPHOTO_TEST_DEBUG=1
---------------------------------------
<a name="test_details"></a>
### Test Details
These tests are intended to verify the openphoto-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
### Testing old servers
By default, all currently supported API versions will be tested.
It's useful to test servers that only support older API versions.
To restrict the testing to a specific maximum API version, use the
``OPENPHOTO_TEST_SERVER_API`` environment variable.
For example, to restrict testing to APIv1 and APIv2:
export OPENPHOTO_TEST_SERVER_API=2
<a name="full_regression"></a>
### Full Regression Test
The ``run_tests`` script uses the ``tox`` package to run a full regression across:
* Multiple Python versions
* All supported API versions
To use it, you must set up multiple OpenPhoto instances and create the following
config files containing your credentials:
test : Latest self-hosted site
test-apiv1 : APIv1 self-hosted site
test-hosted : Credentials for test account on trovebox.com
For full details, see the [functional test README file](functional/README.markdown).

View file

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 910 B

After

Width:  |  Height:  |  Size: 910 B

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 635 B

After

Width:  |  Height:  |  Size: 635 B

Before After
Before After

View file

@ -0,0 +1,104 @@
Functional Testing
=======================
These functional tests check that the openphoto-python library interoperates
correctly with a real OpenPhoto/Trovebox server.
They are slow to run, and require a stable HTTP connection to a test server.
----------------------------------------
<a name="requirements"></a>
### Requirements
A computer, Python and an empty OpenPhoto/Trovebox test host.
---------------------------------------
<a name="setup"></a>
### Setting up
Create a ``~/.config/openphoto/test`` config file containing the following:
# ~/.config/openphoto/test
host = your.host.com
consumerKey = your_consumer_key
consumerSecret = your_consumer_secret
token = your_access_token
tokenSecret = your_access_token_secret
Make sure this is an empty test server, **not a production OpenPhoto server!!!**
You can specify an alternate test config file with the following environment variable:
export OPENPHOTO_TEST_CONFIG=test2
---------------------------------------
<a name="running"></a>
### Running the tests
The following instructions are for Python 2.7. You can adapt them for earlier
Python versions using the ``unittest2`` package.
cd /path/to/openphoto-python
python -m unittest discover -c tests/functional
The "-c" lets you stop the tests gracefully with \[CTRL\]-c.
The easiest way to run a subset of the tests is with the ``nose`` package:
cd /path/to/openphoto-python
nosetests -v -s --nologcapture tests/functional/test_albums.py:TestAlbums.test_view
All HTTP requests and responses are recorded in the file ``tests.log``.
You can enable more verbose output to stdout with the following environment variable:
export OPENPHOTO_TEST_DEBUG=1
---------------------------------------
<a name="test_details"></a>
### Test Details
These tests are intended to verify the openphoto-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
### Testing old servers
By default, all currently supported API versions will be tested.
It's useful to test servers that only support older API versions.
To restrict the testing to a specific maximum API version, use the
``OPENPHOTO_TEST_SERVER_API`` environment variable.
For example, to restrict testing to APIv1 and APIv2:
export OPENPHOTO_TEST_SERVER_API=2
<a name="full_regression"></a>
### Full Regression Test
The ``run_functional_tests`` script runs all functional tests against
all supported API versions.
To use it, you must set up multiple OpenPhoto instances and create the following
config files containing your credentials:
test : Latest self-hosted site
test-apiv1 : APIv1 self-hosted site
test-hosted : Credentials for test account on trovebox.com

View file

@ -1,4 +1,4 @@
from tests import test_albums, test_photos, test_tags
from tests.functional import test_albums, test_photos, test_tags
class TestAlbumsV1(test_albums.TestAlbums):
api_version = 1

View file

@ -2,7 +2,7 @@ try:
import unittest2 as unittest
except ImportError:
import unittest
from tests import test_base, test_albums, test_photos, test_tags
from tests.functional import test_base, test_albums, test_photos, test_tags
@unittest.skipIf(test_base.get_test_server_api() < 2,
"Don't test future API versions")

View file

@ -1,6 +1,6 @@
import tests.test_base
from tests.functional import test_base
class TestAlbums(tests.test_base.TestBase):
class TestAlbums(test_base.TestBase):
testcase_name = "album API"
def test_create_delete(self):

View file

@ -128,13 +128,13 @@ class TestBase(unittest.TestCase):
""" Upload three test photos """
album = cls.client.album.create(cls.TEST_ALBUM)
photos = [
cls.client.photo.upload("tests/test_photo1.jpg",
cls.client.photo.upload("tests/data/test_photo1.jpg",
title=cls.TEST_TITLE,
albums=album.id),
cls.client.photo.upload("tests/test_photo2.jpg",
cls.client.photo.upload("tests/data/test_photo2.jpg",
title=cls.TEST_TITLE,
albums=album.id),
cls.client.photo.upload("tests/test_photo3.jpg",
cls.client.photo.upload("tests/data/test_photo3.jpg",
title=cls.TEST_TITLE,
albums=album.id),
]

View file

@ -1,9 +1,9 @@
import logging
import openphoto
import tests.test_base
from tests.functional import test_base
class TestFramework(tests.test_base.TestBase):
class TestFramework(test_base.TestBase):
testcase_name = "framework"
def setUp(self):
@ -27,7 +27,7 @@ class TestFramework(tests.test_base.TestBase):
"""
For all API versions >0, we get a generic hello world message
"""
for api_version in range(1, tests.test_base.get_test_server_api() + 1):
for api_version in range(1, test_base.get_test_server_api() + 1):
client = openphoto.OpenPhoto(config_file=self.config_file,
api_version=api_version)
result = client.get("hello.json")

View file

@ -1,9 +1,9 @@
from __future__ import unicode_literals
import openphoto
import tests.test_base
from tests.functional import test_base
class TestPhotos(tests.test_base.TestBase):
class TestPhotos(test_base.TestBase):
testcase_name = "photo API"
def test_delete_upload(self):
@ -19,11 +19,11 @@ class TestPhotos(tests.test_base.TestBase):
self.assertEqual(self.client.photos.list(), [])
# Re-upload the photos, one of them using Bas64 encoding
ret_val = self.client.photo.upload("tests/test_photo1.jpg",
ret_val = self.client.photo.upload("tests/data/test_photo1.jpg",
title=self.TEST_TITLE)
self.client.photo.upload("tests/test_photo2.jpg",
self.client.photo.upload("tests/data/test_photo2.jpg",
title=self.TEST_TITLE)
self.client.photo.upload_encoded("tests/test_photo3.jpg",
self.client.photo.upload_encoded("tests/data/test_photo3.jpg",
title=self.TEST_TITLE)
# Check there are now three photos with the correct titles
@ -61,7 +61,7 @@ class TestPhotos(tests.test_base.TestBase):
""" Ensure that duplicate photos are rejected """
# Attempt to upload a duplicate
with self.assertRaises(openphoto.OpenPhotoDuplicateError):
self.client.photo.upload("tests/test_photo1.jpg",
self.client.photo.upload("tests/data/test_photo1.jpg",
title=self.TEST_TITLE)
# Check there are still three photos

View file

@ -3,11 +3,11 @@ try:
except ImportError:
import unittest
import tests.test_base
from tests.functional import test_base
@unittest.skipIf(tests.test_base.get_test_server_api() == 1,
@unittest.skipIf(test_base.get_test_server_api() == 1,
"The tag API didn't work at v1 - see frontend issue #927")
class TestTags(tests.test_base.TestBase):
class TestTags(test_base.TestBase):
testcase_name = "tag API"
def test_create_delete(self, tag_id="create_tag"):

0
tests/unit/__init__.py Normal file
View file

View file

@ -0,0 +1 @@
Test File

262
tests/unit/test_albums.py Normal file
View file

@ -0,0 +1,262 @@
from __future__ import unicode_literals
import mock
try:
import unittest2 as unittest # Python2.6
except ImportError:
import unittest
import openphoto
class TestAlbums(unittest.TestCase):
test_host = "test.example.com"
test_albums_dict = [{"cover": {"id": "1a", "tags": ["tag1", "tag2"]},
"id": "1",
"name": "Album 1",
"totalRows": 2},
{"cover": {"id": "2b", "tags": ["tag3", "tag4"]},
"id": "2",
"name": "Album 2",
"totalRows": 2}]
def setUp(self):
self.client = openphoto.OpenPhoto(host=self.test_host)
self.test_albums = [openphoto.objects.Album(self.client, album)
for album in self.test_albums_dict]
@staticmethod
def _return_value(result, message="", code=200):
return {"message": message, "code": code, "result": result}
class TestAlbumsList(TestAlbums):
@mock.patch.object(openphoto.OpenPhoto, 'get')
def test_albums_list(self, mock_get):
"""Check that the album list is returned correctly"""
mock_get.return_value = self._return_value(self.test_albums_dict)
result = self.client.albums.list()
mock_get.assert_called_with("/albums/list.json")
self.assertEqual(len(result), 2)
self.assertEqual(result[0].id, "1")
self.assertEqual(result[0].name, "Album 1")
self.assertEqual(result[1].id, "2")
self.assertEqual(result[1].name, "Album 2")
# TODO: cover should be updated to Photo object
@unittest.expectedFailure
@mock.patch.object(openphoto.OpenPhoto, 'get')
def test_albums_list_returns_cover_photos(self, mock_get):
"""Check that the album list returns cover photo objects"""
mock_get.return_value = self._return_value(self.test_albums_dict)
result = self.client.albums.list()
mock_get.assert_called_with("/albums/list.json")
self.assertEqual(len(result), 2)
self.assertEqual(result[0].id, "1")
self.assertEqual(result[0].name, "Album 1")
self.assertEqual(result[0].cover.id, "1a")
self.assertEqual(result[0].cover.tags, ["tag1", "tag2"])
self.assertEqual(result[1].id, "2")
self.assertEqual(result[0].name, "Album 2")
self.assertEqual(result[1].cover.id, "2b")
self.assertEqual(result[1].cover.tags, ["tag3", "tag4"])
class TestAlbumCreate(TestAlbums):
# TODO: cover should be updated to Photo object
@mock.patch.object(openphoto.OpenPhoto, 'post')
def test_album_create(self, mock_post):
"""Check that an album can be created"""
mock_post.return_value = self._return_value(self.test_albums_dict[0])
result = self.client.album.create(name="Test", foo="bar")
mock_post.assert_called_with("/album/create.json", name="Test",
foo="bar")
self.assertEqual(result.id, "1")
self.assertEqual(result.name, "Album 1")
# self.assertEqual(result.cover.id, "1a")
# self.assertEqual(result.cover.tags, ["tag1", "tag2"])
class TestAlbumDelete(TestAlbums):
@mock.patch.object(openphoto.OpenPhoto, 'post')
def test_album_delete(self, mock_post):
"""Check that an album can be deleted"""
mock_post.return_value = self._return_value(True)
result = self.client.album.delete(self.test_albums[0])
mock_post.assert_called_with("/album/1/delete.json")
self.assertEqual(result, True)
@mock.patch.object(openphoto.OpenPhoto, 'post')
def test_album_delete_id(self, mock_post):
"""Check that an album can be deleted using its ID"""
mock_post.return_value = self._return_value(True)
result = self.client.album.delete("1")
mock_post.assert_called_with("/album/1/delete.json")
self.assertEqual(result, True)
# TODO: album.delete should raise exception on failure
@unittest.expectedFailure
@mock.patch.object(openphoto.OpenPhoto, 'post')
def test_album_delete_failure(self, mock_post):
"""Check that an exception is raised if an album cannot be deleted"""
mock_post.return_value = self._return_value(False)
with self.assertRaises(openphoto.OpenPhotoError):
self.client.album.delete(self.test_albums[0])
# TODO: after deleting object fields, name and id should be set to None
@mock.patch.object(openphoto.OpenPhoto, 'post')
def test_album_object_delete(self, mock_post):
"""Check that an album can be deleted using the album object directly"""
mock_post.return_value = self._return_value(True)
album = self.test_albums[0]
result = album.delete()
mock_post.assert_called_with("/album/1/delete.json")
self.assertEqual(result, True)
self.assertEqual(album.get_fields(), {})
# self.assertEqual(album.id, None)
# self.assertEqual(album.name, None)
# TODO: album.delete should raise exception on failure
@unittest.expectedFailure
@mock.patch.object(openphoto.OpenPhoto, 'post')
def test_album_object_delete_failure(self, mock_post):
"""
Check that an exception is raised if an album cannot be deleted
when using the album object directly
"""
mock_post.return_value = self._return_value(False)
with self.assertRaises(openphoto.OpenPhotoError):
self.test_albums[0].delete()
class TestAlbumForm(TestAlbums):
@mock.patch.object(openphoto.OpenPhoto, 'post')
def test_album_form(self, _):
""" If album.form gets implemented, write a test! """
with self.assertRaises(NotImplementedError):
self.client.album.form(self.test_albums[0])
@mock.patch.object(openphoto.OpenPhoto, 'post')
def test_album_form_id(self, _):
""" If album.form gets implemented, write a test! """
with self.assertRaises(NotImplementedError):
self.client.album.form("1")
@mock.patch.object(openphoto.OpenPhoto, 'post')
def test_album_object_form(self, _):
""" If album.form gets implemented, write a test! """
with self.assertRaises(NotImplementedError):
self.test_albums[0].form()
class TestAlbumAddPhotos(TestAlbums):
@mock.patch.object(openphoto.OpenPhoto, 'post')
def test_album_add_photos(self, _):
""" If album.add_photos gets implemented, write a test! """
with self.assertRaises(NotImplementedError):
self.client.album.add_photos(self.test_albums[0], ["Photo Objects"])
@mock.patch.object(openphoto.OpenPhoto, 'post')
def test_album_add_photos_id(self, _):
""" If album.add_photos gets implemented, write a test! """
with self.assertRaises(NotImplementedError):
self.client.album.add_photos("1", ["Photo Objects"])
# TODO: object.add_photos should accept photos list as first parameter
@unittest.expectedFailure
@mock.patch.object(openphoto.OpenPhoto, 'post')
def test_album_object_add_photos(self, _):
""" If album.add_photos gets implemented, write a test! """
with self.assertRaises(NotImplementedError):
self.test_albums[0].add_photos(["Photo Objects"])
class TestAlbumRemovePhotos(TestAlbums):
@mock.patch.object(openphoto.OpenPhoto, 'post')
def test_album_remove_photos(self, _):
""" If album.remove_photos gets implemented, write a test! """
with self.assertRaises(NotImplementedError):
self.client.album.remove_photos(self.test_albums[0],
["Photo Objects"])
@mock.patch.object(openphoto.OpenPhoto, 'post')
def test_album_remove_photos_id(self, _):
""" If album.remove_photos gets implemented, write a test! """
with self.assertRaises(NotImplementedError):
self.client.album.remove_photos("1", ["Photo Objects"])
# TODO: object.remove_photos should accept photos list as first parameter
@unittest.expectedFailure
@mock.patch.object(openphoto.OpenPhoto, 'post')
def test_album_object_remove_photos(self, _):
""" If album.remove_photos gets implemented, write a test! """
with self.assertRaises(NotImplementedError):
self.test_albums[0].remove_photos(["Photo Objects"])
class TestAlbumUpdate(TestAlbums):
# TODO: cover should be updated to Photo object
@mock.patch.object(openphoto.OpenPhoto, 'post')
def test_album_update(self, mock_post):
"""Check that an album can be updated"""
mock_post.return_value = self._return_value(self.test_albums_dict[1])
result = self.client.album.update(self.test_albums[0], name="Test")
mock_post.assert_called_with("/album/1/update.json", name="Test")
self.assertEqual(result.id, "2")
self.assertEqual(result.name, "Album 2")
# self.assertEqual(result.cover.id, "2b")
# self.assertEqual(result.cover.tags, ["tag3", "tag4"])
# TODO: cover should be updated to Photo object
@mock.patch.object(openphoto.OpenPhoto, 'post')
def test_album_update_id(self, mock_post):
"""Check that an album can be updated using its ID"""
mock_post.return_value = self._return_value(self.test_albums_dict[1])
result = self.client.album.update("1", name="Test")
mock_post.assert_called_with("/album/1/update.json", name="Test")
self.assertEqual(result.id, "2")
self.assertEqual(result.name, "Album 2")
# self.assertEqual(result.cover.id, "2b")
# self.assertEqual(result.cover.tags, ["tag3", "tag4"])
# TODO: cover should be updated to Photo object
@mock.patch.object(openphoto.OpenPhoto, 'post')
def test_album_object_update(self, mock_post):
"""Check that an album can be updated using the album object directly"""
mock_post.return_value = self._return_value(self.test_albums_dict[1])
album = self.test_albums[0]
album.update(name="Test")
mock_post.assert_called_with("/album/1/update.json", name="Test")
self.assertEqual(album.id, "2")
self.assertEqual(album.name, "Album 2")
# self.assertEqual(album.cover.id, "2b")
# self.assertEqual(album.cover.tags, ["tag3", "tag4"])
class TestAlbumView(TestAlbums):
# TODO: cover should be updated to Photo object
@mock.patch.object(openphoto.OpenPhoto, 'get')
def test_album_view(self, mock_get):
"""Check that an album can be viewed"""
mock_get.return_value = self._return_value(self.test_albums_dict[1])
result = self.client.album.view(self.test_albums[0], name="Test")
mock_get.assert_called_with("/album/1/view.json", name="Test")
self.assertEqual(result.id, "2")
self.assertEqual(result.name, "Album 2")
# self.assertEqual(result.cover.id, "2b")
# self.assertEqual(result.cover.tags, ["tag3", "tag4"])
# TODO: cover should be updated to Photo object
@mock.patch.object(openphoto.OpenPhoto, 'get')
def test_album_view_id(self, mock_get):
"""Check that an album can be viewed using its ID"""
mock_get.return_value = self._return_value(self.test_albums_dict[1])
result = self.client.album.view("1", name="Test")
mock_get.assert_called_with("/album/1/view.json", name="Test")
self.assertEqual(result.id, "2")
self.assertEqual(result.name, "Album 2")
# self.assertEqual(result.cover.id, "2b")
# self.assertEqual(result.cover.tags, ["tag3", "tag4"])
# TODO: cover should be updated to Photo object
@mock.patch.object(openphoto.OpenPhoto, 'get')
def test_album_object_view(self, mock_get):
"""Check that an album can be viewed using the album object directly"""
mock_get.return_value = self._return_value(self.test_albums_dict[1])
album = self.test_albums[0]
album.view(name="Test")
mock_get.assert_called_with("/album/1/view.json", name="Test")
self.assertEqual(album.id, "2")
self.assertEqual(album.name, "Album 2")
# self.assertEqual(album.cover.id, "2b")
# self.assertEqual(album.cover.tags, ["tag3", "tag4"])

122
tests/unit/test_cli.py Normal file
View file

@ -0,0 +1,122 @@
from __future__ import unicode_literals
import os
import sys
import mock
try:
import StringIO as io # Python2
except ImportError:
import io # Python3
try:
import unittest2 as unittest # Python2.6
except ImportError:
import unittest
import openphoto
from openphoto.main import main
class TestException(Exception):
pass
def raise_exception(_):
raise TestException()
class TestCli(unittest.TestCase):
test_file = os.path.join("tests", "unit", "data", "test_file.txt")
@mock.patch.object(openphoto.main, "OpenPhoto")
@mock.patch('sys.stdout', new_callable=io.StringIO)
def test_defaults(self, _, mock_openphoto):
"""Check that the default behaviour is correct"""
get = mock_openphoto.return_value.get
main([])
mock_openphoto.assert_called_with(config_file=None)
get.assert_called_with("/photos/list.json", process_response=False)
@mock.patch.object(openphoto.main, "OpenPhoto")
@mock.patch('sys.stdout', new_callable=io.StringIO)
def test_config(self, _, mock_openphoto):
"""Check that a config file can be specified"""
main(["--config=test"])
mock_openphoto.assert_called_with(config_file="test")
@mock.patch.object(openphoto.main, "OpenPhoto")
@mock.patch('sys.stdout', new_callable=io.StringIO)
def test_get(self, mock_stdout, mock_openphoto):
"""Check that the get operation is working"""
get = mock_openphoto.return_value.get
get.return_value = "Result"
main(["-X", "GET", "-h", "test_host", "-e", "test_endpoint", "-F",
"field1=1", "-F", "field2=2"])
mock_openphoto.assert_called_with(host="test_host")
get.assert_called_with("test_endpoint", field1="1", field2="2",
process_response=False)
self.assertEqual(mock_stdout.getvalue(), "Result\n")
@mock.patch.object(openphoto.main, "OpenPhoto")
@mock.patch('sys.stdout', new_callable=io.StringIO)
def test_post(self, mock_stdout, mock_openphoto):
"""Check that the post operation is working"""
post = mock_openphoto.return_value.post
post.return_value = "Result"
main(["-X", "POST", "-h", "test_host", "-e", "test_endpoint", "-F",
"field1=1", "-F", "field2=2"])
mock_openphoto.assert_called_with(host="test_host")
post.assert_called_with("test_endpoint", field1="1", field2="2",
files={}, process_response=False)
self.assertEqual(mock_stdout.getvalue(), "Result\n")
@mock.patch.object(openphoto.main, "OpenPhoto")
@mock.patch('sys.stdout', new_callable=io.StringIO)
def test_post_files(self, _, mock_openphoto):
"""Check that files are posted correctly"""
post = mock_openphoto.return_value.post
main(["-X", "POST", "-F", "photo=@%s" % self.test_file])
# It's not possible to directly compare the file object,
# so check it manually
files = post.call_args[1]["files"]
self.assertEqual(list(files.keys()), ["photo"])
self.assertEqual(files["photo"].name, self.test_file)
@mock.patch.object(sys, "exit", raise_exception)
@mock.patch('sys.stderr', new_callable=io.StringIO)
def test_unknown_arg(self, mock_stderr):
"""Check that an unknown argument produces an error"""
with self.assertRaises(TestException):
main(["hello"])
self.assertIn("error: Unknown argument", mock_stderr.getvalue())
@mock.patch.object(sys, "exit", raise_exception)
@mock.patch('sys.stderr', new_callable=io.StringIO)
def test_unknown_option(self, mock_stderr):
"""Check that an unknown option produces an error"""
with self.assertRaises(TestException):
main(["--hello"])
self.assertIn("error: no such option", mock_stderr.getvalue())
@mock.patch.object(sys, "exit", raise_exception)
@mock.patch('sys.stdout', new_callable=io.StringIO)
def test_unknown_config(self, mock_stdout):
"""Check that an unknown config file produces an error"""
with self.assertRaises(TestException):
main(["--config=this_config_doesnt_exist"])
self.assertIn("No such file or directory", mock_stdout.getvalue())
self.assertIn("You must create a configuration file",
mock_stdout.getvalue())
self.assertIn("To get your credentials", mock_stdout.getvalue())
@mock.patch.object(openphoto.main, "OpenPhoto")
@mock.patch('sys.stdout', new_callable=io.StringIO)
def test_verbose(self, mock_stdout, _):
"""Check that the verbose option is working"""
main(["-v"])
self.assertIn("Method: GET", mock_stdout.getvalue())
self.assertIn("Endpoint: /photos/list.json", mock_stdout.getvalue())
@mock.patch.object(openphoto.main, "OpenPhoto")
@mock.patch('sys.stdout', new_callable=io.StringIO)
def test_pretty_print(self, mock_stdout, mock_openphoto):
"""Check that the pretty-print option is working"""
get = mock_openphoto.return_value.get
get.return_value = '{"test":1}'
main(["-p"])
self.assertEqual(mock_stdout.getvalue(), '{\n "test":1\n}\n')

View file

@ -29,11 +29,12 @@ class TestConfig(unittest.TestCase):
@staticmethod
def create_config(config_file, host):
"""Create a dummy config file"""
with open(os.path.join(CONFIG_PATH, config_file), "w") as conf:
conf.write("host = %s\n" % host)
conf.write("# Comment\n\n")
conf.write("consumerKey = \"%s_consumer_key\"\n" % config_file)
conf.write("\"consumerSecret\" = %s_consumer_secret\n" % config_file)
conf.write("\"consumerSecret\"= %s_consumer_secret\n" % config_file)
conf.write("'token'=%s_token\n" % config_file)
conf.write("tokenSecret = '%s_token_secret'\n" % config_file)

173
tests/unit/test_http.py Normal file
View file

@ -0,0 +1,173 @@
from __future__ import unicode_literals
import os
import json
import httpretty
try:
import unittest2 as unittest # Python2.6
except ImportError:
import unittest
import openphoto
class TestHttp(unittest.TestCase):
test_host = "test.example.com"
test_endpoint = "test.json"
test_uri = "http://%s/%s" % (test_host, test_endpoint)
test_data = {"message": "Test Message",
"code": 200,
"result": "Test Result"}
test_oauth = {"consumer_key": "dummy",
"consumer_secret": "dummy",
"token": "dummy",
"token_secret": "dummy"}
test_file = os.path.join("tests", "unit", "data", "test_file.txt")
def setUp(self):
self.client = openphoto.OpenPhoto(host=self.test_host,
**self.test_oauth)
def _register_uri(self, method, uri=test_uri, data=None, body=None,
**kwds):
"""Convenience wrapper around httpretty.register_uri"""
if data is None:
data = self.test_data
if body is None:
body = json.dumps(data)
httpretty.register_uri(method, uri=uri, body=body, **kwds)
@staticmethod
def _last_request():
"""This is a temporary measure until httpretty PR#59 is merged"""
return httpretty.httpretty.last_request
def test_attributes(self):
"""Check that the host attribute has been set correctly"""
self.assertEqual(self.client.host, self.test_host)
self.assertEqual(self.client.config.host, self.test_host)
@httpretty.activate
def test_get_with_parameters(self):
"""Check that the get method accepts parameters correctly"""
self._register_uri(httpretty.GET)
response = self.client.get(self.test_endpoint,
foo="bar", spam="eggs")
self.assertIn("OAuth", self._last_request().headers["authorization"])
self.assertEqual(self._last_request().querystring["foo"], ["bar"])
self.assertEqual(self._last_request().querystring["spam"], ["eggs"])
self.assertEqual(response, self.test_data)
self.assertEqual(self.client.last_url, self.test_uri)
self.assertEqual(self.client.last_params, {"foo": b"bar",
"spam": b"eggs"})
self.assertEqual(self.client.last_response.json(), self.test_data)
@httpretty.activate
def test_post_with_parameters(self):
"""Check that the post method accepts parameters correctly"""
self._register_uri(httpretty.POST)
response = self.client.post(self.test_endpoint,
foo="bar", spam="eggs")
self.assertIn(b"spam=eggs", self._last_request().body)
self.assertIn(b"foo=bar", self._last_request().body)
self.assertEqual(response, self.test_data)
self.assertEqual(self.client.last_url, self.test_uri)
self.assertEqual(self.client.last_params, {"foo": b"bar",
"spam": b"eggs"})
self.assertEqual(self.client.last_response.json(), self.test_data)
@httpretty.activate
def test_get_without_oauth(self):
"""Check that the get method works without OAuth parameters"""
self.client = openphoto.OpenPhoto(host=self.test_host)
self._register_uri(httpretty.GET)
response = self.client.get(self.test_endpoint)
self.assertNotIn("authorization", self._last_request().headers)
self.assertEqual(response, self.test_data)
@httpretty.activate
def test_post_without_oauth(self):
"""Check that the post method fails without OAuth parameters"""
self.client = openphoto.OpenPhoto(host=self.test_host)
self._register_uri(httpretty.POST)
with self.assertRaises(openphoto.OpenPhotoError):
self.client.post(self.test_endpoint)
@httpretty.activate
def test_get_without_response_processing(self):
"""Check that the get method works with response processing disabled"""
self._register_uri(httpretty.GET)
response = self.client.get(self.test_endpoint, process_response=False)
self.assertEqual(response, json.dumps(self.test_data))
@httpretty.activate
def test_post_without_response_processing(self):
"""Check that the post method works with response processing disabled"""
self._register_uri(httpretty.POST)
response = self.client.post(self.test_endpoint, process_response=False)
self.assertEqual(response, json.dumps(self.test_data))
@httpretty.activate
def test_get_parameter_processing(self):
"""Check that the parameter processing function is working"""
self._register_uri(httpretty.GET)
photo = openphoto.objects.Photo(None, {"id": "photo_id"})
album = openphoto.objects.Album(None, {"id": "album_id"})
tag = openphoto.objects.Tag(None, {"id": "tag_id"})
self.client.get(self.test_endpoint,
photo=photo, album=album, tag=tag,
list_=[photo, album, tag],
boolean=True,
unicode_="\xfcmlaut")
params = self._last_request().querystring
self.assertEqual(params["photo"], ["photo_id"])
self.assertEqual(params["album"], ["album_id"])
self.assertEqual(params["tag"], ["tag_id"])
self.assertEqual(params["list_"], ["photo_id,album_id,tag_id"])
self.assertEqual(params["boolean"], ["1"])
self.assertIn(params["unicode_"], [["\xc3\xbcmlaut"], ["\xfcmlaut"]])
@httpretty.activate
def test_get_with_api_version(self):
"""Check that an API version can be specified for the get method"""
self.client = openphoto.OpenPhoto(host=self.test_host, api_version=1)
self._register_uri(httpretty.GET,
uri="http://%s/v1/%s" % (self.test_host,
self.test_endpoint))
self.client.get(self.test_endpoint)
@httpretty.activate
def test_post_with_api_version(self):
"""Check that an API version can be specified for the post method"""
self.client = openphoto.OpenPhoto(host=self.test_host, api_version=1,
**self.test_oauth)
self._register_uri(httpretty.POST,
uri="http://%s/v1/%s" % (self.test_host,
self.test_endpoint))
self.client.post(self.test_endpoint)
@httpretty.activate
def test_post_file(self):
"""Check that a file can be posted"""
self._register_uri(httpretty.POST)
with open(self.test_file, 'rb') as in_file:
response = self.client.post(self.test_endpoint,
files={"file": in_file})
self.assertEqual(response, self.test_data)
body = str(self._last_request().body)
self.assertIn("Content-Disposition: form-data; "+
"name=\"file\"; filename=\"test_file.txt\"", body)
self.assertIn("Test File", str(body))
@httpretty.activate
def test_post_file_parameters_are_sent_as_querystring(self):
"""
Check that parameters are send as a query string
when a file is posted
"""
self._register_uri(httpretty.POST)
with open(self.test_file, 'rb') as in_file:
response = self.client.post(self.test_endpoint, foo="bar",
files={"file": in_file})
self.assertEqual(response, self.test_data)
self.assertEqual(self._last_request().querystring["foo"], ["bar"])

View file

@ -0,0 +1,195 @@
from __future__ import unicode_literals
import json
import httpretty
# TEMP: Temporary hack until httpretty string checking is fixed
if httpretty.compat.PY3:
httpretty.core.basestring = (bytes, str)
try:
import unittest2 as unittest # Python2.6
except ImportError:
import unittest
import openphoto
class TestHttpErrors(unittest.TestCase):
test_host = "test.example.com"
test_endpoint = "test.json"
test_uri = "http://%s/%s" % (test_host, test_endpoint)
test_data = {"message": "Test Message",
"code": 200,
"result": "Test Result"}
test_oauth = {"consumer_key": "dummy",
"consumer_secret": "dummy",
"token": "dummy",
"token_secret": "dummy"}
def setUp(self):
self.client = openphoto.OpenPhoto(host=self.test_host,
**self.test_oauth)
def _register_uri(self, method, uri=test_uri,
data=None, body=None, status=200, **kwds):
"""Convenience wrapper around httpretty.register_uri"""
if data is None:
data = self.test_data
# Set the JSON return code to match the HTTP status
data["code"] = status
if body is None:
body = json.dumps(data)
httpretty.register_uri(method, uri=uri, body=body, status=status,
**kwds)
@httpretty.activate
def test_get_with_error_status(self):
"""
Check that an error status causes the get method
to raise an exception
"""
self._register_uri(httpretty.GET, status=500)
with self.assertRaises(openphoto.OpenPhotoError):
self.client.get(self.test_endpoint)
@httpretty.activate
def test_post_with_error_status(self):
"""
Check that an error status causes the post method
to raise an exception
"""
self._register_uri(httpretty.POST, status=500)
with self.assertRaises(openphoto.OpenPhotoError):
self.client.post(self.test_endpoint)
# TODO: 404 status should raise 404 error, even if JSON is valid
@unittest.expectedFailure
@httpretty.activate
def test_get_with_404_status(self):
"""
Check that a 404 status causes the get method
to raise a 404 exception
"""
self._register_uri(httpretty.GET, status=404)
with self.assertRaises(openphoto.OpenPhoto404Error):
self.client.get(self.test_endpoint)
# TODO: 404 status should raise 404 error, even if JSON is valid
@unittest.expectedFailure
@httpretty.activate
def test_post_with_404_status(self):
"""
Check that a 404 status causes the post method
to raise a 404 exception
"""
self._register_uri(httpretty.POST, status=404)
with self.assertRaises(openphoto.OpenPhoto404Error):
self.client.post(self.test_endpoint)
@httpretty.activate
def test_get_with_invalid_json(self):
"""
Check that invalid JSON causes the get method to
raise an exception
"""
self._register_uri(httpretty.GET, body="Invalid JSON")
with self.assertRaises(ValueError):
self.client.get(self.test_endpoint)
@httpretty.activate
def test_post_with_invalid_json(self):
"""
Check that invalid JSON causes the post method to
raise an exception
"""
self._register_uri(httpretty.POST, body="Invalid JSON")
with self.assertRaises(ValueError):
self.client.post(self.test_endpoint)
@httpretty.activate
def test_get_with_error_status_and_invalid_json(self):
"""
Check that invalid JSON causes the get method to raise an exception,
even with an error status is returned
"""
self._register_uri(httpretty.GET, body="Invalid JSON", status=500)
with self.assertRaises(openphoto.OpenPhotoError):
self.client.get(self.test_endpoint)
@httpretty.activate
def test_post_with_error_status_and_invalid_json(self):
"""
Check that invalid JSON causes the post method to raise an exception,
even with an error status is returned
"""
self._register_uri(httpretty.POST, body="Invalid JSON", status=500)
with self.assertRaises(openphoto.OpenPhotoError):
self.client.post(self.test_endpoint)
@httpretty.activate
def test_get_with_404_status_and_invalid_json(self):
"""
Check that invalid JSON causes the get method to raise an exception,
even with a 404 status is returned
"""
self._register_uri(httpretty.GET, body="Invalid JSON", status=404)
with self.assertRaises(openphoto.OpenPhoto404Error):
self.client.get(self.test_endpoint)
@httpretty.activate
def test_post_with_404_status_and_invalid_json(self):
"""
Check that invalid JSON causes the post method to raise an exception,
even with a 404 status is returned
"""
self._register_uri(httpretty.POST, body="Invalid JSON", status=404)
with self.assertRaises(openphoto.OpenPhoto404Error):
self.client.post(self.test_endpoint)
@httpretty.activate
def test_get_with_duplicate_status(self):
"""
Check that a get with a duplicate status
raises a duplicate exception
"""
data = {"message": "This photo already exists", "code": 409}
self._register_uri(httpretty.GET, data=data, status=409)
with self.assertRaises(openphoto.OpenPhotoDuplicateError):
self.client.get(self.test_endpoint)
@httpretty.activate
def test_post_with_duplicate_status(self):
"""
Check that a post with a duplicate status
raises a duplicate exception
"""
data = {"message": "This photo already exists", "code": 409}
self._register_uri(httpretty.POST, data=data, status=409)
with self.assertRaises(openphoto.OpenPhotoDuplicateError):
self.client.post(self.test_endpoint)
# TODO: Status code mismatch should raise an exception
@unittest.expectedFailure
@httpretty.activate
def test_get_with_status_code_mismatch(self):
"""
Check that an exception is raised if a get returns a
status code that doesn't match the JSON code
"""
data = {"message": "Test Message", "code": 200}
self._register_uri(httpretty.GET, data=data, status=202)
with self.assertRaises(openphoto.OpenPhotoError):
self.client.get(self.test_endpoint)
# TODO: Status code mismatch should raise an exception
@unittest.expectedFailure
@httpretty.activate
def test_post_with_status_code_mismatch(self):
"""
Check that an exception is raised if a post returns a
status code that doesn't match the JSON code
"""
data = {"message": "Test Message", "code": 200}
self._register_uri(httpretty.POST, data=data, status=202)
with self.assertRaises(openphoto.OpenPhotoError):
self.client.post(self.test_endpoint)

441
tests/unit/test_photos.py Normal file
View file

@ -0,0 +1,441 @@
from __future__ import unicode_literals
import os
import base64
import mock
try:
import unittest2 as unittest # Python2.6
except ImportError:
import unittest
import openphoto
class TestPhotos(unittest.TestCase):
test_host = "test.example.com"
test_file = os.path.join("tests", "unit", "data", "test_file.txt")
test_photos_dict = [{"id": "1a", "tags": ["tag1", "tag2"],
"totalPages": 1, "totalRows": 2},
{"id": "2b", "tags": ["tag3", "tag4"],
"totalPages": 1, "totalRows": 2}]
def setUp(self):
self.client = openphoto.OpenPhoto(host=self.test_host)
self.test_photos = [openphoto.objects.Photo(self.client, photo)
for photo in self.test_photos_dict]
@staticmethod
def _return_value(result, message="", code=200):
return {"message": message, "code": code, "result": result}
class TestPhotosList(TestPhotos):
@mock.patch.object(openphoto.OpenPhoto, 'get')
def test_photos_list(self, mock_get):
"""Check that the photo list is returned correctly"""
mock_get.return_value = self._return_value(self.test_photos_dict)
result = self.client.photos.list()
mock_get.assert_called_with("/photos/list.json")
self.assertEqual(len(result), 2)
self.assertEqual(result[0].id, "1a")
self.assertEqual(result[0].tags, ["tag1", "tag2"])
self.assertEqual(result[1].id, "2b")
self.assertEqual(result[1].tags, ["tag3", "tag4"])
class TestPhotosUpdate(TestPhotos):
# TODO: photos.update should accept a list of Photo objects
@unittest.expectedFailure
@mock.patch.object(openphoto.OpenPhoto, 'post')
def test_photos_update(self, mock_post):
"""Check that multiple photos can be updated"""
mock_post.return_value = self._return_value(True)
result = self.client.photos.update(self.test_photos, title="Test")
mock_post.assert_called_with("/photos/update.json",
ids=["1a", "2b"], title="Test")
self.assertEqual(result, True)
@mock.patch.object(openphoto.OpenPhoto, 'post')
def test_photos_update_ids(self, mock_post):
"""Check that multiple photos can be updated using their IDs"""
mock_post.return_value = self._return_value(True)
result = self.client.photos.update(["1a", "2b"], title="Test")
mock_post.assert_called_with("/photos/update.json",
ids=["1a", "2b"], title="Test")
self.assertEqual(result, True)
@mock.patch.object(openphoto.OpenPhoto, 'post')
def test_photos_update_failure(self, mock_post):
"""
Check that an exception is raised if multiple photos
cannot be updated
"""
mock_post.return_value = self._return_value(False)
with self.assertRaises(openphoto.OpenPhotoError):
self.client.photos.update(self.test_photos, title="Test")
class TestPhotosDelete(TestPhotos):
# TODO: photos.delete should accept a list of Photo objects
@unittest.expectedFailure
@mock.patch.object(openphoto.OpenPhoto, 'post')
def test_photos_delete(self, mock_post):
"""Check that multiple photos can be deleted"""
mock_post.return_value = self._return_value(True)
result = self.client.photos.delete(self.test_photos)
mock_post.assert_called_with("/photos/delete.json", ids=["1a", "2b"])
self.assertEqual(result, True)
@mock.patch.object(openphoto.OpenPhoto, 'post')
def test_photos_delete_ids(self, mock_post):
"""Check that multiple photos can be deleted using their IDs"""
mock_post.return_value = self._return_value(True)
result = self.client.photos.delete(["1a", "2b"])
mock_post.assert_called_with("/photos/delete.json", ids=["1a", "2b"])
self.assertEqual(result, True)
@mock.patch.object(openphoto.OpenPhoto, 'post')
def test_photos_delete_failure(self, mock_post):
"""
Check that an exception is raised if multiple photos
cannot be deleted
"""
mock_post.return_value = self._return_value(False)
with self.assertRaises(openphoto.OpenPhotoError):
self.client.photos.delete(self.test_photos)
class TestPhotoDelete(TestPhotos):
@mock.patch.object(openphoto.OpenPhoto, 'post')
def test_photo_delete(self, mock_post):
"""Check that a photo can be deleted"""
mock_post.return_value = self._return_value(True)
result = self.client.photo.delete(self.test_photos[0])
mock_post.assert_called_with("/photo/1a/delete.json")
self.assertEqual(result, True)
@mock.patch.object(openphoto.OpenPhoto, 'post')
def test_photo_delete_id(self, mock_post):
"""Check that a photo can be deleted using its ID"""
mock_post.return_value = self._return_value(True)
result = self.client.photo.delete("1a")
mock_post.assert_called_with("/photo/1a/delete.json")
self.assertEqual(result, True)
# TODO: photo.delete should raise exception on failure
@unittest.expectedFailure
@mock.patch.object(openphoto.OpenPhoto, 'post')
def test_photo_delete_failure(self, mock_post):
"""Check that an exception is raised if a photo cannot be deleted"""
mock_post.return_value = self._return_value(False)
with self.assertRaises(openphoto.OpenPhotoError):
self.client.photo.delete(self.test_photos[0])
# TODO: after deleting object fields, name and id should be set to None
@mock.patch.object(openphoto.OpenPhoto, 'post')
def test_photo_object_delete(self, mock_post):
"""
Check that a photo can be deleted when using
the photo object directly
"""
mock_post.return_value = self._return_value(True)
photo = self.test_photos[0]
result = photo.delete()
mock_post.assert_called_with("/photo/1a/delete.json")
self.assertEqual(result, True)
self.assertEqual(photo.get_fields(), {})
# self.assertEqual(photo.id, None)
# TODO: photo.delete should raise exception on failure
@unittest.expectedFailure
@mock.patch.object(openphoto.OpenPhoto, 'post')
def test_photo_object_delete_failure(self, mock_post):
"""
Check that an exception is raised if a photo cannot be deleted
when using the photo object directly
"""
mock_post.return_value = self._return_value(False)
with self.assertRaises(openphoto.OpenPhotoError):
self.test_photos[0].delete()
class TestPhotoEdit(TestPhotos):
@mock.patch.object(openphoto.OpenPhoto, 'get')
def test_photo_edit(self, mock_get):
"""Check that a the photo edit endpoint is working"""
mock_get.return_value = self._return_value({"markup": "<form/>"})
result = self.client.photo.edit(self.test_photos[0])
mock_get.assert_called_with("/photo/1a/edit.json")
self.assertEqual(result, "<form/>")
@mock.patch.object(openphoto.OpenPhoto, 'get')
def test_photo_edit_id(self, mock_get):
"""Check that a the photo edit endpoint is working when using an ID"""
mock_get.return_value = self._return_value({"markup": "<form/>"})
result = self.client.photo.edit("1a")
mock_get.assert_called_with("/photo/1a/edit.json")
self.assertEqual(result, "<form/>")
@mock.patch.object(openphoto.OpenPhoto, 'get')
def test_photo_object_edit(self, mock_get):
"""
Check that a the photo edit endpoint is working
when using the photo object directly
"""
mock_get.return_value = self._return_value({"markup": "<form/>"})
result = self.test_photos[0].edit()
mock_get.assert_called_with("/photo/1a/edit.json")
self.assertEqual(result, "<form/>")
class TestPhotoReplace(TestPhotos):
@mock.patch.object(openphoto.OpenPhoto, 'post')
def test_photo_replace(self, _):
""" If photo.replace gets implemented, write a test! """
with self.assertRaises(NotImplementedError):
self.client.photo.replace(self.test_photos[0], self.test_file)
@mock.patch.object(openphoto.OpenPhoto, 'post')
def test_photo_replace_id(self, _):
""" If photo.replace gets implemented, write a test! """
with self.assertRaises(NotImplementedError):
self.client.photo.replace("1a", self.test_file)
@mock.patch.object(openphoto.OpenPhoto, 'post')
def test_photo_object_replace(self, _):
""" If photo.replace gets implemented, write a test! """
with self.assertRaises(NotImplementedError):
self.test_photos[0].replace(self.test_file)
@mock.patch.object(openphoto.OpenPhoto, 'post')
def test_photo_replace_encoded(self, _):
""" If photo.replace_encoded gets implemented, write a test! """
with self.assertRaises(NotImplementedError):
self.client.photo.replace_encoded(self.test_photos[0],
self.test_file)
@mock.patch.object(openphoto.OpenPhoto, 'post')
def test_photo_replace_encoded_id(self, _):
""" If photo.replace_encoded gets implemented, write a test! """
with self.assertRaises(NotImplementedError):
self.client.photo.replace_encoded("1a", self.test_file)
# TODO: replace_encoded parameter should be called photo_file,
# not encoded_photo
@unittest.expectedFailure
@mock.patch.object(openphoto.OpenPhoto, 'post')
def test_photo_object_replace_encoded(self, _):
""" If photo.replace_encoded gets implemented, write a test! """
with self.assertRaises(NotImplementedError):
self.test_photos[0].replace_encoded(photo_file=self.test_file)
class TestPhotoUpdate(TestPhotos):
@mock.patch.object(openphoto.OpenPhoto, 'post')
def test_photo_update(self, mock_post):
"""Check that a photo can be updated"""
mock_post.return_value = self._return_value(self.test_photos_dict[1])
result = self.client.photo.update(self.test_photos[0], title="Test")
mock_post.assert_called_with("/photo/1a/update.json", title="Test")
self.assertEqual(result.get_fields(), self.test_photos_dict[1])
@mock.patch.object(openphoto.OpenPhoto, 'post')
def test_photo_update_id(self, mock_post):
"""Check that a photo can be updated using its ID"""
mock_post.return_value = self._return_value(self.test_photos_dict[1])
result = self.client.photo.update("1a", title="Test")
mock_post.assert_called_with("/photo/1a/update.json", title="Test")
self.assertEqual(result.get_fields(), self.test_photos_dict[1])
@mock.patch.object(openphoto.OpenPhoto, 'post')
def test_photo_object_update(self, mock_post):
"""
Check that a photo can be updated
when using the photo object directly
"""
mock_post.return_value = self._return_value(self.test_photos_dict[1])
photo = self.test_photos[0]
photo.update(title="Test")
mock_post.assert_called_with("/photo/1a/update.json", title="Test")
self.assertEqual(photo.get_fields(), self.test_photos_dict[1])
class TestPhotoView(TestPhotos):
@mock.patch.object(openphoto.OpenPhoto, 'get')
def test_photo_view(self, mock_get):
"""Check that a photo can be viewed"""
mock_get.return_value = self._return_value(self.test_photos_dict[1])
result = self.client.photo.view(self.test_photos[0],
returnSizes="20x20")
mock_get.assert_called_with("/photo/1a/view.json", returnSizes="20x20")
self.assertEqual(result.get_fields(), self.test_photos_dict[1])
@mock.patch.object(openphoto.OpenPhoto, 'get')
def test_photo_view_id(self, mock_get):
"""Check that a photo can be viewed using its ID"""
mock_get.return_value = self._return_value(self.test_photos_dict[1])
result = self.client.photo.view("1a", returnSizes="20x20")
mock_get.assert_called_with("/photo/1a/view.json", returnSizes="20x20")
self.assertEqual(result.get_fields(), self.test_photos_dict[1])
@mock.patch.object(openphoto.OpenPhoto, 'get')
def test_photo_object_view(self, mock_get):
"""
Check that a photo can be viewed
when using the photo object directly
"""
mock_get.return_value = self._return_value(self.test_photos_dict[1])
photo = self.test_photos[0]
photo.view(returnSizes="20x20")
mock_get.assert_called_with("/photo/1a/view.json", returnSizes="20x20")
self.assertEqual(photo.get_fields(), self.test_photos_dict[1])
class TestPhotoUpload(TestPhotos):
@mock.patch.object(openphoto.OpenPhoto, 'post')
def test_photo_upload(self, mock_post):
"""Check that a photo can be uploaded"""
mock_post.return_value = self._return_value(self.test_photos_dict[0])
result = self.client.photo.upload(self.test_file, title="Test")
# It's not possible to compare the file object,
# so check each parameter individually
endpoint = mock_post.call_args[0]
title = mock_post.call_args[1]["title"]
files = mock_post.call_args[1]["files"]
self.assertEqual(endpoint, ("/photo/upload.json",))
self.assertEqual(title, "Test")
self.assertIn("photo", files)
self.assertEqual(result.get_fields(), self.test_photos_dict[0])
@mock.patch.object(openphoto.OpenPhoto, 'post')
def test_photo_upload_encoded(self, mock_post):
"""Check that a photo can be uploaded using Base64 encoding"""
mock_post.return_value = self._return_value(self.test_photos_dict[0])
result = self.client.photo.upload_encoded(self.test_file, title="Test")
with open(self.test_file, "rb") as in_file:
encoded_file = base64.b64encode(in_file.read())
mock_post.assert_called_with("/photo/upload.json",
photo=encoded_file, title="Test")
self.assertEqual(result.get_fields(), self.test_photos_dict[0])
class TestPhotoDynamicUrl(TestPhotos):
@mock.patch.object(openphoto.OpenPhoto, 'get')
def test_photo_dynamic_url(self, _):
""" If photo.dynamic_url gets implemented, write a test! """
with self.assertRaises(NotImplementedError):
self.client.photo.dynamic_url(self.test_photos[0])
@mock.patch.object(openphoto.OpenPhoto, 'get')
def test_photo_dynamic_url_id(self, _):
""" If photo.dynamic_url gets implemented, write a test! """
with self.assertRaises(NotImplementedError):
self.client.photo.dynamic_url("1a")
@mock.patch.object(openphoto.OpenPhoto, 'get')
def test_photo_object_dynamic_url(self, _):
""" If photo.dynamic_url gets implemented, write a test! """
with self.assertRaises(NotImplementedError):
self.test_photos[0].dynamic_url()
class TestPhotoNextPrevious(TestPhotos):
@mock.patch.object(openphoto.OpenPhoto, 'get')
def test_photo_next_previous(self, mock_get):
"""Check that the next/previous photos are returned"""
mock_get.return_value = self._return_value(
{"next": [self.test_photos_dict[0]],
"previous": [self.test_photos_dict[1]]})
result = self.client.photo.next_previous(self.test_photos[0])
mock_get.assert_called_with("/photo/1a/nextprevious.json")
self.assertEqual(result["next"][0].get_fields(),
self.test_photos_dict[0])
self.assertEqual(result["previous"][0].get_fields(),
self.test_photos_dict[1])
@mock.patch.object(openphoto.OpenPhoto, 'get')
def test_photo_next_previous_id(self, mock_get):
"""
Check that the next/previous photos are returned
when using the photo ID
"""
mock_get.return_value = self._return_value(
{"next": [self.test_photos_dict[0]],
"previous": [self.test_photos_dict[1]]})
result = self.client.photo.next_previous("1a")
mock_get.assert_called_with("/photo/1a/nextprevious.json")
self.assertEqual(result["next"][0].get_fields(),
self.test_photos_dict[0])
self.assertEqual(result["previous"][0].get_fields(),
self.test_photos_dict[1])
@mock.patch.object(openphoto.OpenPhoto, 'get')
def test_photo_object_next_previous(self, mock_get):
"""
Check that the next/previous photos are returned
when using the photo object directly
"""
mock_get.return_value = self._return_value(
{"next": [self.test_photos_dict[0]],
"previous": [self.test_photos_dict[1]]})
result = self.test_photos[0].next_previous()
mock_get.assert_called_with("/photo/1a/nextprevious.json")
self.assertEqual(result["next"][0].get_fields(),
self.test_photos_dict[0])
self.assertEqual(result["previous"][0].get_fields(),
self.test_photos_dict[1])
@mock.patch.object(openphoto.OpenPhoto, 'get')
def test_photo_next(self, mock_get):
"""Check that the next photos are returned"""
mock_get.return_value = self._return_value(
{"next": [self.test_photos_dict[0]]})
result = self.client.photo.next_previous(self.test_photos[0])
mock_get.assert_called_with("/photo/1a/nextprevious.json")
self.assertEqual(result["next"][0].get_fields(),
self.test_photos_dict[0])
self.assertNotIn("previous", result)
@mock.patch.object(openphoto.OpenPhoto, 'get')
def test_photo_previous(self, mock_get):
"""Check that the previous photos are returned"""
mock_get.return_value = self._return_value(
{"previous": [self.test_photos_dict[1]]})
result = self.client.photo.next_previous(self.test_photos[0])
mock_get.assert_called_with("/photo/1a/nextprevious.json")
self.assertEqual(result["previous"][0].get_fields(),
self.test_photos_dict[1])
self.assertNotIn("next", result)
@mock.patch.object(openphoto.OpenPhoto, 'get')
def test_photo_multiple_next_previous(self, mock_get):
"""Check that multiple next/previous photos are returned"""
mock_get.return_value = self._return_value(
{"next": [self.test_photos_dict[0], self.test_photos_dict[0]],
"previous": [self.test_photos_dict[1], self.test_photos_dict[1]]})
result = self.client.photo.next_previous(self.test_photos[0])
mock_get.assert_called_with("/photo/1a/nextprevious.json")
self.assertEqual(result["next"][0].get_fields(),
self.test_photos_dict[0])
self.assertEqual(result["next"][1].get_fields(),
self.test_photos_dict[0])
self.assertEqual(result["previous"][0].get_fields(),
self.test_photos_dict[1])
self.assertEqual(result["previous"][1].get_fields(),
self.test_photos_dict[1])
class TestPhotoTransform(TestPhotos):
@mock.patch.object(openphoto.OpenPhoto, 'post')
def test_photo_transform(self, mock_post):
"""Check that a photo can be transformed"""
mock_post.return_value = self._return_value(self.test_photos_dict[1])
result = self.client.photo.transform(self.test_photos[0], rotate="90")
mock_post.assert_called_with("/photo/1a/transform.json", rotate="90")
self.assertEqual(result.get_fields(), self.test_photos_dict[1])
@mock.patch.object(openphoto.OpenPhoto, 'post')
def test_photo_transform_id(self, mock_post):
"""Check that a photo can be transformed using its ID"""
mock_post.return_value = self._return_value(self.test_photos_dict[1])
result = self.client.photo.transform("1a", rotate="90")
mock_post.assert_called_with("/photo/1a/transform.json", rotate="90")
self.assertEqual(result.get_fields(), self.test_photos_dict[1])
@mock.patch.object(openphoto.OpenPhoto, 'post')
def test_photo_object_transform(self, mock_post):
"""
Check that a photo can be transformed
when using the photo object directly
"""
mock_post.return_value = self._return_value(self.test_photos_dict[1])
photo = self.test_photos[0]
photo.transform(rotate="90")
mock_post.assert_called_with("/photo/1a/transform.json", rotate="90")
self.assertEqual(photo.get_fields(), self.test_photos_dict[1])

128
tests/unit/test_tags.py Normal file
View file

@ -0,0 +1,128 @@
from __future__ import unicode_literals
import mock
try:
import unittest2 as unittest # Python2.6
except ImportError:
import unittest
import openphoto
class TestTags(unittest.TestCase):
test_host = "test.example.com"
test_tags = None
test_tags_dict = [{"count": 11, "id":"tag1"},
{"count": 5, "id":"tag2"}]
def setUp(self):
self.client = openphoto.OpenPhoto(host=self.test_host)
self.test_tags = [openphoto.objects.Tag(self.client, tag)
for tag in self.test_tags_dict]
@staticmethod
def _return_value(result, message="", code=200):
return {"message": message, "code": code, "result": result}
class TestTagsList(TestTags):
@mock.patch.object(openphoto.OpenPhoto, 'get')
def test_tags_list(self, mock_get):
"""Check that the the tag list is returned correctly"""
mock_get.return_value = self._return_value(self.test_tags_dict)
result = self.client.tags.list()
mock_get.assert_called_with("/tags/list.json")
self.assertEqual(len(result), 2)
self.assertEqual(result[0].id, "tag1")
self.assertEqual(result[0].count, 11)
self.assertEqual(result[1].id, "tag2")
self.assertEqual(result[1].count, 5)
class TestTagCreate(TestTags):
# TODO: should return a tag object, not a result dict
@unittest.expectedFailure
@mock.patch.object(openphoto.OpenPhoto, 'post')
def test_tag_create(self, mock_post):
"""Check that a tag can be created"""
mock_post.return_value = self._return_value(self.test_tags_dict[0])
result = self.client.tag.create(tag="Test", foo="bar")
mock_post.assert_called_with("/tag/create.json", tag="Test", foo="bar")
self.assertEqual(result.id, "tag1")
self.assertEqual(result.count, 11)
class TestTagDelete(TestTags):
@mock.patch.object(openphoto.OpenPhoto, 'post')
def test_tag_delete(self, mock_post):
"""Check that a tag can be deleted"""
mock_post.return_value = self._return_value(True)
result = self.client.tag.delete(self.test_tags[0])
mock_post.assert_called_with("/tag/tag1/delete.json")
self.assertEqual(result, True)
@mock.patch.object(openphoto.OpenPhoto, 'post')
def test_tag_delete_id(self, mock_post):
"""Check that a tag can be deleted using its ID"""
mock_post.return_value = self._return_value(True)
result = self.client.tag.delete("tag1")
mock_post.assert_called_with("/tag/tag1/delete.json")
self.assertEqual(result, True)
# TODO: tag.delete should raise exception on failure
@unittest.expectedFailure
@mock.patch.object(openphoto.OpenPhoto, 'post')
def test_tag_delete_failure(self, mock_post):
"""Check that an exception is raised if a tag cannot be deleted"""
mock_post.return_value = self._return_value(False)
with self.assertRaises(openphoto.OpenPhotoError):
self.client.tag.delete(self.test_tags[0])
# TODO: after deleting object fields, id should be set to None
@mock.patch.object(openphoto.OpenPhoto, 'post')
def test_tag_object_delete(self, mock_post):
"""Check that a tag can be deleted when using the tag object directly"""
mock_post.return_value = self._return_value(True)
tag = self.test_tags[0]
result = tag.delete()
mock_post.assert_called_with("/tag/tag1/delete.json")
self.assertEqual(result, True)
self.assertEqual(tag.get_fields(), {})
# self.assertEqual(tag.id, None)
# TODO: tag.delete should raise exception on failure
@unittest.expectedFailure
@mock.patch.object(openphoto.OpenPhoto, 'post')
def test_tag_object_delete_failure(self, mock_post):
"""
Check that an exception is raised if a tag cannot be deleted
when using the tag object directly
"""
mock_post.return_value = self._return_value(False)
with self.assertRaises(openphoto.OpenPhotoError):
self.test_tags[0].delete()
class TestTagUpdate(TestTags):
@mock.patch.object(openphoto.OpenPhoto, 'post')
def test_tag_update(self, mock_post):
"""Check that a tag can be updated"""
mock_post.return_value = self._return_value(self.test_tags_dict[1])
result = self.client.tag.update(self.test_tags[0], name="Test")
mock_post.assert_called_with("/tag/tag1/update.json", name="Test")
self.assertEqual(result.id, "tag2")
self.assertEqual(result.count, 5)
@mock.patch.object(openphoto.OpenPhoto, 'post')
def test_tag_update_id(self, mock_post):
"""Check that a tag can be updated using its ID"""
mock_post.return_value = self._return_value(self.test_tags_dict[1])
result = self.client.tag.update("tag1", name="Test")
mock_post.assert_called_with("/tag/tag1/update.json", name="Test")
self.assertEqual(result.id, "tag2")
self.assertEqual(result.count, 5)
@mock.patch.object(openphoto.OpenPhoto, 'post')
def test_tag_object_update(self, mock_post):
"""Check that a tag can be updated when using the tag object directly"""
mock_post.return_value = self._return_value(self.test_tags_dict[1])
tag = self.test_tags[0]
tag.update(name="Test")
mock_post.assert_called_with("/tag/tag1/update.json", name="Test")
self.assertEqual(tag.id, "tag2")
self.assertEqual(tag.count, 5)

View file

@ -2,10 +2,15 @@
envlist = py26, py27, py33
[testenv]
commands = python -m unittest discover --catch
commands = python -m unittest discover --catch tests/unit
deps =
mock >= 1.0.0
httpretty >= 0.6.1
[testenv:py26]
commands = unit2 discover --catch
commands = unit2 discover --catch tests/unit
deps =
mock >= 1.0.0
httpretty >= 0.6.1
unittest2
discover