mindflayer / python-mocket

a socket mock framework - for all kinds of socket animals, web-clients included
BSD 3-Clause "New" or "Revised" License
283 stars 42 forks source link

Socket recording with multiple connections across several sendall() has issues if there is a common initialization #146

Closed fvigo closed 3 years ago

fvigo commented 3 years ago

This is a tricky scenario that happened in a corner case:

This doesn't apply with HTTP because connections are mostly stateless, but can happen with other protocols (i.e. HTTP proxy)

Each connection is as follows: Step #0 sendall(authentication_string): i.e. "AUTHENTICATE user:password" Step #1 read(authentication_confirmation): i.e. "AUTHENTICATION OK" Step #2 sendall(some command): i.e. "EXECUTE COMMAND x"

Different connections execute different commands (i.e. connection#1 sends "EXECUTE COMMAND x", connection#2 sends "EXECUTE COMMAND y"), but they require authentication to be performed in order to accept commands.

Problematic scenario pseudocode. At the beginning no data is present in the JSON files.

Mocket.enable('some_log_file','.')

s1 = socket() -> we get a MocketSocket
s1.connect(destination_ip, destination_port)
s1.sendall('AUTHENTICATE user:password') -> this gets sent through a true_socket() as there is no cache for this message
s1.read() -> 'AUTHENTICATION OK' -> data stored in JSON file
s1.write('EXECUTE COMMAND x') -> this gets send through a true_socket() as there is no cache for this message
s1.read() -> 'RESULT OF COMMAND x' -> data stored in JSON file
s1.close()

s2 = socket() -> we get a MocketSocket
s2.connect(destination_ip, destination_port) -> connect to the same IP and port as before
s2.sendall('AUTHENTICATE user:password') -> This is never sent to a true_socket, as the request is identical to the one performed with s1, so the Entry is present in the JSON file and a mocked response is provided
s2.read() -> 'AUTHENTICATION OK' -> from the JSON file
s2.write('EXECUTE COMMAND y') -> this gets send through a true_socket() as there is no cache for this message
s2.read() -> 'ERROR: UNAUTHENTICATED' -> we get an error from the server because the authentication message for this connection has never been sent! 
s2.close()

This is not really a bug: mocket is behaving as expected. However, how can you test this scenario? In my case I worked around it adding some entropy in the authentication message to make it different every time, but it's not always possible or easy to achieve.

A possible solution could be provide an option to tell mocket that is working in "Record only" mode, so that cached data is ignored.

Thanks!

mindflayer commented 3 years ago

Why are you sharing the mocket session?

mindflayer commented 3 years ago

I guess you could do something like:

with Mocketizer(truesocket_recording_dir="socket1.json"):
    s1.whatever()
    ...

with Mocketizer(truesocket_recording_dir="socket2.json"):
    s2.whatever()
    ...
fvigo commented 3 years ago

Because all the connections happen serially i.e. my pseudocode is:

def scan():
  result1 = do_something(command_x)
  result2 = do_something(command_y)
  final_result = compute_final_result(result1, result2)
  return final_result

And in the UT I do:

Mocket.Enter('results','.')
result = scan()
assert result == expected_result
mindflayer commented 3 years ago

In this case you could test separately the different do_something with mocket, then test your compute_final_result with these two functions mocked using unittest.mock.

fvigo commented 3 years ago

Yes, that could work. My scenario is more complicated so I solved it in a different way. As mentioned it is not a bug and with proper usage of the library it's possible to achieve the result.

The problem was mainly troubleshooting to understand the behavior, it's probably something to mention in the documentation or maybe (feature request) adding some additional logging capabilities for debugging purposes.

Thanks for the feedback!

mindflayer commented 3 years ago

Regarding logs, I tried many times to approach the subject but still did not find a good way to do it. Feel free to propose a solution, if you have something in mind.