# -*- coding: utf-8 - # # This file is part of gunicorn released under the MIT license. # See the NOTICE for more information. """Tests for HTTP/2 configuration settings.""" import pytest from gunicorn import config from gunicorn.config import Config class TestHttpProtocolsConfig: """Test http_protocols configuration setting.""" def test_default_is_h1(self): c = Config() assert c.http_protocols == ["h1"] def test_set_h1_only(self): c = Config() c.set("http_protocols", "h1") assert c.http_protocols == ["h1"] def test_set_h2_only(self): c = Config() c.set("http_protocols", "h2") assert c.http_protocols == ["h2"] def test_set_h1_and_h2(self): c = Config() c.set("http_protocols", "h2,h1") assert c.http_protocols == ["h2", "h1"] def test_set_h1_h2_order_preserved(self): c = Config() c.set("http_protocols", "h1,h2") assert c.http_protocols == ["h1", "h2"] def test_whitespace_handling(self): c = Config() c.set("http_protocols", " h1 , h2 ") assert c.http_protocols == ["h1", "h2"] def test_case_insensitive(self): c = Config() c.set("http_protocols", "H1,H2") assert c.http_protocols == ["h1", "h2"] def test_empty_string_defaults_to_h1(self): c = Config() c.set("http_protocols", "") assert c.http_protocols == ["h1"] def test_none_defaults_to_h1(self): c = Config() c.set("http_protocols", None) assert c.http_protocols == ["h1"] def test_invalid_protocol(self): c = Config() with pytest.raises(ValueError) as exc_info: c.set("http_protocols", "h4") assert "Invalid protocol" in str(exc_info.value) assert "h4" in str(exc_info.value) def test_invalid_type(self): c = Config() with pytest.raises(TypeError) as exc_info: c.set("http_protocols", 123) assert "must be a string" in str(exc_info.value) def test_invalid_type_list(self): c = Config() with pytest.raises(TypeError): c.set("http_protocols", ["h1", "h2"]) def test_mixed_valid_invalid(self): c = Config() with pytest.raises(ValueError): c.set("http_protocols", "h1,invalid,h2") class TestHttp2MaxConcurrentStreams: """Test http2_max_concurrent_streams configuration setting.""" def test_default_value(self): c = Config() assert c.http2_max_concurrent_streams == 100 def test_set_custom_value(self): c = Config() c.set("http2_max_concurrent_streams", 50) assert c.http2_max_concurrent_streams == 50 def test_set_from_string(self): c = Config() c.set("http2_max_concurrent_streams", "200") assert c.http2_max_concurrent_streams == 200 def test_set_high_value(self): c = Config() c.set("http2_max_concurrent_streams", 1000) assert c.http2_max_concurrent_streams == 1000 def test_negative_value_raises(self): c = Config() with pytest.raises(ValueError): c.set("http2_max_concurrent_streams", -1) def test_zero_value(self): # Zero is technically valid for positive int validator # It may have special meaning (use h2 default) c = Config() c.set("http2_max_concurrent_streams", 0) assert c.http2_max_concurrent_streams == 0 class TestHttp2InitialWindowSize: """Test http2_initial_window_size configuration setting.""" def test_default_value(self): c = Config() # Default per RFC 7540 is 65535 assert c.http2_initial_window_size == 65535 def test_set_custom_value(self): c = Config() c.set("http2_initial_window_size", 131072) assert c.http2_initial_window_size == 131072 def test_set_from_string(self): c = Config() c.set("http2_initial_window_size", "32768") assert c.http2_initial_window_size == 32768 def test_negative_value_raises(self): c = Config() with pytest.raises(ValueError): c.set("http2_initial_window_size", -1) class TestHttp2MaxFrameSize: """Test http2_max_frame_size configuration setting.""" def test_default_value(self): c = Config() # Default per RFC 7540 is 16384 assert c.http2_max_frame_size == 16384 def test_set_custom_value(self): c = Config() c.set("http2_max_frame_size", 32768) assert c.http2_max_frame_size == 32768 def test_set_from_string(self): c = Config() c.set("http2_max_frame_size", "65536") assert c.http2_max_frame_size == 65536 def test_valid_min_value(self): """RFC 7540 minimum is 16384 (2^14).""" c = Config() c.set("http2_max_frame_size", 16384) assert c.http2_max_frame_size == 16384 def test_valid_max_value(self): """RFC 7540 maximum is 16777215 (2^24 - 1).""" c = Config() c.set("http2_max_frame_size", 16777215) assert c.http2_max_frame_size == 16777215 def test_valid_mid_range_value(self): """Test a value in the middle of the valid range.""" c = Config() c.set("http2_max_frame_size", 1048576) # 1MB assert c.http2_max_frame_size == 1048576 def test_below_min_raises(self): """Values below 16384 should raise ValueError per RFC 7540.""" c = Config() with pytest.raises(ValueError) as exc_info: c.set("http2_max_frame_size", 16383) assert "must be between 16384 and 16777215" in str(exc_info.value) def test_above_max_raises(self): """Values above 16777215 should raise ValueError per RFC 7540.""" c = Config() with pytest.raises(ValueError) as exc_info: c.set("http2_max_frame_size", 16777216) assert "must be between 16384 and 16777215" in str(exc_info.value) def test_negative_value_raises(self): c = Config() with pytest.raises(ValueError): c.set("http2_max_frame_size", -1) class TestHttp2MaxHeaderListSize: """Test http2_max_header_list_size configuration setting.""" def test_default_value(self): c = Config() assert c.http2_max_header_list_size == 65536 def test_set_custom_value(self): c = Config() c.set("http2_max_header_list_size", 131072) assert c.http2_max_header_list_size == 131072 def test_set_from_string(self): c = Config() c.set("http2_max_header_list_size", "262144") assert c.http2_max_header_list_size == 262144 def test_negative_value_raises(self): c = Config() with pytest.raises(ValueError): c.set("http2_max_header_list_size", -1) class TestHttp2ConfigPropertyAccess: """Test property access for HTTP/2 settings.""" def test_all_http2_settings_accessible(self): c = Config() # These should not raise _ = c.http_protocols _ = c.http2_max_concurrent_streams _ = c.http2_initial_window_size _ = c.http2_max_frame_size _ = c.http2_max_header_list_size class TestHttp2ConfigDefaults: """Test that defaults match HTTP/2 specification values.""" def test_window_size_matches_rfc(self): """RFC 7540 default is 2^16-1 (65535).""" c = Config() assert c.http2_initial_window_size == 65535 def test_max_frame_size_matches_rfc_minimum(self): """RFC 7540 minimum is 2^14 (16384).""" c = Config() assert c.http2_max_frame_size == 16384 def test_concurrent_streams_reasonable_default(self): """Default should be reasonable for production use.""" c = Config() assert 1 <= c.http2_max_concurrent_streams <= 1000 class TestValidateHttpProtocols: """Test the validate_http_protocols function directly.""" def test_validate_none(self): result = config.validate_http_protocols(None) assert result == ["h1"] def test_validate_empty_string(self): result = config.validate_http_protocols("") assert result == ["h1"] def test_validate_whitespace_only(self): result = config.validate_http_protocols(" ") assert result == ["h1"] def test_validate_single_protocol(self): result = config.validate_http_protocols("h2") assert result == ["h2"] def test_validate_multiple_protocols(self): result = config.validate_http_protocols("h2,h1") assert result == ["h2", "h1"] def test_validate_with_spaces(self): result = config.validate_http_protocols("h2 , h1") assert result == ["h2", "h1"] def test_validate_uppercase(self): result = config.validate_http_protocols("H2,H1") assert result == ["h1", "h2"] or result == ["h2", "h1"] def test_validate_invalid_raises(self): with pytest.raises(ValueError): config.validate_http_protocols("http2") def test_validate_type_error(self): with pytest.raises(TypeError): config.validate_http_protocols(42) class TestValidateHttp2FrameSize: """Test the validate_http2_frame_size function directly.""" def test_validate_min_value(self): """RFC 7540 minimum is 16384 (2^14).""" result = config.validate_http2_frame_size(16384) assert result == 16384 def test_validate_max_value(self): """RFC 7540 maximum is 16777215 (2^24 - 1).""" result = config.validate_http2_frame_size(16777215) assert result == 16777215 def test_validate_mid_range(self): """Test a value in the middle of the valid range.""" result = config.validate_http2_frame_size(1000000) assert result == 1000000 def test_validate_from_string(self): """Test that string values are converted properly.""" result = config.validate_http2_frame_size("32768") assert result == 32768 def test_validate_hex_string(self): """Test hex string conversion.""" result = config.validate_http2_frame_size("0x10000") # 65536 assert result == 65536 def test_validate_below_min_raises(self): """Values below 16384 should raise ValueError.""" with pytest.raises(ValueError) as exc_info: config.validate_http2_frame_size(16383) assert "must be between 16384 and 16777215" in str(exc_info.value) def test_validate_above_max_raises(self): """Values above 16777215 should raise ValueError.""" with pytest.raises(ValueError) as exc_info: config.validate_http2_frame_size(16777216) assert "must be between 16384 and 16777215" in str(exc_info.value) def test_validate_zero_raises(self): """Zero is below minimum and should raise ValueError.""" with pytest.raises(ValueError): config.validate_http2_frame_size(0) def test_validate_negative_raises(self): """Negative values should raise ValueError.""" with pytest.raises(ValueError): config.validate_http2_frame_size(-1)