HTTP

Required files:

test_plan.py

#!/usr/bin/env python
"""
This example is to demonstrate HTTP communication test scenarios.
"""

import sys

from testplan import test_plan
from testplan.report.testing.styles import Style, StyleEnum

import http_basic
import http_custom_handler

OUTPUT_STYLE = Style(StyleEnum.ASSERTION_DETAIL, StyleEnum.ASSERTION_DETAIL)


# Hard-coding `pdf_path`, 'stdout_style' and 'pdf_style' so that the
# downloadable example gives meaningful and presentable output.
# NOTE: this programmatic arguments passing approach will cause Testplan
# to ignore any command line arguments related to that functionality.
@test_plan(
    name="HTTPConnections",
    pdf_path="report.pdf",
    stdout_style=OUTPUT_STYLE,
    pdf_style=OUTPUT_STYLE,
)
def main(plan):
    """
    Testplan decorated main function to add and execute 2 MultiTests.

    :return: Testplan result object.
    :rtype:  ``testplan.base.TestplanResult``
    """
    plan.add(http_basic.get_multitest("HTTPBasic"))
    plan.add(http_custom_handler.get_multitest("HTTPCustomHandler"))


if __name__ == "__main__":
    res = main()
    print("Exiting code: {}".format(res.exit_code))
    sys.exit(res.exit_code)

http_basic.py

"""Tests HTTP requests between a server and a client."""
import json

from testplan.common.utils.context import context

from testplan.testing.multitest import MultiTest, testsuite, testcase
from testplan.testing.multitest.driver.http import (
    HTTPServer,
    HTTPClient,
    HTTPResponse,
)


@testsuite
class HTTPTestsuite:
    """HTTP requests between a server and a client."""

    @testcase
    def request_and_response(self, env, result):
        """
        Client makes a request, server received and responds back.
        """
        # The HTTPClient sends a GET request to some section of the API. The
        # HTTPServer will respond with the next message in it's response queue
        # no matter the HTTP method (GET, POST etc.) or the section of the API
        # it has been sent.
        result.log("Client sends GET request")
        env.http_client.get(api="/random/text")

        # Need to do a receive otherwise it will ruin our next testcase
        received_request = env.http_server.receive()
        result.log(f"Server got GET request: {received_request}")

        # Create some JSON.
        json_content = {"this": ["is", "a", "json", "object"]}

        # We then prepare an HTTPResponse. Headers are added as a dictionary and
        # content as a list. For this example we just indicate that the content
        # type is JSON and dump the JSON as a string so it can be sent.
        prepared_response = HTTPResponse(
            headers={"Content-type": "application/json"},
            content=[json.dumps(json_content)],
        )

        # The HTTPServer then responds. Under the hood this adds the response to
        # the HTTPServer's response queue which will be immediately sent as the
        # HTTPClient has already sent a request.
        result.log("Server receives request and sends response")
        env.http_server.respond(prepared_response)

        # The HTTPClient then receives the HTTPServer's response.
        response = env.http_client.receive()

        # We are verifying the JSON sent back is the same as the one sent by the
        # HTTPServer.
        result.dict.match(
            response.json(), json_content, "JSON response from server"
        )

    @testcase
    def post_and_response(self, env, result):
        """
        Client makes a request, server received and responds back.
        """
        # Create some JSON.
        json_content = {"this": ["is", "another", "json", "object"]}

        # The HTTPClient sends a POST request with some data to some section of the API. The
        # HTTPServer will respond with the same message in it's response queue
        # no matter the HTTP method (GET, POST etc.) or the section of the API
        # it has been sent.
        result.log("Client sends POST request")
        env.http_client.post(
            api="/random/text",
            json=json_content,
            headers={"Content-Type": "application/json"},
        )

        # The HTTP Server receives the request
        received_request = env.http_server.receive()
        result.log(f"Server got POST request: {received_request}")

        # We are verifying the JSON sent back is the same as the one sent by the
        # HTTPServer.
        result.dict.match(
            received_request.json, json_content, "JSON sent to the server"
        )

        # We then prepare an HTTPResponse. Headers are added as a dictionary and
        # content as a list. For this example we just indicate that the content
        # type is JSON and dump the JSON as a string so it can be sent.
        prepared_response = HTTPResponse(
            headers={"Content-type": "application/json"},
            content=[json.dumps(json_content)],
        )

        # The HTTPServer then responds. Under the hood this adds the response to
        # the HTTPServer's response queue which will be immediately sent as the
        # HTTPClient has already sent a request.
        result.log("Server receives request and sends response")
        env.http_server.respond(prepared_response)

        # The HTTPClient then receives the HTTPServer's response.
        response = env.http_client.receive()

        # We are verifying the JSON sent back is the same as the one sent by the
        # HTTPServer.
        result.dict.match(
            response.json(), json_content, "JSON response from server"
        )


