import pytest from multidict import MultiDict from grpclib.metadata import Deadline from grpclib.metadata import encode_timeout, decode_timeout from grpclib.metadata import encode_metadata, decode_metadata from grpclib.metadata import encode_grpc_message, decode_grpc_message @pytest.mark.parametrize('value, expected', [ (100, '100S'), (15, '15S'), (7, '7000m'), (0.02, '20m'), (0.001, '1000u'), (0.00002, '20u'), (0.000001, '1000n'), (0.00000002, '20n'), ]) def test_encode_timeout(value, expected): assert encode_timeout(value) == expected @pytest.mark.parametrize('value, expected', [ ('5H', 5 * 3600), ('4M', 4 * 60), ('3S', 3), ('200m', pytest.approx(0.2)), ('100u', pytest.approx(0.0001)), ('50n', pytest.approx(0.00000005)), ]) def test_decode_timeout(value, expected): assert decode_timeout(value) == expected def test_deadline(): assert Deadline.from_timeout(1) < Deadline.from_timeout(2) with pytest.raises(TypeError) as err: Deadline.from_timeout(1) < 'invalid' err.match('comparison is not supported between instances ' 'of \'Deadline\' and \'str\'') assert Deadline(_timestamp=1) == Deadline(_timestamp=1) assert Deadline.from_timeout(1) != 'invalid' @pytest.mark.parametrize('value, output', [ ('а^2+б^2=ц^2', '%D0%B0^2+%D0%B1^2=%D1%86^2'), ]) def test_grpc_message_encoding(value, output): assert encode_grpc_message(value) == output encode_grpc_message(value).encode('ascii') assert decode_grpc_message(output) == value def test_grpc_message_decode_safe(): # 0xFF is invalid byte in utf-8: # https://www.cl.cam.ac.uk/~mgk25/ucs/examples/UTF-8-test.txt assert (decode_grpc_message('%FF^2+%FF^2=%FF^2') == '\ufffd^2+\ufffd^2=\ufffd^2') @pytest.mark.parametrize('value, output', [ ({'regular': 'value'}, [('regular', 'value')]), # dict-like ([('regular', 'value')], [('regular', 'value')]), # list of pairs ({'binary-bin': b'value'}, [('binary-bin', 'dmFsdWU')]) ]) def test_encode_metadata(value, output): assert encode_metadata(value) == output def test_encode_metadata_errors(): with pytest.raises(TypeError) as e1: encode_metadata({'regular': b'invalid'}) e1.match('Invalid metadata value type, str expected') with pytest.raises(TypeError) as e2: encode_metadata({'binary-bin': 'invalid'}) e2.match('Invalid metadata value type, bytes expected') @pytest.mark.parametrize('key', [ 'grpc-internal', 'Upper-Case', 'invalid~character', ' spaces ', 'new-line\n', ]) def test_encode_metadata_invalid_key(key): with pytest.raises(ValueError) as err: encode_metadata({key: 'anything'}) err.match('Invalid metadata key') @pytest.mark.parametrize('value', [ 'new-line\n', ]) def test_encode_metadata_invalid_value(value): with pytest.raises(ValueError) as err: encode_metadata({'foo': value}) err.match('Invalid metadata value') def test_decode_metadata_empty(): metadata = decode_metadata([ (':method', 'POST'), ('te', 'trailers'), ('content-type', 'application/grpc'), ('user-agent', 'Test'), ('grpc-timeout', '100m'), ]) assert metadata == MultiDict() @pytest.mark.parametrize('key, value, expected', [ ('regular', 'value', 'value'), ('binary-bin', 'dmFsdWU', b'value'), ]) def test_decode_metadata_regular(key, value, expected): metadata = decode_metadata([ (':method', 'POST'), ('te', 'trailers'), ('content-type', 'application/grpc'), ('user-agent', 'Test'), ('grpc-timeout', '100m'), (key, value), ]) assert metadata == MultiDict({key: expected}) assert type(metadata[key]) is type(expected)