You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							272 lines
						
					
					
						
							8.7 KiB
						
					
					
				
			
		
		
	
	
							272 lines
						
					
					
						
							8.7 KiB
						
					
					
				import pytest
 | 
						|
 | 
						|
from .._events import (
 | 
						|
    ConnectionClosed,
 | 
						|
    Data,
 | 
						|
    EndOfMessage,
 | 
						|
    Event,
 | 
						|
    InformationalResponse,
 | 
						|
    Request,
 | 
						|
    Response,
 | 
						|
)
 | 
						|
from .._state import (
 | 
						|
    _SWITCH_CONNECT,
 | 
						|
    _SWITCH_UPGRADE,
 | 
						|
    CLIENT,
 | 
						|
    CLOSED,
 | 
						|
    ConnectionState,
 | 
						|
    DONE,
 | 
						|
    IDLE,
 | 
						|
    MIGHT_SWITCH_PROTOCOL,
 | 
						|
    MUST_CLOSE,
 | 
						|
    SEND_BODY,
 | 
						|
    SEND_RESPONSE,
 | 
						|
    SERVER,
 | 
						|
    SWITCHED_PROTOCOL,
 | 
						|
)
 | 
						|
from .._util import LocalProtocolError
 | 
						|
 | 
						|
 | 
						|
def test_ConnectionState() -> None:
 | 
						|
    cs = ConnectionState()
 | 
						|
 | 
						|
    # Basic event-triggered transitions
 | 
						|
 | 
						|
    assert cs.states == {CLIENT: IDLE, SERVER: IDLE}
 | 
						|
 | 
						|
    cs.process_event(CLIENT, Request)
 | 
						|
    # The SERVER-Request special case:
 | 
						|
    assert cs.states == {CLIENT: SEND_BODY, SERVER: SEND_RESPONSE}
 | 
						|
 | 
						|
    # Illegal transitions raise an error and nothing happens
 | 
						|
    with pytest.raises(LocalProtocolError):
 | 
						|
        cs.process_event(CLIENT, Request)
 | 
						|
    assert cs.states == {CLIENT: SEND_BODY, SERVER: SEND_RESPONSE}
 | 
						|
 | 
						|
    cs.process_event(SERVER, InformationalResponse)
 | 
						|
    assert cs.states == {CLIENT: SEND_BODY, SERVER: SEND_RESPONSE}
 | 
						|
 | 
						|
    cs.process_event(SERVER, Response)
 | 
						|
    assert cs.states == {CLIENT: SEND_BODY, SERVER: SEND_BODY}
 | 
						|
 | 
						|
    cs.process_event(CLIENT, EndOfMessage)
 | 
						|
    cs.process_event(SERVER, EndOfMessage)
 | 
						|
    assert cs.states == {CLIENT: DONE, SERVER: DONE}
 | 
						|
 | 
						|
    # State-triggered transition
 | 
						|
 | 
						|
    cs.process_event(SERVER, ConnectionClosed)
 | 
						|
    assert cs.states == {CLIENT: MUST_CLOSE, SERVER: CLOSED}
 | 
						|
 | 
						|
 | 
						|
def test_ConnectionState_keep_alive() -> None:
 | 
						|
    # keep_alive = False
 | 
						|
    cs = ConnectionState()
 | 
						|
    cs.process_event(CLIENT, Request)
 | 
						|
    cs.process_keep_alive_disabled()
 | 
						|
    cs.process_event(CLIENT, EndOfMessage)
 | 
						|
    assert cs.states == {CLIENT: MUST_CLOSE, SERVER: SEND_RESPONSE}
 | 
						|
 | 
						|
    cs.process_event(SERVER, Response)
 | 
						|
    cs.process_event(SERVER, EndOfMessage)
 | 
						|
    assert cs.states == {CLIENT: MUST_CLOSE, SERVER: MUST_CLOSE}
 | 
						|
 | 
						|
 | 
						|
def test_ConnectionState_keep_alive_in_DONE() -> None:
 | 
						|
    # Check that if keep_alive is disabled when the CLIENT is already in DONE,
 | 
						|
    # then this is sufficient to immediately trigger the DONE -> MUST_CLOSE
 | 
						|
    # transition
 | 
						|
    cs = ConnectionState()
 | 
						|
    cs.process_event(CLIENT, Request)
 | 
						|
    cs.process_event(CLIENT, EndOfMessage)
 | 
						|
    assert cs.states[CLIENT] is DONE
 | 
						|
    cs.process_keep_alive_disabled()
 | 
						|
    assert cs.states[CLIENT] is MUST_CLOSE
 | 
						|
 | 
						|
 | 
						|
