Back to Repositories

Testing Exit Status Handling and HTTP Response Processing in HTTPie CLI

This test suite validates the exit status handling in HTTPie CLI, focusing on various HTTP response scenarios and interrupt handling. The tests ensure proper exit codes are returned for different HTTP status codes, timeouts, and keyboard interruptions.

Test Coverage Overview

The test suite provides comprehensive coverage of exit status handling across multiple scenarios.

  • HTTP status code responses (2xx, 3xx, 4xx, 5xx)
  • Keyboard interruption handling
  • Timeout scenarios
  • Redirect behavior with status checking
  • Error output handling for redirected stdout

Implementation Analysis

The testing approach utilizes Python’s unittest framework with mock objects for controlled testing of interrupt scenarios. The implementation leverages MockEnvironment for environment simulation and uses context managers for exception handling.

Tests follow a consistent pattern of making HTTP requests and validating both the response content and exit status codes.

Technical Details

  • Uses unittest.mock for mocking system interrupts
  • Custom MockEnvironment class for environment control
  • HTTPie’s ExitStatus enumeration for status code validation
  • HTTP request simulation via httpbin endpoints
  • Configurable timeout and status checking parameters

Best Practices Demonstrated

The test suite exemplifies robust testing practices with clear separation of concerns and thorough error handling validation.

  • Comprehensive status code coverage
  • Explicit error tolerance configuration
  • Environment state validation
  • Mock object usage for controlled testing
  • Clear test case naming and organization

httpie/cli

tests/test_exit_status.py

            
from unittest import mock

from httpie.status import ExitStatus
from .utils import MockEnvironment, http, HTTP_OK


def test_keyboard_interrupt_during_arg_parsing_exit_status(httpbin):
    with mock.patch('httpie.cli.definition.parser.parse_args',
                    side_effect=KeyboardInterrupt()):
        r = http('GET', httpbin + '/get', tolerate_error_exit_status=True)
        assert r.exit_status == ExitStatus.ERROR_CTRL_C


def test_keyboard_interrupt_in_program_exit_status(httpbin):
    with mock.patch('httpie.core.program',
                    side_effect=KeyboardInterrupt()):
        r = http('GET', httpbin + '/get', tolerate_error_exit_status=True)
        assert r.exit_status == ExitStatus.ERROR_CTRL_C


def test_ok_response_exits_0(httpbin):
    r = http('GET', httpbin + '/get')
    assert HTTP_OK in r
    assert r.exit_status == ExitStatus.SUCCESS


def test_error_response_exits_0_without_check_status(httpbin):
    r = http('GET', httpbin + '/status/500')
    assert '500 INTERNAL SERVER ERROR' in r
    assert r.exit_status == ExitStatus.SUCCESS
    assert not r.stderr


def test_timeout_exit_status(httpbin):

    r = http('--timeout=0.01', 'GET', httpbin + '/delay/0.5',
             tolerate_error_exit_status=True)
    assert r.exit_status == ExitStatus.ERROR_TIMEOUT


def test_3xx_check_status_exits_3_and_stderr_when_stdout_redirected(
        httpbin):
    env = MockEnvironment(stdout_isatty=False)
    r = http('--check-status', '--headers',
             'GET', httpbin + '/status/301',
             env=env, tolerate_error_exit_status=True)
    assert '301 MOVED PERMANENTLY' in r
    assert r.exit_status == ExitStatus.ERROR_HTTP_3XX
    assert '301 moved permanently' in r.stderr.lower()


def test_3xx_check_status_redirects_allowed_exits_0(httpbin):
    r = http('--check-status', '--follow',
             'GET', httpbin + '/status/301',
             tolerate_error_exit_status=True)
    # The redirect will be followed so 200 is expected.
    assert HTTP_OK in r
    assert r.exit_status == ExitStatus.SUCCESS


def test_4xx_check_status_exits_4(httpbin):
    r = http('--check-status', 'GET', httpbin + '/status/401',
             tolerate_error_exit_status=True)
    assert '401 UNAUTHORIZED' in r
    assert r.exit_status == ExitStatus.ERROR_HTTP_4XX
    # Also stderr should be empty since stdout isn't redirected.
    assert not r.stderr


def test_5xx_check_status_exits_5(httpbin):
    r = http('--check-status', 'GET', httpbin + '/status/500',
             tolerate_error_exit_status=True)
    assert '500 INTERNAL SERVER ERROR' in r
    assert r.exit_status == ExitStatus.ERROR_HTTP_5XX