Back to Repositories

Validating HTTP Output Parsing and Matching in HTTPie CLI

This test suite validates HTTPie’s output parsing and matching functionality, focusing on request/response handling and message formatting. It ensures proper parsing of headers, body content, and message separators across different HTTP communication scenarios.

Test Coverage Overview

The test suite provides comprehensive coverage of HTTPie’s output matching capabilities:

  • HTTP headers validation (both request and response)
  • Message body parsing
  • Multipart form data handling
  • Message separator detection
  • Response metadata verification

Implementation Analysis

The testing approach uses systematic validation of different output components:

The implementation employs dedicated assert functions (assert_output_matches and assert_output_does_not_match) to verify expected output patterns. Tests handle various line ending scenarios (CRLF, LF) and complex multipart message structures.

Technical Details

Key technical components include:

  • Custom assertion utilities for output matching
  • CRLF and line ending handling
  • Message separator constants
  • Expect enum for different output components
  • Support for multipart/form-data boundaries

Best Practices Demonstrated

The test suite exemplifies several testing best practices:

  • Granular test cases for specific functionality
  • Edge case coverage (incomplete/unterminated headers)
  • Systematic validation of complex message structures
  • Clear separation of concerns between different output components
  • Comprehensive boundary testing

httpie/cli

tests/utils/matching/test_matching.py

            
"""
Here we test our output parsing and matching implementation, not HTTPie itself.

"""
from httpie.models import ELAPSED_TIME_LABEL
from httpie.output.writer import MESSAGE_SEPARATOR
from ...utils import CRLF
from . import assert_output_does_not_match, assert_output_matches, Expect


def test_assert_output_matches_headers_incomplete():
    assert_output_does_not_match(f'HTTP/1.1{CRLF}', [Expect.RESPONSE_HEADERS])


def test_assert_output_matches_headers_unterminated():
    assert_output_does_not_match(
        (
            f'HTTP/1.1{CRLF}'
            'AAA:BBB'
            f'{CRLF}'
        ),
        [Expect.RESPONSE_HEADERS],
    )


def test_assert_output_matches_response_headers():
    assert_output_matches(
        (
            f'HTTP/1.1 200 OK{CRLF}'
            f'AAA:BBB{CRLF}'
            f'{CRLF}'
        ),
        [Expect.RESPONSE_HEADERS],
    )


def test_assert_output_matches_request_headers():
    assert_output_matches(
        (
            f'GET / HTTP/1.1{CRLF}'
            f'AAA:BBB{CRLF}'
            f'{CRLF}'
        ),
        [Expect.REQUEST_HEADERS],
    )


def test_assert_output_matches_headers_and_separator():
    assert_output_matches(
        (
            f'HTTP/1.1{CRLF}'
            f'AAA:BBB{CRLF}'
            f'{CRLF}'
            f'{MESSAGE_SEPARATOR}'
        ),
        [Expect.RESPONSE_HEADERS, Expect.SEPARATOR],
    )


def test_assert_output_matches_body_unmatched_crlf():
    assert_output_does_not_match(f'AAA{CRLF}', [Expect.BODY])


def test_assert_output_matches_body_unmatched_separator():
    assert_output_does_not_match(f'AAA{MESSAGE_SEPARATOR}', [Expect.BODY])


def test_assert_output_matches_body_and_separator():
    assert_output_matches(f'AAA{MESSAGE_SEPARATOR}', [Expect.BODY, Expect.SEPARATOR])


def test_assert_output_matches_body_r():
    assert_output_matches('AAA\r', [Expect.BODY])