def test_ConnectionState_switch_denied() -> None:
 | 
						|
    for switch_type in (_SWITCH_CONNECT, _SWITCH_UPGRADE):
 | 
						|
        for deny_early in (True, False):
 | 
						|
            cs = ConnectionState()
 | 
						|
            cs.process_client_switch_proposal(switch_type)
 | 
						|
            cs.process_event(CLIENT, Request)
 | 
						|
            cs.process_event(CLIENT, Data)
 | 
						|
            assert cs.states == {CLIENT: SEND_BODY, SERVER: SEND_RESPONSE}
 | 
						|
 | 
						|
            assert switch_type in cs.pending_switch_proposals
 | 
						|
 | 
						|
            if deny_early:
 | 
						|
                # before client reaches DONE
 | 
						|
                cs.process_event(SERVER, Response)
 | 
						|
                assert not cs.pending_switch_proposals
 | 
						|
 | 
						|
            cs.process_event(CLIENT, EndOfMessage)
 | 
						|
 | 
						|
            if deny_early:
 | 
						|
                assert cs.states == {CLIENT: DONE, SERVER: SEND_BODY}
 | 
						|
            else:
 | 
						|
                assert cs.states == {
 | 
						|
                    CLIENT: MIGHT_SWITCH_PROTOCOL,
 | 
						|
                    SERVER: SEND_RESPONSE,
 | 
						|
                }
 | 
						|
 | 
						|
                cs.process_event(SERVER, InformationalResponse)
 | 
						|
                assert cs.states == {
 | 
						|
                    CLIENT: MIGHT_SWITCH_PROTOCOL,
 | 
						|
                    SERVER: SEND_RESPONSE,
 | 
						|
                }
 | 
						|
 | 
						|
                cs.process_event(SERVER, Response)
 | 
						|
                assert cs.states == {CLIENT: DONE, SERVER: SEND_BODY}
 | 
						|
                assert not cs.pending_switch_proposals
 | 
						|
 | 
						|
 | 
						|
