SaltyCrane Blog — Notes on JavaScript and web development

test http server for unit testing

Actual code to test:

import cStringIO
import json
import os.path
from urlparse import urlparse

import pycurl
from flask import current_app
from PIL import Image


def upload_image(fileobj, mimetype):
    """
    Upload a jpeg image to the image upload service. Return 2-tuple
    (<response code="code">, <image path="path">)

    Note: pycurl was used instead of urllib2 because urllib2 does not easily
    support POSTing binary data.
    """
    # TODO: set the maximum size
    url = 'http://{host}/upload/image?purpose=dp&maxsize;=2000'.format(
        host=current_app.config['IMAGE_API_HOST'])

    current_app.logger.debug('Uploading to: ' + url)

    c = pycurl.Curl()
    c.setopt(pycurl.VERBOSE, 1)
    c.setopt(pycurl.URL, url)
    fout = cStringIO.StringIO()
    c.setopt(pycurl.WRITEFUNCTION, fout.write)
    c.setopt(pycurl.POST, 1)
    c.setopt(pycurl.HTTPHEADER, ['Content-Type: %s' % mimetype])

    # Get the size of the file
    fileobj.seek(0, 2)
    filesize = fileobj.tell()
    fileobj.seek(0, 0)
    c.setopt(pycurl.POSTFIELDSIZE, filesize)

    c.setopt(pycurl.READFUNCTION, fileobj.read)

    c.perform()
    response_code = c.getinfo(pycurl.RESPONSE_CODE)
    response_data = fout.getvalue()
    if response_code == 200:
        image_path = json.loads(response_data)['path']
    else:
        current_app.logger.error(
            'Upload failed: POST %s: HTTP %d: %s' % (
                url, response_code, response_data))
        image_path = None
    c.close()

    return response_code, image_path


def download_image(url):
    """
    Download an image from the image service. Return 2-tuple
    (</image></response><response code="code">, <file object="object">)
    """
    current_app.logger.debug('Downloading: ' + url)

    c = pycurl.Curl()
    c.setopt(pycurl.VERBOSE, 1)
    c.setopt(pycurl.URL, url)
    fout = cStringIO.StringIO()
    c.setopt(pycurl.WRITEFUNCTION, fout.write)

    c.perform()
    response_code = c.getinfo(pycurl.RESPONSE_CODE)
    c.close()
    fout.seek(0, 0)

    return response_code, fout
</file></response>

Test HTTP server:

import BaseHTTPServer
import imghdr
import os.path
import textwrap


TEST_IMAGE_DIR = os.path.join(
    os.path.abspath(os.path.dirname(__file__)), 'testimages')
TEST_SERVER_HOST = 'localhost'
TEST_SERVER_PORT_IMAGE_UPLOAD = 8555
TEST_SERVER_PORT_IMAGE_DOWNLOAD = 8666
TEST_SERVER_IMAGE_UPLOAD = '{}:{}'.format(
    TEST_SERVER_HOST, TEST_SERVER_PORT_IMAGE_UPLOAD)
TEST_SERVER_IMAGE_DOWNLOAD = '{}:{}'.format(
    TEST_SERVER_HOST, TEST_SERVER_PORT_IMAGE_DOWNLOAD)


class ImageUploadAPIHandler(BaseHTTPServer.BaseHTTPRequestHandler):
    def do_POST(self):
        ctype = self.headers.getheader('content-type').lower()
        if ctype != 'image/jpeg':
            self.send_response(400)
            self.end_headers()
            self.wfile.write(textwrap.dedent('''
                <html>
                <head><title>400 Bad Request</title></head>
                <body bgcolor="white">
                <center><h1>400 Bad Request</h1></center>
                <hr /><center>Python Test Server</center>
                </body>
                </html>
            '''))
            return

        length = int(self.headers.getheader('content-length'))
        fstream = self.rfile.read(length)
        imgtype = imghdr.what('', h=fstream)
        if imgtype != 'jpeg':
            self.send_response(415)
            self.end_headers()
            self.wfile.write(textwrap.dedent('''
                <html>
                <head><title>415 Unsupported Media Type</title></head>
                <body bgcolor="white">
                <center><h1>415 Unsupported Media Type</h1></center>
                <hr /><center>Python Test Server</center>
                </body>
                </html>
            '''))
            return

        self.send_response(200)
        self.send_header('Content-Type', 'application/json')
        self.end_headers()
        self.wfile.write(textwrap.dedent('''
            {
               "path":"/path/to/uploaded/test/image.jpg",
               "width":"335",
               "height":"500",
               "type":"jpg"
            }
        '''))