def get_multitest(name):
    """
    Creates and returns a new MultiTest instance to be added to the plan.
    The environment is a server and a client connecting using the context
    functionality that retrieves host/port of the server after is started.
    """
    test = MultiTest(
        name=name,
        suites=[HTTPTestsuite()],
        environment=[
            HTTPServer(name="http_server"),
            HTTPClient(
                name="http_client",
                host=context("http_server", "{{host}}"),
                port=context("http_server", "{{port}}"),
            ),
        ],
    )
    return test

http_custom_handler.py

"""Tests using a custom HTTP response handler."""
from testplan.common.utils.context import context

from testplan.testing.multitest import MultiTest, testsuite, testcase
from testplan.testing.multitest.driver.http import HTTPServer, HTTPClient

from custom_http_request_handler import CustomHTTPRequestHandler


@testsuite
class HTTPTestsuite:
    """Sending requests to an HTTPServer with a custom HTTP request handler."""

    @testcase
    def general_request(self, env, result):
        """
        Client makes a general request, server responds with text file contents.
        """
        # The HTTPClient sends a GET request to some section of the API.
        result.log("Client sends GET request")
        env.http_client.get(api="/random/text")

        # The HTTPServer will use the CustomHTTPRequestHandler to automatically
        # send the contents of the test.txt file back to every request. The
        # HTTPClient receives the HTTPServer's response.
        response = env.http_client.receive()

        # We are verifying the contents of the test.txt file is what the server
        # has sent back.
        text = open("test.txt").read()
        result.equal(
            response.text, text, "HTTPServer sends file contents in response."
        )

    @testcase
    def post_request(self, env, result):
        """
        Client makes a POST request, server responds with custom POST response.
        """
        result.log("Client sends POST request")
        env.http_client.post(api="/random/text")

        response = env.http_client.receive()

        result.equal(
            response.text,
            "POST response.",
            "HTTPServer sends custom POST response.",
        )


def get_multitest(name):
    """
    Creates and returns a new MultiTest instance to be added to the plan.
    The environment is a server and a client connecting using the context
    functionality that retrieves host/port of the server after is started.
    """
    # The HTTPServer can be passed handler_attributes in a dictionary. These
    # will be accessible in the custom HTTP request handler
    # (see custom_http_request_handler.py).
    attributes = {"text_file": "test.txt"}
    test = MultiTest(
        name=name,
        suites=[HTTPTestsuite()],
        environment=[
            HTTPServer(
                name="http_server",
                request_handler=CustomHTTPRequestHandler,
                handler_attributes=attributes,
            ),
            HTTPClient(
                name="http_client",
                host=context("http_server", "{{host}}"),
                port=context("http_server", "{{port}}"),
            ),
        ],
    )
    return test

custom_http_request_handler.py

"""Custom HTTP request handler."""
from testplan.testing.multitest.driver.http import (
    HTTPRequestHandler,
    HTTPResponse,
)


class CustomHTTPRequestHandler(HTTPRequestHandler):
    """Define a custom request handler."""

    def get_response(self, request):
        """
        Override the get_response method to determine how the server will
        respond to all requests. You must return an HTTPResponse object as the
        _parse_request method expects this.
        """
        text_file = self.server.handler_attributes["text_file"]
        with open(text_file) as input:
            text = input.read()
        response = HTTPResponse(content=[text])
        self.server.log_callback(
            "Creating custom response from {}".format(text_file)
        )
        return response

    def do_POST(self):
        """
        Override individual request methods (e.g. do_POST) to determine how the
        server will respond to individual requests. You will have to create the
        response, then call _send_header and _send_content.
        """
        response = HTTPResponse(content=["POST response."])
        self._send_header(response.status_code, response.headers)
        self._send_content(response.content)

test.txt

This is a test file.
It has multiple lines.