From 24fcf3f41554429247fe705121bc0ca68bdb71c7 Mon Sep 17 00:00:00 2001 From: sneakypete81 Date: Sat, 15 Jun 2013 18:40:38 +0100 Subject: [PATCH 01/26] Added basic get/post unit tests --- tests/unit/__init__.py | 0 tests/unit/test_http.py | 108 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 108 insertions(+) create mode 100644 tests/unit/__init__.py create mode 100644 tests/unit/test_http.py diff --git a/tests/unit/__init__.py b/tests/unit/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/unit/test_http.py b/tests/unit/test_http.py new file mode 100644 index 0000000..7fd18d2 --- /dev/null +++ b/tests/unit/test_http.py @@ -0,0 +1,108 @@ +from __future__ import unicode_literals +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"} + + def setUp(self): + self.client = openphoto.OpenPhoto(host=self.TEST_HOST, + consumer_key="dummy", + consumer_secret="dummy", + token="dummy", + token_secret="dummy") + + def _register_uri(self, method, uri=TEST_URI, data=TEST_DATA, **kwds): + """Convenience wrapper around httpretty.register_uri""" + 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): + 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): + 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": "bar", "spam": "eggs"}) + self.assertEqual(self.client.last_response.json(), self.TEST_DATA) + + @httpretty.activate + def test_post_with_parameters(self): + self._register_uri(httpretty.POST) + response = self.client.post(self.TEST_ENDPOINT, + foo="bar", spam="eggs") + self.assertEqual(self._last_request().body, "foo=bar&spam=eggs") + self.assertEqual(response, self.TEST_DATA) + self.assertEqual(self.client.last_url, self.TEST_URI) + self.assertEqual(self.client.last_params, {"foo": "bar", "spam": "eggs"}) + self.assertEqual(self.client.last_response.json(), self.TEST_DATA) + + @httpretty.activate + def test_get_without_oauth(self): + 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_raises_exception(self): + 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): + 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): + 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): + 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"}) + response = 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.assertEqual(params["unicode_"], ["\xc3\xbcmlaut"]) From b9c947c94cd9dfb844bee0380c2e1a450e2bf46e Mon Sep 17 00:00:00 2001 From: sneakypete81 Date: Sun, 16 Jun 2013 12:09:24 +0100 Subject: [PATCH 02/26] Added more unit tests File post, API version selection, HTTP errors --- tests/unit/data/test_file.txt | 1 + tests/unit/test_http.py | 68 +++++++++++++++---- tests/unit/test_http_errors.py | 120 +++++++++++++++++++++++++++++++++ 3 files changed, 177 insertions(+), 12 deletions(-) create mode 100644 tests/unit/data/test_file.txt create mode 100644 tests/unit/test_http_errors.py diff --git a/tests/unit/data/test_file.txt b/tests/unit/data/test_file.txt new file mode 100644 index 0000000..4fff881 --- /dev/null +++ b/tests/unit/data/test_file.txt @@ -0,0 +1 @@ +Test File diff --git a/tests/unit/test_http.py b/tests/unit/test_http.py index 7fd18d2..971dd90 100644 --- a/tests/unit/test_http.py +++ b/tests/unit/test_http.py @@ -1,4 +1,5 @@ from __future__ import unicode_literals +import os import json import httpretty try: @@ -15,17 +16,21 @@ class TestHttp(unittest.TestCase): 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, - consumer_key="dummy", - consumer_secret="dummy", - token="dummy", - token_secret="dummy") + self.client = openphoto.OpenPhoto(host=self.TEST_HOST, **self.TEST_OAUTH) - def _register_uri(self, method, uri=TEST_URI, data=TEST_DATA, **kwds): + def _register_uri(self, method, uri=TEST_URI, data=TEST_DATA, body=None, + **kwds): """Convenience wrapper around httpretty.register_uri""" - body = json.dumps(data) + if body is None: + body = json.dumps(data) httpretty.register_uri(method, uri=uri, body=body, **kwds) @staticmethod @@ -94,11 +99,11 @@ class TestHttp(unittest.TestCase): photo = openphoto.objects.Photo(None, {"id": "photo_id"}) album = openphoto.objects.Album(None, {"id": "album_id"}) tag = openphoto.objects.Tag(None, {"id": "tag_id"}) - response = self.client.get(self.TEST_ENDPOINT, - photo=photo, album=album, tag=tag, - list_=[photo, album, tag], - boolean=True, - unicode_="\xfcmlaut") + 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"]) @@ -106,3 +111,42 @@ class TestHttp(unittest.TestCase): self.assertEqual(params["list_"], ["photo_id,album_id,tag_id"]) self.assertEqual(params["boolean"], ["1"]) self.assertEqual(params["unicode_"], ["\xc3\xbcmlaut"]) + + @httpretty.activate + def test_get_with_api_version(self): + 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): + 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): + 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 = self._last_request().body + self.assertIn("Content-Disposition: form-data; "+ + "name=\"file\"; filename=\"test_file.txt\"", body) + self.assertIn("Test File", body) + + + @httpretty.activate + def test_post_file_parameters_are_sent_as_querystring(self): + 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"]) diff --git a/tests/unit/test_http_errors.py b/tests/unit/test_http_errors.py new file mode 100644 index 0000000..80eec6d --- /dev/null +++ b/tests/unit/test_http_errors.py @@ -0,0 +1,120 @@ +from __future__ import unicode_literals +import json +import httpretty +try: + import unittest2 as unittest # Python2.6 +except ImportError: + import unittest + +import openphoto +from tests.unit.test_http import TestHttp + +class TestHttpErrors(TestHttp): + def _register_uri(self, method, uri=TestHttp.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_raises_openphoto_exception(self): + 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_raises_openphoto_exception(self): + 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_raises_404_exception(self): + self._register_uri(httpretty.GET, status=404) + with self.assertRaises(openphoto.OpenPhoto404Error): + response = 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_raises_404_exception(self): + self._register_uri(httpretty.POST, status=404) + with self.assertRaises(openphoto.OpenPhoto404Error): + response = self.client.post(self.TEST_ENDPOINT) + + @httpretty.activate + def test_get_with_invalid_json_raises_exception(self): + 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_raises_exception(self): + 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_raises_openphoto_exception(self): + self._register_uri(httpretty.GET, body="Invalid JSON", status=500) + with self.assertRaises(openphoto.OpenPhotoError): + response = self.client.get(self.TEST_ENDPOINT) + + @httpretty.activate + def test_post_with_error_status_and_invalid_json_raises_openphoto_exception(self): + self._register_uri(httpretty.POST, body="Invalid JSON", status=500) + with self.assertRaises(openphoto.OpenPhotoError): + response = self.client.post(self.TEST_ENDPOINT) + + @httpretty.activate + def test_get_with_404_status_and_invalid_json_raises_404_exception(self): + self._register_uri(httpretty.GET, body="Invalid JSON", status=404) + with self.assertRaises(openphoto.OpenPhoto404Error): + response = self.client.get(self.TEST_ENDPOINT) + + @httpretty.activate + def test_post_with_404_status_and_invalid_json_raises_404_exception(self): + self._register_uri(httpretty.POST, body="Invalid JSON", status=404) + with self.assertRaises(openphoto.OpenPhoto404Error): + response = self.client.post(self.TEST_ENDPOINT) + + @httpretty.activate + def test_get_with_duplicate_status_raises_duplicate_exception(self): + data = {"message": "This photo already exists", "code": 409} + self._register_uri(httpretty.GET, data=data, status=409) + with self.assertRaises(openphoto.OpenPhotoDuplicateError): + response = self.client.get(self.TEST_ENDPOINT) + + @httpretty.activate + def test_post_with_duplicate_status_raises_duplicate_exception(self): + data = {"message": "This photo already exists", "code": 409} + self._register_uri(httpretty.POST, data=data, status=409) + with self.assertRaises(openphoto.OpenPhotoDuplicateError): + response = 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_raises_openphoto_exception(self): + data = {"message": "Test Message", "code": 200} + self._register_uri(httpretty.GET, data=data, status=202) + with self.assertRaises(openphoto.OpenPhotoError): + response = 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_raises_openphoto_exception(self): + data = {"message": "Test Message", "code": 200} + self._register_uri(httpretty.POST, data=data, status=202) + with self.assertRaises(openphoto.OpenPhotoError): + response = self.client.post(self.TEST_ENDPOINT) + From c1c309bc9d7d605c65bb0cbeddd44874bf04e87c Mon Sep 17 00:00:00 2001 From: sneakypete81 Date: Sun, 16 Jun 2013 13:09:31 +0100 Subject: [PATCH 03/26] Started photo API unit tests --- tests/unit/test_photos.py | 104 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 tests/unit/test_photos.py diff --git a/tests/unit/test_photos.py b/tests/unit/test_photos.py new file mode 100644 index 0000000..bf87ab6 --- /dev/null +++ b/tests/unit/test_photos.py @@ -0,0 +1,104 @@ +from __future__ import unicode_literals +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_PHOTOS = [{"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) + + @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_list(self, mock_get): + photos = self.TEST_PHOTOS + mock_get.return_value = self._return_value(photos) + + 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): + @mock.patch.object(openphoto.OpenPhoto, 'post') + def test_update(self, mock_post): + 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_update_failure_raises_exception(self, mock_post): + mock_post.return_value = self._return_value(False) + with self.assertRaises(openphoto.OpenPhotoError): + self.client.photos.update(["1a", "2b"], title="Test") + +class TestPhotosDelete(TestPhotos): + @mock.patch.object(openphoto.OpenPhoto, 'post') + def test_delete(self, mock_post): + 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_delete_failure_raises_exception(self, mock_post): + mock_post.return_value = self._return_value(False) + with self.assertRaises(openphoto.OpenPhotoError): + self.client.photos.delete(["1a", "2b"]) + +class TestPhotoDelete(TestPhotos): + @mock.patch.object(openphoto.OpenPhoto, 'post') + def test_delete(self, mock_post): + 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_delete_failure_raises_exception(self, mock_post): + mock_post.return_value = self._return_value(False) + with self.assertRaises(openphoto.OpenPhotoError): + self.client.photo.delete("1a") + + @mock.patch.object(openphoto.OpenPhoto, 'post') + def test_object_delete(self, mock_post): + mock_post.return_value = self._return_value(True) + photo = openphoto.objects.Photo(self.client, self.TEST_PHOTOS[0]) + result = photo.delete() + 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_object_delete_failure_raises_exception(self, mock_post): + mock_post.return_value = self._return_value(False) + photo = openphoto.objects.Photo(self.client, self.TEST_PHOTOS[0]) + with self.assertRaises(openphoto.OpenPhotoError): + photo.delete() + From 98abec56979d44d518be491da6d73fbc9a876394 Mon Sep 17 00:00:00 2001 From: sneakypete81 Date: Sun, 16 Jun 2013 19:39:55 +0100 Subject: [PATCH 04/26] Additional photo API unit tests --- tests/unit/test_photos.py | 353 +++++++++++++++++++++++++++++++++----- 1 file changed, 306 insertions(+), 47 deletions(-) diff --git a/tests/unit/test_photos.py b/tests/unit/test_photos.py index bf87ab6..a942028 100644 --- a/tests/unit/test_photos.py +++ b/tests/unit/test_photos.py @@ -1,4 +1,6 @@ from __future__ import unicode_literals +import os +import base64 import mock try: import unittest2 as unittest # Python2.6 @@ -9,18 +11,15 @@ import openphoto class TestPhotos(unittest.TestCase): TEST_HOST = "test.example.com" - - TEST_PHOTOS = [{"id": "1a", - "tags": ["tag1", "tag2"], - "totalPages": 1, - "totalRows": 2}, - {"id": "2b", - "tags": ["tag3", "tag4"], - "totalPages": 1, - "totalRows": 2}] - + 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): @@ -28,12 +27,11 @@ class TestPhotos(unittest.TestCase): class TestPhotosList(TestPhotos): @mock.patch.object(openphoto.OpenPhoto, 'get') - def test_list(self, mock_get): - photos = self.TEST_PHOTOS - mock_get.return_value = self._return_value(photos) + def test_photos_list(self, mock): + mock.return_value = self._return_value(self.TEST_PHOTOS_DICT) result = self.client.photos.list() - mock_get.assert_called_with("/photos/list.json") + mock.assert_called_with("/photos/list.json") self.assertEqual(len(result), 2) self.assertEqual(result[0].id, "1a") self.assertEqual(result[0].tags, ["tag1", "tag2"]) @@ -41,64 +39,325 @@ class TestPhotosList(TestPhotos): 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_update(self, mock_post): - 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", + def test_photos_update(self, mock): + mock.return_value = self._return_value(True) + result = self.client.photos.update(self.TEST_PHOTOS, title="Test") + mock.assert_called_with("/photos/update.json", ids=["1a", "2b"], title="Test") self.assertEqual(result, True) @mock.patch.object(openphoto.OpenPhoto, 'post') - def test_update_failure_raises_exception(self, mock_post): - mock_post.return_value = self._return_value(False) - with self.assertRaises(openphoto.OpenPhotoError): - self.client.photos.update(["1a", "2b"], title="Test") - -class TestPhotosDelete(TestPhotos): - @mock.patch.object(openphoto.OpenPhoto, 'post') - def test_delete(self, mock_post): - 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"]) + def test_photos_update_ids(self, mock): + mock.return_value = self._return_value(True) + result = self.client.photos.update(["1a", "2b"], title="Test") + mock.assert_called_with("/photos/update.json", + ids=["1a", "2b"], title="Test") self.assertEqual(result, True) @mock.patch.object(openphoto.OpenPhoto, 'post') - def test_delete_failure_raises_exception(self, mock_post): - mock_post.return_value = self._return_value(False) + def test_photos_update_failure_raises_exception(self, mock): + mock.return_value = self._return_value(False) with self.assertRaises(openphoto.OpenPhotoError): - self.client.photos.delete(["1a", "2b"]) + 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): + mock.return_value = self._return_value(True) + result = self.client.photos.delete(self.TEST_PHOTOS) + mock.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): + mock.return_value = self._return_value(True) + result = self.client.photos.delete(["1a", "2b"]) + mock.assert_called_with("/photos/delete.json", ids=["1a", "2b"]) + self.assertEqual(result, True) + + @mock.patch.object(openphoto.OpenPhoto, 'post') + def test_photos_delete_failure_raises_exception(self, mock): + mock.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_delete(self, mock_post): - mock_post.return_value = self._return_value(True) + def test_photo_delete(self, mock): + mock.return_value = self._return_value(True) + result = self.client.photo.delete(self.TEST_PHOTOS[0]) + mock.assert_called_with("/photo/1a/delete.json") + self.assertEqual(result, True) + + @mock.patch.object(openphoto.OpenPhoto, 'post') + def test_photo_delete_id(self, mock): + mock.return_value = self._return_value(True) result = self.client.photo.delete("1a") - mock_post.assert_called_with("/photo/1a/delete.json") + mock.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_delete_failure_raises_exception(self, mock_post): - mock_post.return_value = self._return_value(False) + def test_photo_delete_failure_raises_exception(self, mock): + mock.return_value = self._return_value(False) with self.assertRaises(openphoto.OpenPhotoError): - self.client.photo.delete("1a") + self.client.photo.delete(self.TEST_PHOTOS[0]) @mock.patch.object(openphoto.OpenPhoto, 'post') - def test_object_delete(self, mock_post): - mock_post.return_value = self._return_value(True) - photo = openphoto.objects.Photo(self.client, self.TEST_PHOTOS[0]) - result = photo.delete() - mock_post.assert_called_with("/photo/1a/delete.json") + def test_photo_object_delete(self, mock): + mock.return_value = self._return_value(True) + result = self.TEST_PHOTOS[0].delete() + mock.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_object_delete_failure_raises_exception(self, mock_post): - mock_post.return_value = self._return_value(False) - photo = openphoto.objects.Photo(self.client, self.TEST_PHOTOS[0]) + def test_photo_object_delete_failure_raises_exception(self, mock): + mock.return_value = self._return_value(False) with self.assertRaises(openphoto.OpenPhotoError): - photo.delete() + self.TEST_PHOTOS[0].delete() +class TestPhotoEdit(TestPhotos): + @mock.patch.object(openphoto.OpenPhoto, 'get') + def test_photo_edit(self, mock): + mock.return_value = self._return_value({"markup": "
"}) + result = self.client.photo.edit(self.TEST_PHOTOS[0]) + mock.assert_called_with("/photo/1a/edit.json") + self.assertEqual(result, "") + + @mock.patch.object(openphoto.OpenPhoto, 'get') + def test_photo_edit_id(self, mock): + mock.return_value = self._return_value({"markup": ""}) + result = self.client.photo.edit("1a") + mock.assert_called_with("/photo/1a/edit.json") + self.assertEqual(result, "") + + @mock.patch.object(openphoto.OpenPhoto, 'get') + def test_photo_object_edit(self, mock): + mock.return_value = self._return_value({"markup": ""}) + result = self.TEST_PHOTOS[0].edit() + mock.assert_called_with("/photo/1a/edit.json") + self.assertEqual(result, "") + +class TestPhotoReplace(TestPhotos): + @mock.patch.object(openphoto.OpenPhoto, 'post') + def test_photo_replace(self, mock): + 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, mock): + with self.assertRaises(NotImplementedError): + self.client.photo.replace("1a", self.TEST_FILE) + + @mock.patch.object(openphoto.OpenPhoto, 'post') + def test_photo_object_replace(self, mock): + with self.assertRaises(NotImplementedError): + self.TEST_PHOTOS[0].replace(self.TEST_FILE) + + @mock.patch.object(openphoto.OpenPhoto, 'post') + def test_photo_replace_encoded(self, mock): + 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, mock): + 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, mock): + 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): + mock.return_value = self._return_value(self.TEST_PHOTOS_DICT[1]) + result = self.client.photo.update(self.TEST_PHOTOS[0], title="Test") + mock.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): + mock.return_value = self._return_value(self.TEST_PHOTOS_DICT[1]) + result = self.client.photo.update("1a", title="Test") + mock.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): + mock.return_value = self._return_value(self.TEST_PHOTOS_DICT[1]) + photo = self.TEST_PHOTOS[0] + photo.update(title="Test") + mock.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): + mock.return_value = self._return_value(self.TEST_PHOTOS_DICT[1]) + result = self.client.photo.view(self.TEST_PHOTOS[0], returnSizes="20x20") + mock.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): + mock.return_value = self._return_value(self.TEST_PHOTOS_DICT[1]) + result = self.client.photo.view("1a", returnSizes="20x20") + mock.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): + mock.return_value = self._return_value(self.TEST_PHOTOS_DICT[1]) + photo = self.TEST_PHOTOS[0] + photo.view(returnSizes="20x20") + mock.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): + mock.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.call_args[0] + title = mock.call_args[1]["title"] + files = mock.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): + encoded_file = base64.b64encode(open(self.TEST_FILE, "rb").read()) + mock.return_value = self._return_value(self.TEST_PHOTOS_DICT[0]) + result = self.client.photo.upload_encoded(self.TEST_FILE, title="Test") + mock.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, mock): + 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, mock): + with self.assertRaises(NotImplementedError): + self.client.photo.dynamic_url("1a") + + @mock.patch.object(openphoto.OpenPhoto, 'get') + def test_photo_object_dynamic_url(self, mock): + 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): + mock.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.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): + mock.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.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): + mock.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.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): + mock.return_value = self._return_value( + {"next": [self.TEST_PHOTOS_DICT[0]]}) + result = self.client.photo.next_previous(self.TEST_PHOTOS[0]) + mock.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): + mock.return_value = self._return_value( + {"previous": [self.TEST_PHOTOS_DICT[1]]}) + result = self.client.photo.next_previous(self.TEST_PHOTOS[0]) + mock.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): + mock.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.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_transform(self, mock): + mock.return_value = self._return_value(self.TEST_PHOTOS_DICT[1]) + result = self.client.photo.transform(self.TEST_PHOTOS[0], rotate="90") + mock.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_transform_id(self, mock): + mock.return_value = self._return_value(self.TEST_PHOTOS_DICT[1]) + result = self.client.photo.transform("1a", rotate="90") + mock.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_object_transform(self, mock): + mock.return_value = self._return_value(self.TEST_PHOTOS_DICT[1]) + photo = self.TEST_PHOTOS[0] + photo.transform(rotate="90") + mock.assert_called_with("/photo/1a/transform.json", rotate="90") + self.assertEqual(photo.get_fields(), self.TEST_PHOTOS_DICT[1]) From 75157a15e167455862630665873380c5c2be3e2e Mon Sep 17 00:00:00 2001 From: Pete Date: Wed, 26 Jun 2013 19:42:20 +0100 Subject: [PATCH 05/26] Add CLI testcases. --- tests/unit/test_cli.py | 95 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) create mode 100644 tests/unit/test_cli.py diff --git a/tests/unit/test_cli.py b/tests/unit/test_cli.py new file mode 100644 index 0000000..4733690 --- /dev/null +++ b/tests/unit/test_cli.py @@ -0,0 +1,95 @@ +import os +import sys +import mock +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") + def test_defaults(self, MockOpenPhoto): + get = MockOpenPhoto.return_value.get + main([]) + MockOpenPhoto.assert_called_with(config_file=None) + get.assert_called_with("/photos/list.json", process_response=False) + + @mock.patch.object(openphoto.main, "OpenPhoto") + def test_config(self, MockOpenPhoto): + main(["--config=test"]) + MockOpenPhoto.assert_called_with(config_file="test") + + @mock.patch.object(openphoto.main, "OpenPhoto") + def test_get(self, MockOpenPhoto): + get = MockOpenPhoto.return_value.get + get.return_value = "Result" + main(["-X", "GET", "-h", "test_host", "-e", "test_endpoint", "-F", + "field1=1", "-F", "field2=2"]) + MockOpenPhoto.assert_called_with(host="test_host") + get.assert_called_with("test_endpoint", field1="1", field2="2", + process_response=False) + # TODO: self.assertEq(mock_stdout.getvalue(), "Result") + + @mock.patch.object(openphoto.main, "OpenPhoto") + def test_post(self, MockOpenPhoto): + post = MockOpenPhoto.return_value.post + post.return_value = "Result" + main(["-X", "POST", "-h", "test_host", "-e", "test_endpoint", "-F", + "field1=1", "-F", "field2=2"]) + MockOpenPhoto.assert_called_with(host="test_host") + post.assert_called_with("test_endpoint", field1="1", field2="2", files={}, + process_response=False) + # TODO: self.assertEq(mock_stdout.getvalue(), "Result") + + @mock.patch.object(openphoto.main, "OpenPhoto") + def test_post_files(self, MockOpenPhoto): + post = MockOpenPhoto.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(files.keys(), ["photo"]) + self.assertEqual(files["photo"].name, self.TEST_FILE) + + @mock.patch.object(sys, "exit", raise_exception) + def test_unknown_arg(self): + with self.assertRaises(TestException): + main(["hello"]) + # TODO: self.assertIn(mock_stdout.getvalue(), "Error: Unknown argument") + + @mock.patch.object(sys, "exit", raise_exception) + def test_unknown_option(self): + with self.assertRaises(TestException): + main(["--hello"]) + # TODO: self.assertIn(mock_stdout.getvalue(), "Error: no such option") + + @mock.patch.object(sys, "exit", raise_exception) + def test_unknown_config(self): + with self.assertRaises(TestException): + main(["--config=this_config_doesnt_exist"]) + # TODO: self.assertIn(mock_stdout.getvalue(), "No such file or directory") + # TODO: self.assertIn(mock_stdout.getvalue(), "You must create a configuration file") + # TODO: self.assertIn(mock_stdout.getvalue(), "To get your credentials") + + @mock.patch.object(openphoto.main, "OpenPhoto") + def test_verbose(self, _): + main(["-v"]) + # TODO: self.assertIn(mock_stdout.getvalue(), "Method: GET") + # TODO: self.assertIn(mock_stdout.getvalue(), "Endpoint: /photos/list.json") + + @mock.patch.object(openphoto.main, "OpenPhoto") + def test_pretty_print(self, MockOpenPhoto): + get = MockOpenPhoto.return_value.get + get.return_value = '{"test":1}' + main(["-p"]) + # TODO: self.assertEq(mock_stdout.getvalue(), '{\n "test":1\n}") From 0c1a4149a19072de5383d9402e0f7404cebd742d Mon Sep 17 00:00:00 2001 From: sneakypete81 Date: Sat, 29 Jun 2013 10:49:15 +0100 Subject: [PATCH 06/26] Add album unit tests --- tests/unit/test_albums.py | 234 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 234 insertions(+) create mode 100644 tests/unit/test_albums.py diff --git a/tests/unit/test_albums.py b/tests/unit/test_albums.py new file mode 100644 index 0000000..4aad3e6 --- /dev/null +++ b/tests/unit/test_albums.py @@ -0,0 +1,234 @@ +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): + mock.return_value = self._return_value(self.TEST_ALBUMS_DICT) + result = self.client.albums.list() + mock.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): + mock.return_value = self._return_value(self.TEST_ALBUMS_DICT) + result = self.client.albums.list() + mock.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): + mock.return_value = self._return_value(self.TEST_ALBUMS_DICT[0]) + result = self.client.album.create(name="Test", foo="bar") + mock.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): + mock.return_value = self._return_value(True) + result = self.client.album.delete(self.TEST_ALBUMS[0]) + mock.assert_called_with("/album/1/delete.json") + self.assertEqual(result, True) + + @mock.patch.object(openphoto.OpenPhoto, 'post') + def test_album_delete_id(self, mock): + mock.return_value = self._return_value(True) + result = self.client.album.delete("1") + mock.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_raises_exception(self, mock): + mock.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): + mock.return_value = self._return_value(True) + album = self.TEST_ALBUMS[0] + result = album.delete() + mock.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_raises_exception(self, mock): + mock.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, mock): + with self.assertRaises(NotImplementedError): + self.client.album.form(self.TEST_ALBUMS[0]) + + @mock.patch.object(openphoto.OpenPhoto, 'post') + def test_album_form_id(self, mock): + with self.assertRaises(NotImplementedError): + self.client.album.form("1") + + @mock.patch.object(openphoto.OpenPhoto, 'post') + def test_album_object_form(self, mock): + with self.assertRaises(NotImplementedError): + self.TEST_ALBUMS[0].form() + +class TestAlbumAddPhotos(TestAlbums): + @mock.patch.object(openphoto.OpenPhoto, 'post') + def test_album_add_photos(self, mock): + 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, mock): + 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, mock): + 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, mock): + 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, mock): + 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, mock): + 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): + mock.return_value = self._return_value(self.TEST_ALBUMS_DICT[1]) + result = self.client.album.update(self.TEST_ALBUMS[0], name="Test") + mock.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): + mock.return_value = self._return_value(self.TEST_ALBUMS_DICT[1]) + result = self.client.album.update("1", name="Test") + mock.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): + mock.return_value = self._return_value(self.TEST_ALBUMS_DICT[1]) + album = self.TEST_ALBUMS[0] + album.update(name="Test") + mock.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): + mock.return_value = self._return_value(self.TEST_ALBUMS_DICT[1]) + result = self.client.album.view(self.TEST_ALBUMS[0], name="Test") + mock.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): + mock.return_value = self._return_value(self.TEST_ALBUMS_DICT[1]) + result = self.client.album.view("1", name="Test") + mock.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): + mock.return_value = self._return_value(self.TEST_ALBUMS_DICT[1]) + album = self.TEST_ALBUMS[0] + album.view(name="Test") + mock.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"]) + From e3e09bd2246b0c35338c298bc87f234f180c0525 Mon Sep 17 00:00:00 2001 From: sneakypete81 Date: Sat, 29 Jun 2013 10:50:52 +0100 Subject: [PATCH 07/26] Check that local object's fields are empty after photo is deleted --- tests/unit/test_photos.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/unit/test_photos.py b/tests/unit/test_photos.py index a942028..8b05052 100644 --- a/tests/unit/test_photos.py +++ b/tests/unit/test_photos.py @@ -109,12 +109,16 @@ class TestPhotoDelete(TestPhotos): 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): mock.return_value = self._return_value(True) - result = self.TEST_PHOTOS[0].delete() + photo = self.TEST_PHOTOS[0] + result = photo.delete() mock.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 From 32544878d0ced00a202aec9eb5200ed3dfe0ca5d Mon Sep 17 00:00:00 2001 From: sneakypete81 Date: Sat, 29 Jun 2013 10:53:08 +0100 Subject: [PATCH 08/26] Rename transform tests to be consistent --- tests/unit/test_photos.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/unit/test_photos.py b/tests/unit/test_photos.py index 8b05052..932cf1a 100644 --- a/tests/unit/test_photos.py +++ b/tests/unit/test_photos.py @@ -345,21 +345,21 @@ class TestPhotoNextPrevious(TestPhotos): class TestPhotoTransform(TestPhotos): @mock.patch.object(openphoto.OpenPhoto, 'post') - def test_transform(self, mock): + def test_photo_transform(self, mock): mock.return_value = self._return_value(self.TEST_PHOTOS_DICT[1]) result = self.client.photo.transform(self.TEST_PHOTOS[0], rotate="90") mock.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_transform_id(self, mock): + def test_photo_transform_id(self, mock): mock.return_value = self._return_value(self.TEST_PHOTOS_DICT[1]) result = self.client.photo.transform("1a", rotate="90") mock.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_object_transform(self, mock): + def test_photo_object_transform(self, mock): mock.return_value = self._return_value(self.TEST_PHOTOS_DICT[1]) photo = self.TEST_PHOTOS[0] photo.transform(rotate="90") From b22a04f07132f62ac0003ca48e66b3e40c6d8cd8 Mon Sep 17 00:00:00 2001 From: sneakypete81 Date: Sat, 29 Jun 2013 10:56:18 +0100 Subject: [PATCH 09/26] TestHttpErrors shouldn't inherit from TestHttp --- tests/unit/test_http_errors.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/tests/unit/test_http_errors.py b/tests/unit/test_http_errors.py index 80eec6d..b25f78c 100644 --- a/tests/unit/test_http_errors.py +++ b/tests/unit/test_http_errors.py @@ -7,10 +7,23 @@ except ImportError: import unittest import openphoto -from tests.unit.test_http import TestHttp -class TestHttpErrors(TestHttp): - def _register_uri(self, method, uri=TestHttp.TEST_URI, +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: From f083503b7a9c76ac56aa50a6390ece4539b2c265 Mon Sep 17 00:00:00 2001 From: sneakypete81 Date: Sat, 29 Jun 2013 11:19:02 +0100 Subject: [PATCH 10/26] Capture and check stdout/stderr during cli tests --- tests/unit/test_cli.py | 51 +++++++++++++++++++++++++----------------- 1 file changed, 31 insertions(+), 20 deletions(-) diff --git a/tests/unit/test_cli.py b/tests/unit/test_cli.py index 4733690..46f9f65 100644 --- a/tests/unit/test_cli.py +++ b/tests/unit/test_cli.py @@ -1,5 +1,6 @@ import os import sys +from StringIO import StringIO import mock try: import unittest2 as unittest # Python2.6 @@ -19,19 +20,22 @@ class TestCli(unittest.TestCase): TEST_FILE = os.path.join("tests", "unit", "data", "test_file.txt") @mock.patch.object(openphoto.main, "OpenPhoto") - def test_defaults(self, MockOpenPhoto): + @mock.patch('sys.stdout', new_callable=StringIO) + def test_defaults(self, _, MockOpenPhoto): get = MockOpenPhoto.return_value.get main([]) MockOpenPhoto.assert_called_with(config_file=None) get.assert_called_with("/photos/list.json", process_response=False) @mock.patch.object(openphoto.main, "OpenPhoto") - def test_config(self, MockOpenPhoto): + @mock.patch('sys.stdout', new_callable=StringIO) + def test_config(self, _, MockOpenPhoto): main(["--config=test"]) MockOpenPhoto.assert_called_with(config_file="test") @mock.patch.object(openphoto.main, "OpenPhoto") - def test_get(self, MockOpenPhoto): + @mock.patch('sys.stdout', new_callable=StringIO) + def test_get(self, mock_stdout, MockOpenPhoto): get = MockOpenPhoto.return_value.get get.return_value = "Result" main(["-X", "GET", "-h", "test_host", "-e", "test_endpoint", "-F", @@ -39,10 +43,11 @@ class TestCli(unittest.TestCase): MockOpenPhoto.assert_called_with(host="test_host") get.assert_called_with("test_endpoint", field1="1", field2="2", process_response=False) - # TODO: self.assertEq(mock_stdout.getvalue(), "Result") + self.assertEqual(mock_stdout.getvalue(), "Result\n") @mock.patch.object(openphoto.main, "OpenPhoto") - def test_post(self, MockOpenPhoto): + @mock.patch('sys.stdout', new_callable=StringIO) + def test_post(self, mock_stdout, MockOpenPhoto): post = MockOpenPhoto.return_value.post post.return_value = "Result" main(["-X", "POST", "-h", "test_host", "-e", "test_endpoint", "-F", @@ -50,10 +55,11 @@ class TestCli(unittest.TestCase): MockOpenPhoto.assert_called_with(host="test_host") post.assert_called_with("test_endpoint", field1="1", field2="2", files={}, process_response=False) - # TODO: self.assertEq(mock_stdout.getvalue(), "Result") + self.assertEqual(mock_stdout.getvalue(), "Result\n") @mock.patch.object(openphoto.main, "OpenPhoto") - def test_post_files(self, MockOpenPhoto): + @mock.patch('sys.stdout', new_callable=StringIO) + def test_post_files(self, _, MockOpenPhoto): post = MockOpenPhoto.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 @@ -62,34 +68,39 @@ class TestCli(unittest.TestCase): self.assertEqual(files["photo"].name, self.TEST_FILE) @mock.patch.object(sys, "exit", raise_exception) - def test_unknown_arg(self): + @mock.patch('sys.stderr', new_callable=StringIO) + def test_unknown_arg(self, mock_stderr): with self.assertRaises(TestException): main(["hello"]) - # TODO: self.assertIn(mock_stdout.getvalue(), "Error: Unknown argument") + self.assertIn("error: Unknown argument", mock_stderr.getvalue()) @mock.patch.object(sys, "exit", raise_exception) - def test_unknown_option(self): + @mock.patch('sys.stderr', new_callable=StringIO) + def test_unknown_option(self, mock_stderr): with self.assertRaises(TestException): main(["--hello"]) - # TODO: self.assertIn(mock_stdout.getvalue(), "Error: no such option") + self.assertIn("error: no such option", mock_stderr.getvalue()) @mock.patch.object(sys, "exit", raise_exception) - def test_unknown_config(self): + @mock.patch('sys.stdout', new_callable=StringIO) + def test_unknown_config(self, mock_stdout): with self.assertRaises(TestException): main(["--config=this_config_doesnt_exist"]) - # TODO: self.assertIn(mock_stdout.getvalue(), "No such file or directory") - # TODO: self.assertIn(mock_stdout.getvalue(), "You must create a configuration file") - # TODO: self.assertIn(mock_stdout.getvalue(), "To get your credentials") + 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") - def test_verbose(self, _): + @mock.patch('sys.stdout', new_callable=StringIO) + def test_verbose(self, mock_stdout, _): main(["-v"]) - # TODO: self.assertIn(mock_stdout.getvalue(), "Method: GET") - # TODO: self.assertIn(mock_stdout.getvalue(), "Endpoint: /photos/list.json") + self.assertIn("Method: GET", mock_stdout.getvalue()) + self.assertIn("Endpoint: /photos/list.json", mock_stdout.getvalue()) @mock.patch.object(openphoto.main, "OpenPhoto") - def test_pretty_print(self, MockOpenPhoto): + @mock.patch('sys.stdout', new_callable=StringIO) + def test_pretty_print(self, mock_stdout, MockOpenPhoto): get = MockOpenPhoto.return_value.get get.return_value = '{"test":1}' main(["-p"]) - # TODO: self.assertEq(mock_stdout.getvalue(), '{\n "test":1\n}") + self.assertEqual(mock_stdout.getvalue(), '{\n "test":1\n}\n') From 9319f903ac5c46ac1421a59fea7f48ca1262944d Mon Sep 17 00:00:00 2001 From: sneakypete81 Date: Sat, 29 Jun 2013 12:16:58 +0100 Subject: [PATCH 11/26] Added tag unit tests --- tests/unit/test_tags.py | 114 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 tests/unit/test_tags.py diff --git a/tests/unit/test_tags.py b/tests/unit/test_tags.py new file mode 100644 index 0000000..562092c --- /dev/null +++ b/tests/unit/test_tags.py @@ -0,0 +1,114 @@ +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_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): + mock.return_value = self._return_value(self.TEST_TAGS_DICT) + result = self.client.tags.list() + mock.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): + mock.return_value = self._return_value(self.TEST_TAGS_DICT[0]) + result = self.client.tag.create(tag="Test", foo="bar") + mock.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): + mock.return_value = self._return_value(True) + result = self.client.tag.delete(self.TEST_TAGS[0]) + mock.assert_called_with("/tag/tag1/delete.json") + self.assertEqual(result, True) + + @mock.patch.object(openphoto.OpenPhoto, 'post') + def test_tag_delete_id(self, mock): + mock.return_value = self._return_value(True) + result = self.client.tag.delete("tag1") + mock.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_raises_exception(self, mock): + mock.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): + mock.return_value = self._return_value(True) + tag = self.TEST_TAGS[0] + result = tag.delete() + mock.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_raises_exception(self, mock): + mock.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): + mock.return_value = self._return_value(self.TEST_TAGS_DICT[1]) + result = self.client.tag.update(self.TEST_TAGS[0], name="Test") + mock.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): + mock.return_value = self._return_value(self.TEST_TAGS_DICT[1]) + result = self.client.tag.update("tag1", name="Test") + mock.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): + mock.return_value = self._return_value(self.TEST_TAGS_DICT[1]) + tag = self.TEST_TAGS[0] + tag.update(name="Test") + mock.assert_called_with("/tag/tag1/update.json", name="Test") + self.assertEqual(tag.id, "tag2") + self.assertEqual(tag.count, 5) + From d3a40368172ed94746fe075955e6a738b79dd5d9 Mon Sep 17 00:00:00 2001 From: sneakypete81 Date: Sat, 29 Jun 2013 12:43:41 +0100 Subject: [PATCH 12/26] Reorganise tests into unit and functional categories. Tox (and hence Travis) only runs unit tests Functional tests can be run manually, with the default Python version --- run_tests => run_functional_tests | 11 +++++------ tests/{ => data}/test_photo1.jpg | Bin tests/{ => data}/test_photo2.jpg | Bin tests/{ => data}/test_photo3.jpg | Bin tests/{api_versions => functional}/__init__.py | 0 tests/functional/api_versions/__init__.py | 0 tests/{ => functional}/api_versions/test_v1.py | 2 +- tests/{ => functional}/api_versions/test_v2.py | 2 +- tests/{ => functional}/test_albums.py | 4 ++-- tests/{ => functional}/test_base.py | 6 +++--- tests/{ => functional}/test_framework.py | 6 +++--- tests/{ => functional}/test_photos.py | 12 ++++++------ tests/{ => functional}/test_tags.py | 6 +++--- tests/{ => unit}/test_config.py | 0 tox.ini | 9 +++++++-- 15 files changed, 31 insertions(+), 27 deletions(-) rename run_tests => run_functional_tests (65%) rename tests/{ => data}/test_photo1.jpg (100%) rename tests/{ => data}/test_photo2.jpg (100%) rename tests/{ => data}/test_photo3.jpg (100%) rename tests/{api_versions => functional}/__init__.py (100%) create mode 100644 tests/functional/api_versions/__init__.py rename tests/{ => functional}/api_versions/test_v1.py (74%) rename tests/{ => functional}/api_versions/test_v2.py (88%) rename tests/{ => functional}/test_albums.py (97%) rename tests/{ => functional}/test_base.py (96%) rename tests/{ => functional}/test_framework.py (93%) rename tests/{ => functional}/test_photos.py (94%) rename tests/{ => functional}/test_tags.py (96%) rename tests/{ => unit}/test_config.py (100%) diff --git a/run_tests b/run_functional_tests similarity index 65% rename from run_tests rename to run_functional_tests index b800038..5208de8 100755 --- a/run_tests +++ b/run_functional_tests @@ -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 diff --git a/tests/test_photo1.jpg b/tests/data/test_photo1.jpg similarity index 100% rename from tests/test_photo1.jpg rename to tests/data/test_photo1.jpg diff --git a/tests/test_photo2.jpg b/tests/data/test_photo2.jpg similarity index 100% rename from tests/test_photo2.jpg rename to tests/data/test_photo2.jpg diff --git a/tests/test_photo3.jpg b/tests/data/test_photo3.jpg similarity index 100% rename from tests/test_photo3.jpg rename to tests/data/test_photo3.jpg diff --git a/tests/api_versions/__init__.py b/tests/functional/__init__.py similarity index 100% rename from tests/api_versions/__init__.py rename to tests/functional/__init__.py diff --git a/tests/functional/api_versions/__init__.py b/tests/functional/api_versions/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/api_versions/test_v1.py b/tests/functional/api_versions/test_v1.py similarity index 74% rename from tests/api_versions/test_v1.py rename to tests/functional/api_versions/test_v1.py index 92baabb..aa3c652 100644 --- a/tests/api_versions/test_v1.py +++ b/tests/functional/api_versions/test_v1.py @@ -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 diff --git a/tests/api_versions/test_v2.py b/tests/functional/api_versions/test_v2.py similarity index 88% rename from tests/api_versions/test_v2.py rename to tests/functional/api_versions/test_v2.py index 545e647..a2c425c 100644 --- a/tests/api_versions/test_v2.py +++ b/tests/functional/api_versions/test_v2.py @@ -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") diff --git a/tests/test_albums.py b/tests/functional/test_albums.py similarity index 97% rename from tests/test_albums.py rename to tests/functional/test_albums.py index c48420e..95b28ab 100644 --- a/tests/test_albums.py +++ b/tests/functional/test_albums.py @@ -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): diff --git a/tests/test_base.py b/tests/functional/test_base.py similarity index 96% rename from tests/test_base.py rename to tests/functional/test_base.py index eb05caf..2c3926e 100644 --- a/tests/test_base.py +++ b/tests/functional/test_base.py @@ -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), ] diff --git a/tests/test_framework.py b/tests/functional/test_framework.py similarity index 93% rename from tests/test_framework.py rename to tests/functional/test_framework.py index 6d34f73..788ac13 100644 --- a/tests/test_framework.py +++ b/tests/functional/test_framework.py @@ -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") diff --git a/tests/test_photos.py b/tests/functional/test_photos.py similarity index 94% rename from tests/test_photos.py rename to tests/functional/test_photos.py index d0029a4..c6c9b57 100644 --- a/tests/test_photos.py +++ b/tests/functional/test_photos.py @@ -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 diff --git a/tests/test_tags.py b/tests/functional/test_tags.py similarity index 96% rename from tests/test_tags.py rename to tests/functional/test_tags.py index 40a577e..14ed6c0 100644 --- a/tests/test_tags.py +++ b/tests/functional/test_tags.py @@ -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"): diff --git a/tests/test_config.py b/tests/unit/test_config.py similarity index 100% rename from tests/test_config.py rename to tests/unit/test_config.py diff --git a/tox.ini b/tox.ini index 7f1f87f..5e5c469 100644 --- a/tox.ini +++ b/tox.ini @@ -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 + httpretty [testenv:py26] -commands = unit2 discover --catch +commands = unit2 discover --catch tests/unit deps = + mock + httpretty unittest2 discover From 2147b41383c1dc1ff12fb8585498e302e5ea410f Mon Sep 17 00:00:00 2001 From: sneakypete81 Date: Sat, 29 Jun 2013 13:17:33 +0100 Subject: [PATCH 13/26] Updated test documentation --- tests/README.markdown | 112 ++++++------------------------- tests/functional/README.markdown | 104 ++++++++++++++++++++++++++++ 2 files changed, 125 insertions(+), 91 deletions(-) create mode 100644 tests/functional/README.markdown diff --git a/tests/README.markdown b/tests/README.markdown index 84eb6ab..8eb88a0 100644 --- a/tests/README.markdown +++ b/tests/README.markdown @@ -1,101 +1,31 @@ -Tests for the Open Photo API / Python Library +Tests for the OpenPhoto/Trovebox Python Library ======================= -#### OpenPhoto, a photo service for the masses + +The openphoto-python tests are split into two categories: + +###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. ---------------------------------------- -### Requirements -A computer, Python and an empty OpenPhoto test host. +#### Requirements + * mock >= 1.0.0 + * httpretty >= 0.6.1 ---------------------------------------- - -### Setting up +#### Running the tests +To run the unit tests: -Create a ``~/.config/openphoto/test`` config file containing the following: + python -m unittest discover tests/unit - # ~/.config/openphoto/test - host = your.host.com - consumerKey = your_consumer_key - consumerSecret = your_consumer_secret - token = your_access_token - tokenSecret = your_access_token_secret +###Functional Tests -Make sure this is an empty test server, **not a production OpenPhoto server!!!** +The functional tests check that the openphoto-python library interoperates +correctly with a real OpenPhoto/Trovebox server. -You can specify an alternate test config file with the following environment variable: +They are slow to run, and rely on a stable HTTP connection to a test server. - export OPENPHOTO_TEST_CONFIG=test2 - ---------------------------------------- - -### 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 - ---------------------------------------- - -### 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 - - -### 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). diff --git a/tests/functional/README.markdown b/tests/functional/README.markdown new file mode 100644 index 0000000..7b0e6b4 --- /dev/null +++ b/tests/functional/README.markdown @@ -0,0 +1,104 @@ +Functional Tests for the OpenPhoto/Trovebox Python Library +======================= + +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. + +---------------------------------------- + +### Requirements +A computer, Python and an empty OpenPhoto/Trovebox test host. + +--------------------------------------- + +### 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 + +--------------------------------------- + +### 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 + +--------------------------------------- + +### 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 + + +### Full Regression Test + +The ``run_functional_tests`` script runs a full regression across +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 From 134409b8148ed94228a2b1462cc54b1e765f59b9 Mon Sep 17 00:00:00 2001 From: sneakypete81 Date: Sat, 29 Jun 2013 15:36:44 +0100 Subject: [PATCH 14/26] Update test readme file --- tests/README.markdown | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/tests/README.markdown b/tests/README.markdown index 8eb88a0..320b5b3 100644 --- a/tests/README.markdown +++ b/tests/README.markdown @@ -1,31 +1,29 @@ -Tests for the OpenPhoto/Trovebox Python Library +OpenPhoto/Trovebox Python Testing ======================= -The openphoto-python tests are split into two categories: - ###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. +They run very quickly and don't require any external test hosts. ----------------------------------------- #### Requirements * mock >= 1.0.0 * httpretty >= 0.6.1 -#### Running the tests -To run the unit tests: +#### Running the Unit Tests python -m unittest discover tests/unit +---------------------------------------- + ###Functional Tests The functional tests check that the openphoto-python library interoperates correctly with a real OpenPhoto/Trovebox server. -They are slow to run, and rely on a stable HTTP connection to a test server. +They are slow to run and rely on a stable HTTP connection to a test server. For full details, see the [functional test README file](functional/README.markdown). From a4f7a1194dc05a3cef4ff94be9c7ff8f7dc25f6a Mon Sep 17 00:00:00 2001 From: sneakypete81 Date: Sat, 29 Jun 2013 15:39:52 +0100 Subject: [PATCH 15/26] Update functional test README file --- tests/functional/README.markdown | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/functional/README.markdown b/tests/functional/README.markdown index 7b0e6b4..64601a8 100644 --- a/tests/functional/README.markdown +++ b/tests/functional/README.markdown @@ -1,4 +1,4 @@ -Functional Tests for the OpenPhoto/Trovebox Python Library +Functional Testing ======================= These functional tests check that the openphoto-python library interoperates @@ -93,7 +93,7 @@ For example, to restrict testing to APIv1 and APIv2: ### Full Regression Test -The ``run_functional_tests`` script runs a full regression across +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 From 5825a751c3a4a75c30150a5c20447bfe24037a88 Mon Sep 17 00:00:00 2001 From: sneakypete81 Date: Sat, 29 Jun 2013 17:02:12 +0100 Subject: [PATCH 16/26] Removed config secrets from travis setup, since we're now only running the unit tests --- .travis.yml | 10 +--------- .travis/run_travis | 19 ------------------- 2 files changed, 1 insertion(+), 28 deletions(-) delete mode 100755 .travis/run_travis diff --git a/.travis.yml b/.travis.yml index b03f4e1..ee74c8c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -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 -e py27 after_script: # Run Pylint diff --git a/.travis/run_travis b/.travis/run_travis deleted file mode 100755 index bfaee74..0000000 --- a/.travis/run_travis +++ /dev/null @@ -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 From 77fef49c6433a3e6041ffed63d584378a8e3a4c1 Mon Sep 17 00:00:00 2001 From: sneakypete81 Date: Sat, 29 Jun 2013 18:57:12 +0100 Subject: [PATCH 17/26] PyLint fixes --- tests/unit/test_albums.py | 164 +++++++------ tests/unit/test_cli.py | 56 +++-- tests/unit/test_config.py | 3 +- tests/unit/test_http.py | 114 +++++---- tests/unit/test_http_errors.py | 129 +++++++--- tests/unit/test_photos.py | 423 +++++++++++++++++++-------------- tests/unit/test_tags.py | 92 ++++--- 7 files changed, 593 insertions(+), 388 deletions(-) diff --git a/tests/unit/test_albums.py b/tests/unit/test_albums.py index 4aad3e6..a117bd5 100644 --- a/tests/unit/test_albums.py +++ b/tests/unit/test_albums.py @@ -8,8 +8,8 @@ except ImportError: import openphoto class TestAlbums(unittest.TestCase): - TEST_HOST = "test.example.com" - TEST_ALBUMS_DICT = [{"cover": {"id": "1a", "tags": ["tag1", "tag2"]}, + test_host = "test.example.com" + test_albums_dict = [{"cover": {"id": "1a", "tags": ["tag1", "tag2"]}, "id": "1", "name": "Album 1", "totalRows": 2}, @@ -18,9 +18,9 @@ class TestAlbums(unittest.TestCase): "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] + 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): @@ -28,10 +28,11 @@ class TestAlbums(unittest.TestCase): class TestAlbumsList(TestAlbums): @mock.patch.object(openphoto.OpenPhoto, 'get') - def test_albums_list(self, mock): - mock.return_value = self._return_value(self.TEST_ALBUMS_DICT) + 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.assert_called_with("/albums/list.json") + 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") @@ -41,10 +42,11 @@ class TestAlbumsList(TestAlbums): # 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): - mock.return_value = self._return_value(self.TEST_ALBUMS_DICT) + 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.assert_called_with("/albums/list.json") + 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") @@ -58,10 +60,12 @@ class TestAlbumsList(TestAlbums): class TestAlbumCreate(TestAlbums): # TODO: cover should be updated to Photo object @mock.patch.object(openphoto.OpenPhoto, 'post') - def test_album_create(self, mock): - mock.return_value = self._return_value(self.TEST_ALBUMS_DICT[0]) + 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.assert_called_with("/album/create.json", 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") @@ -69,34 +73,38 @@ class TestAlbumCreate(TestAlbums): class TestAlbumDelete(TestAlbums): @mock.patch.object(openphoto.OpenPhoto, 'post') - def test_album_delete(self, mock): - mock.return_value = self._return_value(True) - result = self.client.album.delete(self.TEST_ALBUMS[0]) - mock.assert_called_with("/album/1/delete.json") + 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): - mock.return_value = self._return_value(True) + 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.assert_called_with("/album/1/delete.json") + 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_raises_exception(self, mock): - mock.return_value = self._return_value(False) + 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]) + 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): - mock.return_value = self._return_value(True) - album = self.TEST_ALBUMS[0] + 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.assert_called_with("/album/1/delete.json") + mock_post.assert_called_with("/album/1/delete.json") self.assertEqual(result, True) self.assertEqual(album.get_fields(), {}) # self.assertEqual(album.id, None) @@ -105,70 +113,85 @@ class TestAlbumDelete(TestAlbums): # TODO: album.delete should raise exception on failure @unittest.expectedFailure @mock.patch.object(openphoto.OpenPhoto, 'post') - def test_album_object_delete_failure_raises_exception(self, mock): - mock.return_value = self._return_value(False) + 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() + self.test_albums[0].delete() class TestAlbumForm(TestAlbums): @mock.patch.object(openphoto.OpenPhoto, 'post') - def test_album_form(self, mock): + 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]) + self.client.album.form(self.test_albums[0]) @mock.patch.object(openphoto.OpenPhoto, 'post') - def test_album_form_id(self, mock): + 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, mock): + def test_album_object_form(self, _): + """ If album.form gets implemented, write a test! """ with self.assertRaises(NotImplementedError): - self.TEST_ALBUMS[0].form() + self.test_albums[0].form() class TestAlbumAddPhotos(TestAlbums): @mock.patch.object(openphoto.OpenPhoto, 'post') - def test_album_add_photos(self, mock): + 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"]) + self.client.album.add_photos(self.test_albums[0], ["Photo Objects"]) @mock.patch.object(openphoto.OpenPhoto, 'post') - def test_album_add_photos_id(self, mock): + 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, mock): + 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"]) + self.test_albums[0].add_photos(["Photo Objects"]) class TestAlbumRemovePhotos(TestAlbums): @mock.patch.object(openphoto.OpenPhoto, 'post') - def test_album_remove_photos(self, mock): + 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"]) + self.client.album.remove_photos(self.test_albums[0], + ["Photo Objects"]) @mock.patch.object(openphoto.OpenPhoto, 'post') - def test_album_remove_photos_id(self, mock): + 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, mock): + 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"]) + 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): - mock.return_value = self._return_value(self.TEST_ALBUMS_DICT[1]) - result = self.client.album.update(self.TEST_ALBUMS[0], name="Test") - mock.assert_called_with("/album/1/update.json", name="Test") + 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") @@ -176,10 +199,11 @@ class TestAlbumUpdate(TestAlbums): # TODO: cover should be updated to Photo object @mock.patch.object(openphoto.OpenPhoto, 'post') - def test_album_update_id(self, mock): - mock.return_value = self._return_value(self.TEST_ALBUMS_DICT[1]) + 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.assert_called_with("/album/1/update.json", 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") @@ -187,11 +211,12 @@ class TestAlbumUpdate(TestAlbums): # TODO: cover should be updated to Photo object @mock.patch.object(openphoto.OpenPhoto, 'post') - def test_album_object_update(self, mock): - mock.return_value = self._return_value(self.TEST_ALBUMS_DICT[1]) - album = self.TEST_ALBUMS[0] + 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.assert_called_with("/album/1/update.json", 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") @@ -200,10 +225,11 @@ class TestAlbumUpdate(TestAlbums): class TestAlbumView(TestAlbums): # TODO: cover should be updated to Photo object @mock.patch.object(openphoto.OpenPhoto, 'get') - def test_album_view(self, mock): - mock.return_value = self._return_value(self.TEST_ALBUMS_DICT[1]) - result = self.client.album.view(self.TEST_ALBUMS[0], name="Test") - mock.assert_called_with("/album/1/view.json", name="Test") + 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") @@ -211,10 +237,11 @@ class TestAlbumView(TestAlbums): # TODO: cover should be updated to Photo object @mock.patch.object(openphoto.OpenPhoto, 'get') - def test_album_view_id(self, mock): - mock.return_value = self._return_value(self.TEST_ALBUMS_DICT[1]) + 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.assert_called_with("/album/1/view.json", 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") @@ -222,11 +249,12 @@ class TestAlbumView(TestAlbums): # TODO: cover should be updated to Photo object @mock.patch.object(openphoto.OpenPhoto, 'get') - def test_album_object_view(self, mock): - mock.return_value = self._return_value(self.TEST_ALBUMS_DICT[1]) - album = self.TEST_ALBUMS[0] + 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.assert_called_with("/album/1/view.json", 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") diff --git a/tests/unit/test_cli.py b/tests/unit/test_cli.py index 46f9f65..07fce91 100644 --- a/tests/unit/test_cli.py +++ b/tests/unit/test_cli.py @@ -17,59 +17,66 @@ def raise_exception(_): raise TestException() class TestCli(unittest.TestCase): - TEST_FILE = os.path.join("tests", "unit", "data", "test_file.txt") + test_file = os.path.join("tests", "unit", "data", "test_file.txt") @mock.patch.object(openphoto.main, "OpenPhoto") @mock.patch('sys.stdout', new_callable=StringIO) - def test_defaults(self, _, MockOpenPhoto): - get = MockOpenPhoto.return_value.get + def test_defaults(self, _, mock_openphoto): + """Check that the default behaviour is correct""" + get = mock_openphoto.return_value.get main([]) - MockOpenPhoto.assert_called_with(config_file=None) + 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=StringIO) - def test_config(self, _, MockOpenPhoto): + def test_config(self, _, mock_openphoto): + """Check that a config file can be specified""" main(["--config=test"]) - MockOpenPhoto.assert_called_with(config_file="test") + mock_openphoto.assert_called_with(config_file="test") @mock.patch.object(openphoto.main, "OpenPhoto") @mock.patch('sys.stdout', new_callable=StringIO) - def test_get(self, mock_stdout, MockOpenPhoto): - get = MockOpenPhoto.return_value.get + 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"]) - MockOpenPhoto.assert_called_with(host="test_host") + 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=StringIO) - def test_post(self, mock_stdout, MockOpenPhoto): - post = MockOpenPhoto.return_value.post + 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"]) - MockOpenPhoto.assert_called_with(host="test_host") - post.assert_called_with("test_endpoint", field1="1", field2="2", files={}, - process_response=False) + 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=StringIO) - def test_post_files(self, _, MockOpenPhoto): - post = MockOpenPhoto.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 + 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(files.keys(), ["photo"]) - self.assertEqual(files["photo"].name, self.TEST_FILE) + self.assertEqual(files["photo"].name, self.test_file) @mock.patch.object(sys, "exit", raise_exception) @mock.patch('sys.stderr', new_callable=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()) @@ -77,6 +84,7 @@ class TestCli(unittest.TestCase): @mock.patch.object(sys, "exit", raise_exception) @mock.patch('sys.stderr', new_callable=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()) @@ -84,23 +92,27 @@ class TestCli(unittest.TestCase): @mock.patch.object(sys, "exit", raise_exception) @mock.patch('sys.stdout', new_callable=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("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=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=StringIO) - def test_pretty_print(self, mock_stdout, MockOpenPhoto): - get = MockOpenPhoto.return_value.get + 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') diff --git a/tests/unit/test_config.py b/tests/unit/test_config.py index 6e37ab2..9016e20 100644 --- a/tests/unit/test_config.py +++ b/tests/unit/test_config.py @@ -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) diff --git a/tests/unit/test_http.py b/tests/unit/test_http.py index 971dd90..ba092b7 100644 --- a/tests/unit/test_http.py +++ b/tests/unit/test_http.py @@ -10,25 +10,28 @@ except ImportError: 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", + 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", + test_oauth = {"consumer_key": "dummy", "consumer_secret": "dummy", "token": "dummy", "token_secret": "dummy"} - TEST_FILE = os.path.join("tests", "unit", "data", "test_file.txt") + 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) + self.client = openphoto.OpenPhoto(host=self.test_host, + **self.test_oauth) - def _register_uri(self, method, uri=TEST_URI, data=TEST_DATA, body=None, + 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) @@ -39,72 +42,82 @@ class TestHttp(unittest.TestCase): return httpretty.httpretty.last_request def test_attributes(self): - self.assertEqual(self.client.host, self.TEST_HOST) - self.assertEqual(self.client.config.host, self.TEST_HOST) + """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, + 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": "bar", "spam": "eggs"}) - self.assertEqual(self.client.last_response.json(), self.TEST_DATA) + self.assertEqual(response, self.test_data) + self.assertEqual(self.client.last_url, self.test_uri) + self.assertEqual(self.client.last_params, {"foo": "bar", + "spam": "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, + response = self.client.post(self.test_endpoint, foo="bar", spam="eggs") self.assertEqual(self._last_request().body, "foo=bar&spam=eggs") - self.assertEqual(response, self.TEST_DATA) - self.assertEqual(self.client.last_url, self.TEST_URI) - self.assertEqual(self.client.last_params, {"foo": "bar", "spam": "eggs"}) - self.assertEqual(self.client.last_response.json(), self.TEST_DATA) + self.assertEqual(response, self.test_data) + self.assertEqual(self.client.last_url, self.test_uri) + self.assertEqual(self.client.last_params, {"foo": "bar", + "spam": "eggs"}) + self.assertEqual(self.client.last_response.json(), self.test_data) @httpretty.activate def test_get_without_oauth(self): - self.client = openphoto.OpenPhoto(host=self.TEST_HOST) + """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) + response = self.client.get(self.test_endpoint) self.assertNotIn("authorization", self._last_request().headers) - self.assertEqual(response, self.TEST_DATA) + self.assertEqual(response, self.test_data) @httpretty.activate - def test_post_without_oauth_raises_exception(self): - self.client = openphoto.OpenPhoto(host=self.TEST_HOST) + 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) + 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)) + 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)) + 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, + 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 + params = self._last_request().querystring self.assertEqual(params["photo"], ["photo_id"]) self.assertEqual(params["album"], ["album_id"]) self.assertEqual(params["tag"], ["tag_id"]) @@ -114,28 +127,31 @@ class TestHttp(unittest.TestCase): @httpretty.activate def test_get_with_api_version(self): - self.client = openphoto.OpenPhoto(host=self.TEST_HOST, api_version=1) + """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) + 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): - self.client = openphoto.OpenPhoto(host=self.TEST_HOST, api_version=1, - **self.TEST_OAUTH) + """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) + 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, + 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) + self.assertEqual(response, self.test_data) body = self._last_request().body self.assertIn("Content-Disposition: form-data; "+ "name=\"file\"; filename=\"test_file.txt\"", body) @@ -144,9 +160,13 @@ class TestHttp(unittest.TestCase): @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", + 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(response, self.test_data) self.assertEqual(self._last_request().querystring["foo"], ["bar"]) diff --git a/tests/unit/test_http_errors.py b/tests/unit/test_http_errors.py index b25f78c..ddd25ec 100644 --- a/tests/unit/test_http_errors.py +++ b/tests/unit/test_http_errors.py @@ -9,25 +9,26 @@ except ImportError: 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", + 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", + 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) + self.client = openphoto.OpenPhoto(host=self.test_host, + **self.test_oauth) - def _register_uri(self, method, uri=TEST_URI, + 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 + data = self.test_data # Set the JSON return code to match the HTTP status data["code"] = status if body is None: @@ -36,98 +37,154 @@ class TestHttpErrors(unittest.TestCase): **kwds) @httpretty.activate - def test_get_with_error_status_raises_openphoto_exception(self): + 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) + self.client.get(self.test_endpoint) @httpretty.activate - def test_post_with_error_status_raises_openphoto_exception(self): + 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) + 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_raises_404_exception(self): + 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): - response = self.client.get(self.TEST_ENDPOINT) + 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_raises_404_exception(self): + 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): - response = self.client.post(self.TEST_ENDPOINT) + self.client.post(self.test_endpoint) @httpretty.activate - def test_get_with_invalid_json_raises_exception(self): + 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) + self.client.get(self.test_endpoint) @httpretty.activate - def test_post_with_invalid_json_raises_exception(self): + 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) + self.client.post(self.test_endpoint) @httpretty.activate - def test_get_with_error_status_and_invalid_json_raises_openphoto_exception(self): + 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): - response = self.client.get(self.TEST_ENDPOINT) + self.client.get(self.test_endpoint) @httpretty.activate - def test_post_with_error_status_and_invalid_json_raises_openphoto_exception(self): + 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): - response = self.client.post(self.TEST_ENDPOINT) + self.client.post(self.test_endpoint) @httpretty.activate - def test_get_with_404_status_and_invalid_json_raises_404_exception(self): + 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): - response = self.client.get(self.TEST_ENDPOINT) + self.client.get(self.test_endpoint) @httpretty.activate - def test_post_with_404_status_and_invalid_json_raises_404_exception(self): + 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): - response = self.client.post(self.TEST_ENDPOINT) + self.client.post(self.test_endpoint) @httpretty.activate - def test_get_with_duplicate_status_raises_duplicate_exception(self): + 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): - response = self.client.get(self.TEST_ENDPOINT) + self.client.get(self.test_endpoint) @httpretty.activate - def test_post_with_duplicate_status_raises_duplicate_exception(self): + 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): - response = self.client.post(self.TEST_ENDPOINT) + 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_raises_openphoto_exception(self): + 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): - response = self.client.get(self.TEST_ENDPOINT) + 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_raises_openphoto_exception(self): + 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): - response = self.client.post(self.TEST_ENDPOINT) + self.client.post(self.test_endpoint) diff --git a/tests/unit/test_photos.py b/tests/unit/test_photos.py index 932cf1a..8e55664 100644 --- a/tests/unit/test_photos.py +++ b/tests/unit/test_photos.py @@ -10,16 +10,16 @@ except ImportError: 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"], + 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] + 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): @@ -27,11 +27,12 @@ class TestPhotos(unittest.TestCase): class TestPhotosList(TestPhotos): @mock.patch.object(openphoto.OpenPhoto, 'get') - def test_photos_list(self, mock): - mock.return_value = self._return_value(self.TEST_PHOTOS_DICT) + 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.assert_called_with("/photos/list.json") + 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"]) @@ -42,80 +43,99 @@ 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): - mock.return_value = self._return_value(True) - result = self.client.photos.update(self.TEST_PHOTOS, title="Test") - mock.assert_called_with("/photos/update.json", + 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): - mock.return_value = self._return_value(True) + 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.assert_called_with("/photos/update.json", + 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_raises_exception(self, mock): - mock.return_value = self._return_value(False) + 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") + 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): - mock.return_value = self._return_value(True) - result = self.client.photos.delete(self.TEST_PHOTOS) - mock.assert_called_with("/photos/delete.json", ids=["1a", "2b"]) + 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): - mock.return_value = self._return_value(True) + 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.assert_called_with("/photos/delete.json", ids=["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_raises_exception(self, mock): - mock.return_value = self._return_value(False) + 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) + self.client.photos.delete(self.test_photos) class TestPhotoDelete(TestPhotos): @mock.patch.object(openphoto.OpenPhoto, 'post') - def test_photo_delete(self, mock): - mock.return_value = self._return_value(True) - result = self.client.photo.delete(self.TEST_PHOTOS[0]) - mock.assert_called_with("/photo/1a/delete.json") + 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): - mock.return_value = self._return_value(True) + 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.assert_called_with("/photo/1a/delete.json") + 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_raises_exception(self, mock): - mock.return_value = self._return_value(False) + 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]) + 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): - mock.return_value = self._return_value(True) - photo = self.TEST_PHOTOS[0] + 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.assert_called_with("/photo/1a/delete.json") + mock_post.assert_called_with("/photo/1a/delete.json") self.assertEqual(result, True) self.assertEqual(photo.get_fields(), {}) # self.assertEqual(photo.id, None) @@ -123,245 +143,298 @@ class TestPhotoDelete(TestPhotos): # TODO: photo.delete should raise exception on failure @unittest.expectedFailure @mock.patch.object(openphoto.OpenPhoto, 'post') - def test_photo_object_delete_failure_raises_exception(self, mock): - mock.return_value = self._return_value(False) + 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() + self.test_photos[0].delete() class TestPhotoEdit(TestPhotos): @mock.patch.object(openphoto.OpenPhoto, 'get') - def test_photo_edit(self, mock): - mock.return_value = self._return_value({"markup": ""}) - result = self.client.photo.edit(self.TEST_PHOTOS[0]) - mock.assert_called_with("/photo/1a/edit.json") + def test_photo_edit(self, mock_get): + """Check that a the photo edit endpoint is working""" + mock_get.return_value = self._return_value({"markup": ""}) + result = self.client.photo.edit(self.test_photos[0]) + mock_get.assert_called_with("/photo/1a/edit.json") self.assertEqual(result, "") @mock.patch.object(openphoto.OpenPhoto, 'get') - def test_photo_edit_id(self, mock): - mock.return_value = self._return_value({"markup": ""}) + 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": ""}) result = self.client.photo.edit("1a") - mock.assert_called_with("/photo/1a/edit.json") + mock_get.assert_called_with("/photo/1a/edit.json") self.assertEqual(result, "") @mock.patch.object(openphoto.OpenPhoto, 'get') - def test_photo_object_edit(self, mock): - mock.return_value = self._return_value({"markup": ""}) - result = self.TEST_PHOTOS[0].edit() - mock.assert_called_with("/photo/1a/edit.json") + 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": ""}) + result = self.test_photos[0].edit() + mock_get.assert_called_with("/photo/1a/edit.json") self.assertEqual(result, "") class TestPhotoReplace(TestPhotos): @mock.patch.object(openphoto.OpenPhoto, 'post') - def test_photo_replace(self, mock): + 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) + self.client.photo.replace(self.test_photos[0], self.test_file) @mock.patch.object(openphoto.OpenPhoto, 'post') - def test_photo_replace_id(self, mock): + 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) + self.client.photo.replace("1a", self.test_file) @mock.patch.object(openphoto.OpenPhoto, 'post') - def test_photo_object_replace(self, mock): + 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) + self.test_photos[0].replace(self.test_file) @mock.patch.object(openphoto.OpenPhoto, 'post') - def test_photo_replace_encoded(self, mock): + 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) + 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, mock): + 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) + 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, mock): + 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) + 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): - mock.return_value = self._return_value(self.TEST_PHOTOS_DICT[1]) - result = self.client.photo.update(self.TEST_PHOTOS[0], title="Test") - mock.assert_called_with("/photo/1a/update.json", title="Test") - self.assertEqual(result.get_fields(), self.TEST_PHOTOS_DICT[1]) + 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): - mock.return_value = self._return_value(self.TEST_PHOTOS_DICT[1]) + 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.assert_called_with("/photo/1a/update.json", title="Test") - self.assertEqual(result.get_fields(), self.TEST_PHOTOS_DICT[1]) + 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): - mock.return_value = self._return_value(self.TEST_PHOTOS_DICT[1]) - photo = self.TEST_PHOTOS[0] + 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.assert_called_with("/photo/1a/update.json", title="Test") - self.assertEqual(photo.get_fields(), self.TEST_PHOTOS_DICT[1]) + 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): - mock.return_value = self._return_value(self.TEST_PHOTOS_DICT[1]) - result = self.client.photo.view(self.TEST_PHOTOS[0], returnSizes="20x20") - mock.assert_called_with("/photo/1a/view.json", returnSizes="20x20") - self.assertEqual(result.get_fields(), self.TEST_PHOTOS_DICT[1]) + 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): - mock.return_value = self._return_value(self.TEST_PHOTOS_DICT[1]) + 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.assert_called_with("/photo/1a/view.json", returnSizes="20x20") - self.assertEqual(result.get_fields(), self.TEST_PHOTOS_DICT[1]) + 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): - mock.return_value = self._return_value(self.TEST_PHOTOS_DICT[1]) - photo = self.TEST_PHOTOS[0] + 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.assert_called_with("/photo/1a/view.json", returnSizes="20x20") - self.assertEqual(photo.get_fields(), self.TEST_PHOTOS_DICT[1]) + 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): - mock.return_value = self._return_value(self.TEST_PHOTOS_DICT[0]) - result = self.client.photo.upload(self.TEST_FILE, title="Test") + 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.call_args[0] - title = mock.call_args[1]["title"] - files = mock.call_args[1]["files"] + 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]) + self.assertEqual(result.get_fields(), self.test_photos_dict[0]) @mock.patch.object(openphoto.OpenPhoto, 'post') - def test_photo_upload_encoded(self, mock): - encoded_file = base64.b64encode(open(self.TEST_FILE, "rb").read()) - mock.return_value = self._return_value(self.TEST_PHOTOS_DICT[0]) - result = self.client.photo.upload_encoded(self.TEST_FILE, title="Test") - mock.assert_called_with("/photo/upload.json", - photo=encoded_file, title="Test") - self.assertEqual(result.get_fields(), self.TEST_PHOTOS_DICT[0]) + def test_photo_upload_encoded(self, mock_post): + """Check that a photo can be uploaded using Base64 encoding""" + encoded_file = base64.b64encode(open(self.test_file, "rb").read()) + mock_post.return_value = self._return_value(self.test_photos_dict[0]) + result = self.client.photo.upload_encoded(self.test_file, title="Test") + 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, mock): + 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]) + self.client.photo.dynamic_url(self.test_photos[0]) @mock.patch.object(openphoto.OpenPhoto, 'get') - def test_photo_dynamic_url_id(self, mock): + 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, mock): + 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() + self.test_photos[0].dynamic_url() class TestPhotoNextPrevious(TestPhotos): @mock.patch.object(openphoto.OpenPhoto, 'get') - def test_photo_next_previous(self, mock): - mock.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.assert_called_with("/photo/1a/nextprevious.json") + 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.test_photos_dict[0]) self.assertEqual(result["previous"][0].get_fields(), - self.TEST_PHOTOS_DICT[1]) + self.test_photos_dict[1]) @mock.patch.object(openphoto.OpenPhoto, 'get') - def test_photo_next_previous_id(self, mock): - mock.return_value = self._return_value( - {"next": [self.TEST_PHOTOS_DICT[0]], - "previous": [self.TEST_PHOTOS_DICT[1]]}) + 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.assert_called_with("/photo/1a/nextprevious.json") + mock_get.assert_called_with("/photo/1a/nextprevious.json") self.assertEqual(result["next"][0].get_fields(), - self.TEST_PHOTOS_DICT[0]) + self.test_photos_dict[0]) self.assertEqual(result["previous"][0].get_fields(), - self.TEST_PHOTOS_DICT[1]) + self.test_photos_dict[1]) @mock.patch.object(openphoto.OpenPhoto, 'get') - def test_photo_object_next_previous(self, mock): - mock.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.assert_called_with("/photo/1a/nextprevious.json") + 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.test_photos_dict[0]) self.assertEqual(result["previous"][0].get_fields(), - self.TEST_PHOTOS_DICT[1]) + self.test_photos_dict[1]) @mock.patch.object(openphoto.OpenPhoto, 'get') - def test_photo_next(self, mock): - mock.return_value = self._return_value( - {"next": [self.TEST_PHOTOS_DICT[0]]}) - result = self.client.photo.next_previous(self.TEST_PHOTOS[0]) - mock.assert_called_with("/photo/1a/nextprevious.json") + 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.test_photos_dict[0]) self.assertNotIn("previous", result) @mock.patch.object(openphoto.OpenPhoto, 'get') - def test_photo_previous(self, mock): - mock.return_value = self._return_value( - {"previous": [self.TEST_PHOTOS_DICT[1]]}) - result = self.client.photo.next_previous(self.TEST_PHOTOS[0]) - mock.assert_called_with("/photo/1a/nextprevious.json") + 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.test_photos_dict[1]) self.assertNotIn("next", result) @mock.patch.object(openphoto.OpenPhoto, 'get') - def test_photo_multiple_next_previous(self, mock): - mock.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.assert_called_with("/photo/1a/nextprevious.json") + 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.test_photos_dict[0]) self.assertEqual(result["next"][1].get_fields(), - self.TEST_PHOTOS_DICT[0]) + self.test_photos_dict[0]) self.assertEqual(result["previous"][0].get_fields(), - self.TEST_PHOTOS_DICT[1]) + self.test_photos_dict[1]) self.assertEqual(result["previous"][1].get_fields(), - self.TEST_PHOTOS_DICT[1]) + self.test_photos_dict[1]) class TestPhotoTransform(TestPhotos): @mock.patch.object(openphoto.OpenPhoto, 'post') - def test_photo_transform(self, mock): - mock.return_value = self._return_value(self.TEST_PHOTOS_DICT[1]) - result = self.client.photo.transform(self.TEST_PHOTOS[0], rotate="90") - mock.assert_called_with("/photo/1a/transform.json", rotate="90") - self.assertEqual(result.get_fields(), self.TEST_PHOTOS_DICT[1]) + 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): - mock.return_value = self._return_value(self.TEST_PHOTOS_DICT[1]) + 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.assert_called_with("/photo/1a/transform.json", rotate="90") - self.assertEqual(result.get_fields(), self.TEST_PHOTOS_DICT[1]) + 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): - mock.return_value = self._return_value(self.TEST_PHOTOS_DICT[1]) - photo = self.TEST_PHOTOS[0] + 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.assert_called_with("/photo/1a/transform.json", rotate="90") - self.assertEqual(photo.get_fields(), self.TEST_PHOTOS_DICT[1]) + mock_post.assert_called_with("/photo/1a/transform.json", rotate="90") + self.assertEqual(photo.get_fields(), self.test_photos_dict[1]) diff --git a/tests/unit/test_tags.py b/tests/unit/test_tags.py index 562092c..76fac62 100644 --- a/tests/unit/test_tags.py +++ b/tests/unit/test_tags.py @@ -8,14 +8,15 @@ except ImportError: import openphoto class TestTags(unittest.TestCase): - TEST_HOST = "test.example.com" - TEST_TAGS_DICT = [{"count": 11, "id":"tag1"}, + 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] + 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): @@ -23,10 +24,11 @@ class TestTags(unittest.TestCase): class TestTagsList(TestTags): @mock.patch.object(openphoto.OpenPhoto, 'get') - def test_tags_list(self, mock): - mock.return_value = self._return_value(self.TEST_TAGS_DICT) + 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.assert_called_with("/tags/list.json") + 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) @@ -37,43 +39,48 @@ 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): - mock.return_value = self._return_value(self.TEST_TAGS_DICT[0]) + 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.assert_called_with("/tag/create.json", 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): - mock.return_value = self._return_value(True) - result = self.client.tag.delete(self.TEST_TAGS[0]) - mock.assert_called_with("/tag/tag1/delete.json") + 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): - mock.return_value = self._return_value(True) + 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.assert_called_with("/tag/tag1/delete.json") + 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_raises_exception(self, mock): - mock.return_value = self._return_value(False) + 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]) + 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): - mock.return_value = self._return_value(True) - tag = self.TEST_TAGS[0] + 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.assert_called_with("/tag/tag1/delete.json") + mock_post.assert_called_with("/tag/tag1/delete.json") self.assertEqual(result, True) self.assertEqual(tag.get_fields(), {}) # self.assertEqual(tag.id, None) @@ -81,34 +88,41 @@ class TestTagDelete(TestTags): # TODO: tag.delete should raise exception on failure @unittest.expectedFailure @mock.patch.object(openphoto.OpenPhoto, 'post') - def test_tag_object_delete_failure_raises_exception(self, mock): - mock.return_value = self._return_value(False) + 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() + self.test_tags[0].delete() class TestTagUpdate(TestTags): @mock.patch.object(openphoto.OpenPhoto, 'post') - def test_tag_update(self, mock): - mock.return_value = self._return_value(self.TEST_TAGS_DICT[1]) - result = self.client.tag.update(self.TEST_TAGS[0], name="Test") - mock.assert_called_with("/tag/tag1/update.json", name="Test") + 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): - mock.return_value = self._return_value(self.TEST_TAGS_DICT[1]) + 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.assert_called_with("/tag/tag1/update.json", 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): - mock.return_value = self._return_value(self.TEST_TAGS_DICT[1]) - tag = self.TEST_TAGS[0] + 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.assert_called_with("/tag/tag1/update.json", name="Test") + mock_post.assert_called_with("/tag/tag1/update.json", name="Test") self.assertEqual(tag.id, "tag2") self.assertEqual(tag.count, 5) From 7359c1ae5aaa863aa145b1a422cd69a97aba51ed Mon Sep 17 00:00:00 2001 From: sneakypete81 Date: Sat, 29 Jun 2013 20:11:09 +0100 Subject: [PATCH 18/26] Ensure files are closed properly, to avoid Python3 warning --- openphoto/main.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openphoto/main.py b/openphoto/main.py index 4849625..59a41a7 100644 --- a/openphoto/main.py +++ b/openphoto/main.py @@ -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" % From e662f32cc3290b7c58c11494ad542032a09d6cd0 Mon Sep 17 00:00:00 2001 From: sneakypete81 Date: Sat, 29 Jun 2013 20:23:31 +0100 Subject: [PATCH 19/26] Ensure the test file is closed after use to prevent Python3 warnings --- tests/unit/test_photos.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/unit/test_photos.py b/tests/unit/test_photos.py index 8e55664..bf783fe 100644 --- a/tests/unit/test_photos.py +++ b/tests/unit/test_photos.py @@ -299,11 +299,12 @@ class TestPhotoUpload(TestPhotos): @mock.patch.object(openphoto.OpenPhoto, 'post') def test_photo_upload_encoded(self, mock_post): """Check that a photo can be uploaded using Base64 encoding""" - encoded_file = base64.b64encode(open(self.test_file, "rb").read()) mock_post.return_value = self._return_value(self.test_photos_dict[0]) result = self.client.photo.upload_encoded(self.test_file, title="Test") - mock_post.assert_called_with("/photo/upload.json", - photo=encoded_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): From af4260937f8c42e11661e288c250a70eb0bbdb78 Mon Sep 17 00:00:00 2001 From: sneakypete81 Date: Sat, 29 Jun 2013 20:24:12 +0100 Subject: [PATCH 20/26] Unit test fixes for Python3 support --- tests/unit/test_cli.py | 28 ++++++++++++++++------------ tests/unit/test_http.py | 17 +++++++++-------- 2 files changed, 25 insertions(+), 20 deletions(-) diff --git a/tests/unit/test_cli.py b/tests/unit/test_cli.py index 07fce91..c33c47b 100644 --- a/tests/unit/test_cli.py +++ b/tests/unit/test_cli.py @@ -1,7 +1,11 @@ +from __future__ import unicode_literals import os import sys -from StringIO import StringIO import mock +try: + import StringIO as io # Python2 +except ImportError: + import io # Python3 try: import unittest2 as unittest # Python2.6 except ImportError: @@ -20,7 +24,7 @@ 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=StringIO) + @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 @@ -29,14 +33,14 @@ class TestCli(unittest.TestCase): get.assert_called_with("/photos/list.json", process_response=False) @mock.patch.object(openphoto.main, "OpenPhoto") - @mock.patch('sys.stdout', new_callable=StringIO) + @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=StringIO) + @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 @@ -49,7 +53,7 @@ class TestCli(unittest.TestCase): self.assertEqual(mock_stdout.getvalue(), "Result\n") @mock.patch.object(openphoto.main, "OpenPhoto") - @mock.patch('sys.stdout', new_callable=StringIO) + @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 @@ -62,7 +66,7 @@ class TestCli(unittest.TestCase): self.assertEqual(mock_stdout.getvalue(), "Result\n") @mock.patch.object(openphoto.main, "OpenPhoto") - @mock.patch('sys.stdout', new_callable=StringIO) + @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 @@ -70,11 +74,11 @@ class TestCli(unittest.TestCase): # It's not possible to directly compare the file object, # so check it manually files = post.call_args[1]["files"] - self.assertEqual(files.keys(), ["photo"]) + 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=StringIO) + @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): @@ -82,7 +86,7 @@ class TestCli(unittest.TestCase): self.assertIn("error: Unknown argument", mock_stderr.getvalue()) @mock.patch.object(sys, "exit", raise_exception) - @mock.patch('sys.stderr', new_callable=StringIO) + @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): @@ -90,7 +94,7 @@ class TestCli(unittest.TestCase): self.assertIn("error: no such option", mock_stderr.getvalue()) @mock.patch.object(sys, "exit", raise_exception) - @mock.patch('sys.stdout', new_callable=StringIO) + @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): @@ -101,7 +105,7 @@ class TestCli(unittest.TestCase): self.assertIn("To get your credentials", mock_stdout.getvalue()) @mock.patch.object(openphoto.main, "OpenPhoto") - @mock.patch('sys.stdout', new_callable=StringIO) + @mock.patch('sys.stdout', new_callable=io.StringIO) def test_verbose(self, mock_stdout, _): """Check that the verbose option is working""" main(["-v"]) @@ -109,7 +113,7 @@ class TestCli(unittest.TestCase): self.assertIn("Endpoint: /photos/list.json", mock_stdout.getvalue()) @mock.patch.object(openphoto.main, "OpenPhoto") - @mock.patch('sys.stdout', new_callable=StringIO) + @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 diff --git a/tests/unit/test_http.py b/tests/unit/test_http.py index ba092b7..899d24f 100644 --- a/tests/unit/test_http.py +++ b/tests/unit/test_http.py @@ -57,8 +57,8 @@ class TestHttp(unittest.TestCase): 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": "bar", - "spam": "eggs"}) + self.assertEqual(self.client.last_params, {"foo": b"bar", + "spam": b"eggs"}) self.assertEqual(self.client.last_response.json(), self.test_data) @httpretty.activate @@ -67,11 +67,12 @@ class TestHttp(unittest.TestCase): self._register_uri(httpretty.POST) response = self.client.post(self.test_endpoint, foo="bar", spam="eggs") - self.assertEqual(self._last_request().body, "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": "bar", - "spam": "eggs"}) + self.assertEqual(self.client.last_params, {"foo": b"bar", + "spam": b"eggs"}) self.assertEqual(self.client.last_response.json(), self.test_data) @httpretty.activate @@ -123,7 +124,7 @@ class TestHttp(unittest.TestCase): self.assertEqual(params["tag"], ["tag_id"]) self.assertEqual(params["list_"], ["photo_id,album_id,tag_id"]) self.assertEqual(params["boolean"], ["1"]) - self.assertEqual(params["unicode_"], ["\xc3\xbcmlaut"]) + self.assertIn(params["unicode_"], [["\xc3\xbcmlaut"], ["\xfcmlaut"]]) @httpretty.activate def test_get_with_api_version(self): @@ -152,10 +153,10 @@ class TestHttp(unittest.TestCase): response = self.client.post(self.test_endpoint, files={"file": in_file}) self.assertEqual(response, self.test_data) - body = self._last_request().body + body = str(self._last_request().body) self.assertIn("Content-Disposition: form-data; "+ "name=\"file\"; filename=\"test_file.txt\"", body) - self.assertIn("Test File", body) + self.assertIn("Test File", str(body)) @httpretty.activate From 6387877e48407e91b73a2117f8e0f7cdaede4100 Mon Sep 17 00:00:00 2001 From: sneakypete81 Date: Sat, 29 Jun 2013 20:25:29 +0100 Subject: [PATCH 21/26] Monkey-patch httpretty for Python3 support This fix is in the httpretty codebase, but not yet released --- tests/unit/test_http_errors.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/unit/test_http_errors.py b/tests/unit/test_http_errors.py index ddd25ec..d95a838 100644 --- a/tests/unit/test_http_errors.py +++ b/tests/unit/test_http_errors.py @@ -1,6 +1,11 @@ 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: From a1f22c0b6d71798cea4f6f56734c17b683a9c426 Mon Sep 17 00:00:00 2001 From: sneakypete81 Date: Sat, 29 Jun 2013 20:26:30 +0100 Subject: [PATCH 22/26] requests-oauthlib 0.3.2 introduced a Unicode bug. Hold back to 0.3.1 until this is resolved --- tox.ini | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/tox.ini b/tox.ini index 5e5c469..540d538 100644 --- a/tox.ini +++ b/tox.ini @@ -4,13 +4,14 @@ envlist = py26, py27, py33 [testenv] commands = python -m unittest discover --catch tests/unit deps = - mock - httpretty + requests_oauthlib == 0.3.1 # since 0.3.2 introduced a Unicode bug + mock >= 1.0.0 + httpretty >= 0.6.1 [testenv:py26] commands = unit2 discover --catch tests/unit deps = - mock - httpretty + mock >= 1.0.0 + httpretty >= 0.6.1 unittest2 discover From 2c6841bdd778187a695ba0fb376bda7d74991552 Mon Sep 17 00:00:00 2001 From: sneakypete81 Date: Sat, 29 Jun 2013 20:32:56 +0100 Subject: [PATCH 23/26] Travis now tests all supported Python versions --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index ee74c8c..e8f456e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,7 +4,7 @@ install: - pip install tox --use-mirrors - .travis/install_pylint -script: tox -e py27 +script: tox after_script: # Run Pylint From 9cc884932226466251775c53c4c312a9a31ee88d Mon Sep 17 00:00:00 2001 From: sneakypete81 Date: Sat, 29 Jun 2013 21:24:33 +0100 Subject: [PATCH 24/26] Latest requests_oauthlib fixes a Unicode bug, so we don't need to ask requests to decode anymore --- openphoto/openphoto_http.py | 10 ++-------- tox.ini | 1 - 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/openphoto/openphoto_http.py b/openphoto/openphoto_http.py index 178057e..86a8b3d 100644 --- a/openphoto/openphoto_http.py +++ b/openphoto/openphoto_http.py @@ -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 diff --git a/tox.ini b/tox.ini index 540d538..2657d68 100644 --- a/tox.ini +++ b/tox.ini @@ -4,7 +4,6 @@ envlist = py26, py27, py33 [testenv] commands = python -m unittest discover --catch tests/unit deps = - requests_oauthlib == 0.3.1 # since 0.3.2 introduced a Unicode bug mock >= 1.0.0 httpretty >= 0.6.1 From a54ec012549da4485e72e023ff0fc3a05313cacc Mon Sep 17 00:00:00 2001 From: sneakypete81 Date: Sun, 30 Jun 2013 17:05:50 +0100 Subject: [PATCH 25/26] Updated functional test instructions --- tests/functional/README.markdown | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/functional/README.markdown b/tests/functional/README.markdown index 64601a8..9f4f804 100644 --- a/tests/functional/README.markdown +++ b/tests/functional/README.markdown @@ -38,14 +38,14 @@ 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 + 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/test_albums.py:TestAlbums.test_view + nosetests -v -s --nologcapture tests/functional/test_albums.py:TestAlbums.test_view All HTTP requests and responses are recorded in the file ``tests.log``. From 56d376d50e3d0ef04a2f6ba27350bf17e90e3482 Mon Sep 17 00:00:00 2001 From: sneakypete81 Date: Sun, 30 Jun 2013 17:08:54 +0100 Subject: [PATCH 26/26] Added tox instructions --- tests/README.markdown | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/README.markdown b/tests/README.markdown index 320b5b3..793ae68 100644 --- a/tests/README.markdown +++ b/tests/README.markdown @@ -12,11 +12,16 @@ They run very quickly and don't require any external test hosts. #### 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 + ---------------------------------------- ###Functional Tests