Spaces:
Paused
Paused
| 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) | |