def test_assert_output_matches_body_n():
    assert_output_matches('AAA
', [Expect.BODY])


def test_assert_output_matches_body_r_body():
    assert_output_matches('AAA\rBBB', [Expect.BODY])


def test_assert_output_matches_body_n_body():
    assert_output_matches('AAA
BBB', [Expect.BODY])


def test_assert_output_matches_headers_and_body():
    assert_output_matches(
        (
            f'HTTP/1.1{CRLF}'
            f'AAA:BBB{CRLF}'
            f'{CRLF}'
            'CCC'
        ),
        [Expect.RESPONSE_HEADERS, Expect.BODY]
    )


def test_assert_output_matches_headers_with_body_and_separator():
    assert_output_matches(
        (
            f'HTTP/1.1 {CRLF}'
            f'AAA:BBB{CRLF}{CRLF}'
            f'CCC{MESSAGE_SEPARATOR}'
        ),
        [Expect.RESPONSE_HEADERS, Expect.BODY, Expect.SEPARATOR]
    )


def test_assert_output_matches_response_meta():
    assert_output_matches(
        (
            'Key: Value
'
            f'{ELAPSED_TIME_LABEL}: 3.3s'
        ),
        [Expect.RESPONSE_META]
    )


def test_assert_output_matches_whole_response():
    assert_output_matches(
        (
            f'HTTP/1.1{CRLF}'
            f'AAA:BBB{CRLF}'
            f'{CRLF}'
            f'CCC{MESSAGE_SEPARATOR}'
            f'{ELAPSED_TIME_LABEL}: 3.3s'
        ),
        [Expect.RESPONSE_HEADERS, Expect.BODY, Expect.RESPONSE_META]
    )


def test_assert_output_matches_multiple_messages():
    assert_output_matches(
        (
            f'POST / HTTP/1.1{CRLF}'
            f'AAA:BBB{CRLF}'
            f'{CRLF}'

            f'CCC'
            f'{MESSAGE_SEPARATOR}'

            f'HTTP/1.1 200 OK{CRLF}'
            f'EEE:FFF{CRLF}'
            f'{CRLF}'

            'GGG'
            f'{MESSAGE_SEPARATOR}'
        ), [
            Expect.REQUEST_HEADERS,
            Expect.BODY,
            Expect.SEPARATOR,
            Expect.RESPONSE_HEADERS,
            Expect.BODY,
            Expect.SEPARATOR,
        ]
    )


def test_assert_output_matches_multipart_body():
    output = (
        'POST / HTTP/1.1\r
'
        'User-Agent: HTTPie/2.4.0-dev\r
'
        'Accept-Encoding: gzip, deflate\r
'
        'Accept: */*\r
'
        'Connection: keep-alive\r
'
        'Content-Type: multipart/form-data; boundary=1e22169de43e4a2e8d9e41c0a1c93cc5\r
'
        'Content-Length: 212\r
'
        'Host: pie.dev\r
'
        '\r
'
        '--1e22169de43e4a2e8d9e41c0a1c93cc5\r
'
        'Content-Disposition: form-data; name="AAA"\r
'
        '\r
'
        'BBB\r
'
        '--1e22169de43e4a2e8d9e41c0a1c93cc5\r
'
        'Content-Disposition: form-data; name="CCC"\r
'
        '\r
'
        'DDD\r
'
        '--1e22169de43e4a2e8d9e41c0a1c93cc5--\r
'
    )
    assert_output_matches(output, [Expect.REQUEST_HEADERS, Expect.BODY])


def test_assert_output_matches_multipart_body_with_separator():
    output = (
        'POST / HTTP/1.1\r
'
        'User-Agent: HTTPie/2.4.0-dev\r
'
        'Accept-Encoding: gzip, deflate\r
'
        'Accept: */*\r
'
        'Connection: keep-alive\r
'
        'Content-Type: multipart/form-data; boundary=1e22169de43e4a2e8d9e41c0a1c93cc5\r
'
        'Content-Length: 212\r
'
        'Host: pie.dev\r
'
        '\r
'
        '--1e22169de43e4a2e8d9e41c0a1c93cc5\r
'
        'Content-Disposition: form-data; name="AAA"\r
'
        '\r
'
        'BBB\r
'
        '--1e22169de43e4a2e8d9e41c0a1c93cc5\r
'
        'Content-Disposition: form-data; name="CCC"\r
'
        '\r
'
        'DDD\r
'
        '--1e22169de43e4a2e8d9e41c0a1c93cc5--\r
'
        f'{MESSAGE_SEPARATOR}'
    )
    assert_output_matches(output, [Expect.REQUEST_HEADERS, Expect.BODY, Expect.SEPARATOR])


def test_assert_output_matches_multiple_separators():
    assert_output_matches(
        MESSAGE_SEPARATOR + MESSAGE_SEPARATOR + 'AAA' + MESSAGE_SEPARATOR + MESSAGE_SEPARATOR,
        [Expect.SEPARATOR, Expect.SEPARATOR, Expect.BODY, Expect.SEPARATOR, Expect.SEPARATOR]
    )