_response_type_for_switch = {
 | 
						|
    _SWITCH_UPGRADE: InformationalResponse,
 | 
						|
    _SWITCH_CONNECT: Response,
 | 
						|
    None: Response,
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
def test_ConnectionState_protocol_switch_accepted() -> None:
 | 
						|
    for switch_event in [_SWITCH_UPGRADE, _SWITCH_CONNECT]:
 | 
						|
        cs = ConnectionState()
 | 
						|
        cs.process_client_switch_proposal(switch_event)
 | 
						|
        cs.process_event(CLIENT, Request)
 | 
						|
        cs.process_event(CLIENT, Data)
 | 
						|
        assert cs.states == {CLIENT: SEND_BODY, SERVER: SEND_RESPONSE}
 | 
						|
 | 
						|
        cs.process_event(CLIENT, EndOfMessage)
 | 
						|
        assert cs.states == {CLIENT: MIGHT_SWITCH_PROTOCOL, SERVER: SEND_RESPONSE}
 | 
						|
 | 
						|
        cs.process_event(SERVER, InformationalResponse)
 | 
						|
        assert cs.states == {CLIENT: MIGHT_SWITCH_PROTOCOL, SERVER: SEND_RESPONSE}
 | 
						|
 | 
						|
        cs.process_event(SERVER, _response_type_for_switch[switch_event], switch_event)
 | 
						|
        assert cs.states == {CLIENT: SWITCHED_PROTOCOL, SERVER: SWITCHED_PROTOCOL}
 | 
						|
 | 
						|
 | 
						|
def test_ConnectionState_double_protocol_switch() -> None:
 | 
						|
    # CONNECT + Upgrade is legal! Very silly, but legal. So we support
 | 
						|
    # it. Because sometimes doing the silly thing is easier than not.
 | 
						|
    for server_switch in [None, _SWITCH_UPGRADE, _SWITCH_CONNECT]:
 | 
						|
        cs = ConnectionState()
 | 
						|
        cs.process_client_switch_proposal(_SWITCH_UPGRADE)
 | 
						|
        cs.process_client_switch_proposal(_SWITCH_CONNECT)
 | 
						|
        cs.process_event(CLIENT, Request)
 | 
						|
        cs.process_event(CLIENT, EndOfMessage)
 | 
						|
        assert cs.states == {CLIENT: MIGHT_SWITCH_PROTOCOL, SERVER: SEND_RESPONSE}
 | 
						|
        cs.process_event(
 | 
						|
            SERVER, _response_type_for_switch[server_switch], server_switch
 | 
						|
        )
 | 
						|
        if server_switch is None:
 | 
						|
            assert cs.states == {CLIENT: DONE, SERVER: SEND_BODY}
 | 
						|
        else:
 | 
						|
            assert cs.states == {CLIENT: SWITCHED_PROTOCOL, SERVER: SWITCHED_PROTOCOL}
 | 
						|
 | 
						|
 | 
						|
def test_ConnectionState_inconsistent_protocol_switch() -> None:
 | 
						|
    for client_switches, server_switch in [
 | 
						|
        ([], _SWITCH_CONNECT),
 | 
						|
        ([], _SWITCH_UPGRADE),
 | 
						|
        ([_SWITCH_UPGRADE], _SWITCH_CONNECT),
 | 
						|
        ([_SWITCH_CONNECT], _SWITCH_UPGRADE),
 | 
						|
    ]:
 | 
						|
        cs = ConnectionState()
 | 
						|
        for client_switch in client_switches:  # type: ignore[attr-defined]
 | 
						|
            cs.process_client_switch_proposal(client_switch)
 | 
						|
        cs.process_event(CLIENT, Request)
 | 
						|
        with pytest.raises(LocalProtocolError):
 | 
						|
            cs.process_event(SERVER, Response, server_switch)
 | 
						|
 | 
						|
 | 
						|
def test_ConnectionState_keepalive_protocol_switch_interaction() -> None:
 | 
						|
    # keep_alive=False + pending_switch_proposals
 | 
						|
    cs = ConnectionState()
 | 
						|
    cs.process_client_switch_proposal(_SWITCH_UPGRADE)
 | 
						|
    cs.process_event(CLIENT, Request)
 | 
						|
    cs.process_keep_alive_disabled()
 | 
						|
    cs.process_event(CLIENT, Data)
 | 
						|
    assert cs.states == {CLIENT: SEND_BODY, SERVER: SEND_RESPONSE}
 | 
						|
 | 
						|
    # the protocol switch "wins"
 | 
						|
    cs.process_event(CLIENT, EndOfMessage)
 | 
						|
    assert cs.states == {CLIENT: MIGHT_SWITCH_PROTOCOL, SERVER: SEND_RESPONSE}
 | 
						|
 | 
						|
    # but when the server denies the request, keep_alive comes back into play
 | 
						|
    cs.process_event(SERVER, Response)
 | 
						|
    assert cs.states == {CLIENT: MUST_CLOSE, SERVER: SEND_BODY}
 | 
						|
 | 
						|
 | 
						|
def test_ConnectionState_reuse() -> None:
 | 
						|
    cs = ConnectionState()
 | 
						|
 | 
						|
    with pytest.raises(LocalProtocolError):
 | 
						|
        cs.start_next_cycle()
 | 
						|
 | 
						|
    cs.process_event(CLIENT, Request)
 | 
						|
    cs.process_event(CLIENT, EndOfMessage)
 | 
						|
 | 
						|
    with pytest.raises(LocalProtocolError):
 | 
						|
        cs.start_next_cycle()
 | 
						|
 | 
						|
    cs.process_event(SERVER, Response)
 | 
						|
    cs.process_event(SERVER, EndOfMessage)
 | 
						|
 | 
						|
    cs.start_next_cycle()
 | 
						|
    assert cs.states == {CLIENT: IDLE, SERVER: IDLE}
 | 
						|
 | 
						|
    # No keepalive
 | 
						|
 | 
						|
    cs.process_event(CLIENT, Request)
 | 
						|
    cs.process_keep_alive_disabled()
 | 
						|
    cs.process_event(CLIENT, EndOfMessage)
 | 
						|
    cs.process_event(SERVER, Response)
 | 
						|
    cs.process_event(SERVER, EndOfMessage)
 | 
						|
 | 
						|
    with pytest.raises(LocalProtocolError):
 | 
						|
        cs.start_next_cycle()
 | 
						|
 | 
						|
    # One side closed
 | 
						|
 | 
						|
    cs = ConnectionState()
 | 
						|
    cs.process_event(CLIENT, Request)
 | 
						|
    cs.process_event(CLIENT, EndOfMessage)
 | 
						|
    cs.process_event(CLIENT, ConnectionClosed)
 | 
						|
    cs.process_event(SERVER, Response)
 | 
						|
    cs.process_event(SERVER, EndOfMessage)
 | 
						|
 | 
						|
    with pytest.raises(LocalProtocolError):
 | 
						|
        cs.start_next_cycle()
 | 
						|
 | 
						|
    # Succesful protocol switch
 | 
						|
 | 
						|
    cs = ConnectionState()
 | 
						|
    cs.process_client_switch_proposal(_SWITCH_UPGRADE)
 | 
						|
    cs.process_event(CLIENT, Request)
 | 
						|
    cs.process_event(CLIENT, EndOfMessage)
 | 
						|
    cs.process_event(SERVER, InformationalResponse, _SWITCH_UPGRADE)
 | 
						|
 | 
						|
    with pytest.raises(LocalProtocolError):
 | 
						|
        cs.start_next_cycle()
 | 
						|
 | 
						|
    # Failed protocol switch
 | 
						|
 | 
						|
    cs = ConnectionState()
 | 
						|
    cs.process_client_switch_proposal(_SWITCH_UPGRADE)
 | 
						|
    cs.process_event(CLIENT, Request)
 | 
						|
    cs.process_event(CLIENT, EndOfMessage)
 | 
						|
    cs.process_event(SERVER, Response)
 | 
						|
    cs.process_event(SERVER, EndOfMessage)
 | 
						|
 | 
						|
    cs.start_next_cycle()
 | 
						|
    assert cs.states == {CLIENT: IDLE, SERVER: IDLE}
 | 
						|
 | 
						|
 | 
						|
def test_server_request_is_illegal() -> None:
 | 
						|
    # There used to be a bug in how we handled the Request special case that
 | 
						|
    # made this allowed...
 | 
						|
    cs = ConnectionState()
 | 
						|
    with pytest.raises(LocalProtocolError):
 | 
						|
        cs.process_event(SERVER, Request)
 |