1# ------------------------------------
2# Copyright (c) Microsoft Corporation.
3# Licensed under the MIT License.
4# ------------------------------------
5"""Tests for the HttpLoggingPolicy."""
6import pytest
7import logging
8import types
9try:
10    from unittest.mock import Mock
11except ImportError:  # python < 3.3
12    from mock import Mock  # type: ignore
13from azure.core.pipeline import (
14    PipelineResponse,
15    PipelineRequest,
16    PipelineContext
17)
18from azure.core.pipeline.policies import (
19    HttpLoggingPolicy,
20)
21from utils import HTTP_RESPONSES, create_http_response, request_and_responses_product
22from azure.core.pipeline._tools import is_rest
23
24@pytest.mark.parametrize("http_request,http_response", request_and_responses_product(HTTP_RESPONSES))
25def test_http_logger(http_request, http_response):
26
27    class MockHandler(logging.Handler):
28        def __init__(self):
29            super(MockHandler, self).__init__()
30            self.messages = []
31        def reset(self):
32            self.messages = []
33        def emit(self, record):
34            self.messages.append(record)
35    mock_handler = MockHandler()
36
37    logger = logging.getLogger("testlogger")
38    logger.addHandler(mock_handler)
39    logger.setLevel(logging.DEBUG)
40
41    policy = HttpLoggingPolicy(logger=logger)
42
43    universal_request = http_request('GET', 'http://localhost/')
44    http_response = create_http_response(http_response, universal_request, None)
45    http_response.status_code = 202
46    request = PipelineRequest(universal_request, PipelineContext(None))
47
48    # Basics
49
50    policy.on_request(request)
51    response = PipelineResponse(request, http_response, request.context)
52    policy.on_response(request, response)
53
54    assert all(m.levelname == 'INFO' for m in mock_handler.messages)
55    assert len(mock_handler.messages) == 2
56    messages_request = mock_handler.messages[0].message.split("/n")
57    messages_response = mock_handler.messages[1].message.split("/n")
58    assert messages_request[0] == "Request URL: 'http://localhost/'"
59    assert messages_request[1] == "Request method: 'GET'"
60    assert messages_request[2] == 'Request headers:'
61    assert messages_request[3] == 'No body was attached to the request'
62    assert messages_response[0] == 'Response status: 202'
63    assert messages_response[1] == 'Response headers:'
64
65    mock_handler.reset()
66
67    # Let's make this request a failure, retried twice
68
69    policy.on_request(request)
70    response = PipelineResponse(request, http_response, request.context)
71    policy.on_response(request, response)
72
73    policy.on_request(request)
74    response = PipelineResponse(request, http_response, request.context)
75    policy.on_response(request, response)
76
77    assert all(m.levelname == 'INFO' for m in mock_handler.messages)
78    assert len(mock_handler.messages) == 4
79    messages_request1 = mock_handler.messages[0].message.split("/n")
80    messages_response1 = mock_handler.messages[1].message.split("/n")
81    messages_request2 = mock_handler.messages[2].message.split("/n")
82    messages_response2 = mock_handler.messages[3].message.split("/n")
83    assert messages_request1[0] == "Request URL: 'http://localhost/'"
84    assert messages_request1[1] == "Request method: 'GET'"
85    assert messages_request1[2] == 'Request headers:'
86    assert messages_request1[3] == 'No body was attached to the request'
87    assert messages_response1[0] == 'Response status: 202'
88    assert messages_response1[1] == 'Response headers:'
89    assert messages_request2[0] == "Request URL: 'http://localhost/'"
90    assert messages_request2[1] == "Request method: 'GET'"
91    assert messages_request2[2] == 'Request headers:'
92    assert messages_request2[3] == 'No body was attached to the request'
93    assert messages_response2[0] == 'Response status: 202'
94    assert messages_response2[1] == 'Response headers:'
95
96    mock_handler.reset()
97
98    # Headers and query parameters
99
100    policy.allowed_query_params = ['country']
101
102    universal_request.headers = {
103        "Accept": "Caramel",
104        "Hate": "Chocolat",
105    }
106    http_response.headers = {
107        "Content-Type": "Caramel",
108        "HateToo": "Chocolat",
109    }
110    universal_request.url = "http://localhost/?country=france&city=aix"
111
112    policy.on_request(request)
113    response = PipelineResponse(request, http_response, request.context)
114    policy.on_response(request, response)
115
116    assert all(m.levelname == 'INFO' for m in mock_handler.messages)
117    assert len(mock_handler.messages) == 2
118    messages_request = mock_handler.messages[0].message.split("/n")
119    messages_response = mock_handler.messages[1].message.split("/n")
120    assert messages_request[0] == "Request URL: 'http://localhost/?country=france&city=REDACTED'"
121    assert messages_request[1] == "Request method: 'GET'"
122    assert messages_request[2] == "Request headers:"
123    # Dict not ordered in Python, exact logging order doesn't matter
124    assert set([
125        messages_request[3],
126        messages_request[4]
127    ]) == set([
128        "    'Accept': 'Caramel'",
129        "    'Hate': 'REDACTED'"
130    ])
131    assert messages_request[5] == 'No body was attached to the request'
132    assert messages_response[0] == "Response status: 202"
133    assert messages_response[1] == "Response headers:"
134    # Dict not ordered in Python, exact logging order doesn't matter
135    assert set([
136        messages_response[2],
137        messages_response[3]
138    ]) == set([
139        "    'Content-Type': 'Caramel'",
140        "    'HateToo': 'REDACTED'"
141    ])
142
143    mock_handler.reset()
144
145
146
147@pytest.mark.parametrize("http_request,http_response", request_and_responses_product(HTTP_RESPONSES))
148def test_http_logger_operation_level(http_request, http_response):
149
150    class MockHandler(logging.Handler):
151        def __init__(self):
152            super(MockHandler, self).__init__()
153            self.messages = []
154        def reset(self):
155            self.messages = []
156        def emit(self, record):
157            self.messages.append(record)
158    mock_handler = MockHandler()
159
160    logger = logging.getLogger("testlogger")
161    logger.addHandler(mock_handler)
162    logger.setLevel(logging.DEBUG)
163
164    policy = HttpLoggingPolicy()
165    kwargs={'logger': logger}
166
167    universal_request = http_request('GET', 'http://localhost/')
168    http_response = create_http_response(http_response, universal_request, None)
169    http_response.status_code = 202
170    request = PipelineRequest(universal_request, PipelineContext(None, **kwargs))
171
172    # Basics
173
174    policy.on_request(request)
175    response = PipelineResponse(request, http_response, request.context)
176    policy.on_response(request, response)
177
178    assert all(m.levelname == 'INFO' for m in mock_handler.messages)
179    assert len(mock_handler.messages) == 2
180    messages_request = mock_handler.messages[0].message.split("/n")
181    messages_response = mock_handler.messages[1].message.split("/n")
182    assert messages_request[0] == "Request URL: 'http://localhost/'"
183    assert messages_request[1] == "Request method: 'GET'"
184    assert messages_request[2] == 'Request headers:'
185    assert messages_request[3] == 'No body was attached to the request'
186    assert messages_response[0] == 'Response status: 202'
187    assert messages_response[1] == 'Response headers:'
188
189    mock_handler.reset()
190
191    # Let's make this request a failure, retried twice
192
193    request = PipelineRequest(universal_request, PipelineContext(None, **kwargs))
194
195    policy.on_request(request)
196    response = PipelineResponse(request, http_response, request.context)
197    policy.on_response(request, response)
198
199    policy.on_request(request)
200    response = PipelineResponse(request, http_response, request.context)
201    policy.on_response(request, response)
202
203    assert all(m.levelname == 'INFO' for m in mock_handler.messages)
204    assert len(mock_handler.messages) == 4
205    messages_request1 = mock_handler.messages[0].message.split("/n")
206    messages_response1 = mock_handler.messages[1].message.split("/n")
207    messages_request2 = mock_handler.messages[2].message.split("/n")
208    messages_response2 = mock_handler.messages[3].message.split("/n")
209    assert messages_request1[0] == "Request URL: 'http://localhost/'"
210    assert messages_request1[1] == "Request method: 'GET'"
211    assert messages_request1[2] == 'Request headers:'
212    assert messages_request1[3] == 'No body was attached to the request'
213    assert messages_response1[0] == 'Response status: 202'
214    assert messages_response1[1] == 'Response headers:'
215    assert messages_request2[0] == "Request URL: 'http://localhost/'"
216    assert messages_request2[1] == "Request method: 'GET'"
217    assert messages_request2[2] == 'Request headers:'
218    assert messages_request2[3] == 'No body was attached to the request'
219    assert messages_response2[0] == 'Response status: 202'
220    assert messages_response2[1] == 'Response headers:'
221
222    mock_handler.reset()
223
224
225@pytest.mark.parametrize("http_request,http_response", request_and_responses_product(HTTP_RESPONSES))
226def test_http_logger_with_body(http_request, http_response):
227
228    class MockHandler(logging.Handler):
229        def __init__(self):
230            super(MockHandler, self).__init__()
231            self.messages = []
232        def reset(self):
233            self.messages = []
234        def emit(self, record):
235            self.messages.append(record)
236    mock_handler = MockHandler()
237
238    logger = logging.getLogger("testlogger")
239    logger.addHandler(mock_handler)
240    logger.setLevel(logging.DEBUG)
241
242    policy = HttpLoggingPolicy(logger=logger)
243
244    universal_request = http_request('GET', 'http://localhost/')
245    universal_request.body = "testbody"
246    http_response = create_http_response(http_response, universal_request, None)
247    http_response.status_code = 202
248    request = PipelineRequest(universal_request, PipelineContext(None))
249
250    policy.on_request(request)
251    response = PipelineResponse(request, http_response, request.context)
252    policy.on_response(request, response)
253
254    assert all(m.levelname == 'INFO' for m in mock_handler.messages)
255    assert len(mock_handler.messages) == 2
256    messages_request = mock_handler.messages[0].message.split("/n")
257    messages_response = mock_handler.messages[1].message.split("/n")
258    assert messages_request[0] == "Request URL: 'http://localhost/'"
259    assert messages_request[1] == "Request method: 'GET'"
260    assert messages_request[2] == 'Request headers:'
261    assert messages_request[3] == 'A body is sent with the request'
262    assert messages_response[0] == 'Response status: 202'
263    assert messages_response[1] == 'Response headers:'
264
265    mock_handler.reset()
266
267
268@pytest.mark.parametrize("http_request,http_response", request_and_responses_product(HTTP_RESPONSES))
269def test_http_logger_with_generator_body(http_request, http_response):
270
271    class MockHandler(logging.Handler):
272        def __init__(self):
273            super(MockHandler, self).__init__()
274            self.messages = []
275        def reset(self):
276            self.messages = []
277        def emit(self, record):
278            self.messages.append(record)
279    mock_handler = MockHandler()
280
281    logger = logging.getLogger("testlogger")
282    logger.addHandler(mock_handler)
283    logger.setLevel(logging.DEBUG)
284
285    policy = HttpLoggingPolicy(logger=logger)
286
287    universal_request = http_request('GET', 'http://localhost/')
288    mock = Mock()
289    mock.__class__ = types.GeneratorType
290    universal_request.body = mock
291    http_response = create_http_response(http_response, universal_request, None)
292    http_response.status_code = 202
293    request = PipelineRequest(universal_request, PipelineContext(None))
294
295    policy.on_request(request)
296    response = PipelineResponse(request, http_response, request.context)
297    policy.on_response(request, response)
298
299    assert all(m.levelname == 'INFO' for m in mock_handler.messages)
300    assert len(mock_handler.messages) == 2
301    messages_request = mock_handler.messages[0].message.split("/n")
302    messages_response = mock_handler.messages[1].message.split("/n")
303    assert messages_request[0] == "Request URL: 'http://localhost/'"
304    assert messages_request[1] == "Request method: 'GET'"
305    assert messages_request[2] == 'Request headers:'
306    assert messages_request[3] == 'File upload'
307    assert messages_response[0] == 'Response status: 202'
308    assert messages_response[1] == 'Response headers:'
309
310    mock_handler.reset()
311