class ImageDownloadHandler(BaseHTTPServer.BaseHTTPRequestHandler):
    def do_GET(self):
        imageobj = open(os.path.join(TEST_IMAGE_DIR, 'tower-crane.jpg'))

        self.send_response(200)
        self.end_headers()
        self.wfile.write(imageobj.read())


httpd_image_upload = BaseHTTPServer.HTTPServer(
    (TEST_SERVER_HOST, TEST_SERVER_PORT_IMAGE_UPLOAD), ImageUploadAPIHandler)
httpd_image_download = BaseHTTPServer.HTTPServer(
    (TEST_SERVER_HOST, TEST_SERVER_PORT_IMAGE_DOWNLOAD), ImageDownloadHandler)

Test code:

import imghdr
import os.path
import textwrap
import threading

from flask import current_app
from mock import Mock

from dp.blueprints.profile import imageutil
from tests.blueprints.profile.asset_service_test_server import (
    httpd_image_upload, httpd_image_download, TEST_SERVER_IMAGE_UPLOAD,
    TEST_SERVER_IMAGE_DOWNLOAD, TEST_IMAGE_DIR)
from tests.util import _BaseTestCase


class UploadImageTestCase(_BaseTestCase):

    def setUp(self):
        super(UploadImageTestCase, self).setUp()

        self.app.logger.error = Mock()
        self.app.config['IMAGE_API_HOST'] = TEST_SERVER_IMAGE_UPLOAD

        # Start the asset service test server in another thread
        httpd_thread = threading.Thread(target=httpd_image_upload.serve_forever)
        httpd_thread.daemon = True
        httpd_thread.start()

    def test_success(self):
        fileobj = open(os.path.join(TEST_IMAGE_DIR, 'tower-crane.jpg'))
        response_code, image_path = imageutil.upload_image(fileobj, 'image/jpeg')
        self.assertEqual(response_code, 200)
        self.assertEqual(image_path, '/path/to/uploaded/test/image.jpg')

    def test_error_400(self):
        fileobj = open(os.path.join(TEST_IMAGE_DIR, 'tower-crane.jpg'))
        response_code, image_path = imageutil.upload_image(fileobj, 'image/png')
        current_app.logger.error.assert_called_once_with(
            'Upload failed: POST http://{}/upload/image?purpose=dp&maxsize;=2000: HTTP 400: '.format(
                TEST_SERVER_IMAGE_UPLOAD)
            + textwrap.dedent('''
                <html>
                <head><title>400 Bad Request</title></head>
                <body bgcolor="white">
                <center><h1>400 Bad Request</h1></center>
                <hr /><center>Python Test Server</center>
                </body>
                </html>
            '''))
        self.assertEqual(response_code, 400)
        self.assertEqual(image_path, None)

    def test_error_415(self):
        fileobj = open(os.path.join(TEST_IMAGE_DIR, 'Tropical-Fish.png'))
        response_code, image_path = imageutil.upload_image(fileobj, 'image/jpeg')
        current_app.logger.error.assert_called_once_with(
            'Upload failed: POST http://{}/upload/image?purpose=dp&maxsize;=2000: HTTP 415: '.format(
                TEST_SERVER_IMAGE_UPLOAD)
            + textwrap.dedent('''
                <html>
                <head><title>415 Unsupported Media Type</title></head>
                <body bgcolor="white">
                <center><h1>415 Unsupported Media Type</h1></center>
                <hr /><center>Python Test Server</center>
                </body>
                </html>
            '''))
        self.assertEqual(response_code, 415)
        self.assertEqual(image_path, None)


class DownloadImageTestCase(_BaseTestCase):

    def setUp(self):
        super(DownloadImageTestCase, self).setUp()

        self.app.logger.error = Mock()

        # Start the asset service test server in another thread
        httpd_thread = threading.Thread(target=httpd_image_download.serve_forever)
        httpd_thread.daemon = True
        httpd_thread.start()

    def test_success(self):
        image_url = 'http://{}/path/to/download/image.jpg'.format(
            TEST_SERVER_IMAGE_DOWNLOAD)
        response_code, fout = imageutil.download_image(image_url)
        imgtype = imghdr.what('', h=fout.read())
        self.assertEqual(response_code, 200)
        self.assertEqual(imgtype, 'jpeg')

Comments