httpdbg 0.29.1__tar.gz → 0.30.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. {httpdbg-0.29.1 → httpdbg-0.30.0}/PKG-INFO +2 -1
  2. {httpdbg-0.29.1 → httpdbg-0.30.0}/README.md +1 -0
  3. {httpdbg-0.29.1 → httpdbg-0.30.0}/httpdbg/__init__.py +1 -1
  4. {httpdbg-0.29.1 → httpdbg-0.30.0}/httpdbg/__main__.py +1 -1
  5. {httpdbg-0.29.1 → httpdbg-0.30.0}/httpdbg/hooks/all.py +4 -2
  6. {httpdbg-0.29.1 → httpdbg-0.30.0}/httpdbg/hooks/external.py +1 -0
  7. httpdbg-0.30.0/httpdbg/hooks/unittest.py +263 -0
  8. httpdbg-0.30.0/httpdbg/mode_console.py +81 -0
  9. {httpdbg-0.29.1 → httpdbg-0.30.0}/httpdbg/mode_script.py +11 -0
  10. {httpdbg-0.29.1 → httpdbg-0.30.0}/httpdbg.egg-info/PKG-INFO +2 -1
  11. {httpdbg-0.29.1 → httpdbg-0.30.0}/httpdbg.egg-info/SOURCES.txt +3 -0
  12. {httpdbg-0.29.1 → httpdbg-0.30.0}/tests/test_api.py +2 -2
  13. httpdbg-0.30.0/tests/test_hook_pytest.py +38 -0
  14. httpdbg-0.30.0/tests/test_hook_unittest.py +132 -0
  15. {httpdbg-0.29.1 → httpdbg-0.30.0}/tests/test_initiator.py +6 -6
  16. {httpdbg-0.29.1 → httpdbg-0.30.0}/tests/test_mode_console.py +29 -1
  17. {httpdbg-0.29.1 → httpdbg-0.30.0}/tests/test_mode_module_pytest.py +0 -25
  18. httpdbg-0.29.1/httpdbg/mode_console.py +0 -37
  19. {httpdbg-0.29.1 → httpdbg-0.30.0}/LICENSE +0 -0
  20. {httpdbg-0.29.1 → httpdbg-0.30.0}/httpdbg/args.py +0 -0
  21. {httpdbg-0.29.1 → httpdbg-0.30.0}/httpdbg/env.py +0 -0
  22. {httpdbg-0.29.1 → httpdbg-0.30.0}/httpdbg/exception.py +0 -0
  23. {httpdbg-0.29.1 → httpdbg-0.30.0}/httpdbg/hooks/__init__.py +0 -0
  24. {httpdbg-0.29.1 → httpdbg-0.30.0}/httpdbg/hooks/aiohttp.py +0 -0
  25. {httpdbg-0.29.1 → httpdbg-0.30.0}/httpdbg/hooks/generic.py +0 -0
  26. {httpdbg-0.29.1 → httpdbg-0.30.0}/httpdbg/hooks/httpx.py +0 -0
  27. {httpdbg-0.29.1 → httpdbg-0.30.0}/httpdbg/hooks/pytest.py +0 -0
  28. {httpdbg-0.29.1 → httpdbg-0.30.0}/httpdbg/hooks/requests.py +0 -0
  29. {httpdbg-0.29.1 → httpdbg-0.30.0}/httpdbg/hooks/socket.py +0 -0
  30. {httpdbg-0.29.1 → httpdbg-0.30.0}/httpdbg/hooks/urllib3.py +0 -0
  31. {httpdbg-0.29.1 → httpdbg-0.30.0}/httpdbg/hooks/utils.py +0 -0
  32. {httpdbg-0.29.1 → httpdbg-0.30.0}/httpdbg/initiator.py +0 -0
  33. {httpdbg-0.29.1 → httpdbg-0.30.0}/httpdbg/log.py +0 -0
  34. {httpdbg-0.29.1 → httpdbg-0.30.0}/httpdbg/mode_module.py +0 -0
  35. {httpdbg-0.29.1 → httpdbg-0.30.0}/httpdbg/preview.py +0 -0
  36. {httpdbg-0.29.1 → httpdbg-0.30.0}/httpdbg/records.py +0 -0
  37. {httpdbg-0.29.1 → httpdbg-0.30.0}/httpdbg/server.py +0 -0
  38. {httpdbg-0.29.1 → httpdbg-0.30.0}/httpdbg/utils.py +0 -0
  39. {httpdbg-0.29.1 → httpdbg-0.30.0}/httpdbg/webapp/__init__.py +0 -0
  40. {httpdbg-0.29.1 → httpdbg-0.30.0}/httpdbg/webapp/api.py +0 -0
  41. {httpdbg-0.29.1 → httpdbg-0.30.0}/httpdbg/webapp/app.py +0 -0
  42. {httpdbg-0.29.1 → httpdbg-0.30.0}/httpdbg/webapp/static/api.js +0 -0
  43. {httpdbg-0.29.1 → httpdbg-0.30.0}/httpdbg/webapp/static/column.css +0 -0
  44. {httpdbg-0.29.1 → httpdbg-0.30.0}/httpdbg/webapp/static/configuration.js +0 -0
  45. {httpdbg-0.29.1 → httpdbg-0.30.0}/httpdbg/webapp/static/detail.css +0 -0
  46. {httpdbg-0.29.1 → httpdbg-0.30.0}/httpdbg/webapp/static/favicon.ico +0 -0
  47. {httpdbg-0.29.1 → httpdbg-0.30.0}/httpdbg/webapp/static/filter.js +0 -0
  48. {httpdbg-0.29.1 → httpdbg-0.30.0}/httpdbg/webapp/static/icons/chevron-direction-top-outline-icon.svg +0 -0
  49. {httpdbg-0.29.1 → httpdbg-0.30.0}/httpdbg/webapp/static/icons/download-install-line-icon.svg +0 -0
  50. {httpdbg-0.29.1 → httpdbg-0.30.0}/httpdbg/webapp/static/icons/engine-gears-icon.svg +0 -0
  51. {httpdbg-0.29.1 → httpdbg-0.30.0}/httpdbg/webapp/static/icons/files-papers-outline-icon.svg +0 -0
  52. {httpdbg-0.29.1 → httpdbg-0.30.0}/httpdbg/webapp/static/icons/filter-outline-icon.svg +0 -0
  53. {httpdbg-0.29.1 → httpdbg-0.30.0}/httpdbg/webapp/static/icons/information-mark-circle-outline-icon.svg +0 -0
  54. {httpdbg-0.29.1 → httpdbg-0.30.0}/httpdbg/webapp/static/icons/math-multiplication-icon.svg +0 -0
  55. {httpdbg-0.29.1 → httpdbg-0.30.0}/httpdbg/webapp/static/icons/push-pin-green-icon.svg +0 -0
  56. {httpdbg-0.29.1 → httpdbg-0.30.0}/httpdbg/webapp/static/icons/question-mark-round-line-icon.svg +0 -0
  57. {httpdbg-0.29.1 → httpdbg-0.30.0}/httpdbg/webapp/static/icons/recycle-bin-line-icon.svg +0 -0
  58. {httpdbg-0.29.1 → httpdbg-0.30.0}/httpdbg/webapp/static/icons/wait-sandclock-icon.svg +0 -0
  59. {httpdbg-0.29.1 → httpdbg-0.30.0}/httpdbg/webapp/static/index.htm +0 -0
  60. {httpdbg-0.29.1 → httpdbg-0.30.0}/httpdbg/webapp/static/main.js +0 -0
  61. {httpdbg-0.29.1 → httpdbg-0.30.0}/httpdbg/webapp/static/mustache.min.js +0 -0
  62. {httpdbg-0.29.1 → httpdbg-0.30.0}/httpdbg/webapp/static/render.js +0 -0
  63. {httpdbg-0.29.1 → httpdbg-0.30.0}/httpdbg/webapp/static/style.css +0 -0
  64. {httpdbg-0.29.1 → httpdbg-0.30.0}/httpdbg/webapp/static/table.css +0 -0
  65. {httpdbg-0.29.1 → httpdbg-0.30.0}/httpdbg.egg-info/dependency_links.txt +0 -0
  66. {httpdbg-0.29.1 → httpdbg-0.30.0}/httpdbg.egg-info/entry_points.txt +0 -0
  67. {httpdbg-0.29.1 → httpdbg-0.30.0}/httpdbg.egg-info/requires.txt +0 -0
  68. {httpdbg-0.29.1 → httpdbg-0.30.0}/httpdbg.egg-info/top_level.txt +0 -0
  69. {httpdbg-0.29.1 → httpdbg-0.30.0}/pyproject.toml +0 -0
  70. {httpdbg-0.29.1 → httpdbg-0.30.0}/setup.cfg +0 -0
  71. {httpdbg-0.29.1 → httpdbg-0.30.0}/tests/test_cli.py +0 -0
  72. {httpdbg-0.29.1 → httpdbg-0.30.0}/tests/test_context_manager.py +0 -0
  73. {httpdbg-0.29.1 → httpdbg-0.30.0}/tests/test_hook_aiohttp.py +0 -0
  74. {httpdbg-0.29.1 → httpdbg-0.30.0}/tests/test_hook_httpx.py +0 -0
  75. {httpdbg-0.29.1 → httpdbg-0.30.0}/tests/test_hook_requests.py +0 -0
  76. {httpdbg-0.29.1 → httpdbg-0.30.0}/tests/test_hook_urllib3.py +0 -0
  77. {httpdbg-0.29.1 → httpdbg-0.30.0}/tests/test_mode_script.py +0 -0
  78. {httpdbg-0.29.1 → httpdbg-0.30.0}/tests/test_preview.py +0 -0
  79. {httpdbg-0.29.1 → httpdbg-0.30.0}/tests/test_server.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: httpdbg
3
- Version: 0.29.1
3
+ Version: 0.30.0
4
4
  Summary: A very simple tool to debug HTTP(S) client requests
5
5
  Author-email: cle-b <cle@tictac.pm>
6
6
  License: Apache-2.0
@@ -116,6 +116,7 @@ You can use any package as an initiator, this is not limited to HTTP requests.
116
116
  | httpx | supported |
117
117
  | aiohttp | supported |
118
118
  | pytest | supported |
119
+ | unittest | supported |
119
120
  | _your_package_ | yes, with the arg _-i your_package_ |
120
121
 
121
122
  ## configuration
@@ -88,6 +88,7 @@ You can use any package as an initiator, this is not limited to HTTP requests.
88
88
  | httpx | supported |
89
89
  | aiohttp | supported |
90
90
  | pytest | supported |
91
+ | unittest | supported |
91
92
  | _your_package_ | yes, with the arg _-i your_package_ |
92
93
 
93
94
  ## configuration
@@ -3,6 +3,6 @@ from httpdbg.hooks.all import httprecord
3
3
  from httpdbg.records import HTTPRecords
4
4
 
5
5
 
6
- __version__ = "0.29.1"
6
+ __version__ = "0.30.0"
7
7
 
8
8
  __all__ = ["httprecord", "HTTPRecords"]
@@ -35,7 +35,7 @@ def pyhttpdbg(params, subparams, test_mode=False):
35
35
  elif params.script:
36
36
  run_script(subparams)
37
37
  else:
38
- run_console(test_mode)
38
+ run_console(records, test_mode)
39
39
 
40
40
  if not (params.force_quit or test_mode):
41
41
  print_msg(f" httpdbg - HTTP(S) requests available at {url}")
@@ -11,6 +11,7 @@ from httpdbg.hooks.httpx import hook_httpx
11
11
  from httpdbg.hooks.pytest import hook_pytest
12
12
  from httpdbg.hooks.requests import hook_requests
13
13
  from httpdbg.hooks.socket import hook_socket
14
+ from httpdbg.hooks.unittest import hook_unittest
14
15
  from httpdbg.hooks.urllib3 import hook_urllib3
15
16
  from httpdbg.records import HTTPRecords
16
17
 
@@ -29,5 +30,6 @@ def httprecord(
29
30
  with hook_urllib3(records):
30
31
  with hook_aiohttp(records):
31
32
  with hook_pytest(records):
32
- with hook_generic(records, initiators):
33
- yield records
33
+ with hook_unittest(records):
34
+ with hook_generic(records, initiators):
35
+ yield records
@@ -15,6 +15,7 @@ from httpdbg.records import HTTPRecords
15
15
  @contextmanager
16
16
  def watcher_external(records: HTTPRecords) -> Generator[HTTPRecords, None, None]:
17
17
  try:
18
+ watcher = None
18
19
  if HTTPDBG_SUBPROCESS_DIR not in os.environ:
19
20
  with tempfile.TemporaryDirectory(
20
21
  prefix="httpdbg"
@@ -0,0 +1,263 @@
1
+ # -*- coding: utf-8 -*-
2
+ from collections.abc import Callable
3
+ from contextlib import contextmanager
4
+ import inspect
5
+ import os
6
+ import typing
7
+ from typing import Generator
8
+
9
+ from httpdbg.initiator import httpdbg_group
10
+ from httpdbg.initiator import httpdbg_tag
11
+ from httpdbg.hooks.utils import decorate
12
+ from httpdbg.hooks.utils import getcallargs
13
+ from httpdbg.hooks.utils import undecorate
14
+ from httpdbg.records import HTTPRecords
15
+
16
+
17
+ def set_hook_for_unittest_run(records: HTTPRecords, method: Callable):
18
+
19
+ def hook(*args, **kwargs):
20
+ callargs = getcallargs(method, *args, **kwargs)
21
+
22
+ if "self" in callargs:
23
+ test_case = callargs["self"]
24
+
25
+ label = "unknown test"
26
+ full_label = "unknown test"
27
+
28
+ method_name = getattr(test_case, "_testMethodName")
29
+ if method_name:
30
+ test_class_name = test_case.__class__.__name__
31
+ label = f"{test_class_name}::{method_name}"
32
+ full_label = label
33
+ test_module = inspect.getmodule(test_case.__class__)
34
+ test_module_path = getattr(test_module, "__file__")
35
+ if test_module_path:
36
+ test_module_path = os.path.relpath(test_module_path)
37
+ full_label = f"{test_module_path}::{full_label}"
38
+
39
+ with httpdbg_group(records, label, full_label):
40
+ ret = method(*args, **kwargs)
41
+ else:
42
+ ret = method(*args, **kwargs)
43
+ return ret
44
+
45
+ return hook
46
+
47
+
48
+ def set_hook_for_unittest_fixture(_: HTTPRecords, method: Callable):
49
+
50
+ def hook(*args, **kwargs):
51
+
52
+ if getattr(method, "__name__") == "_callSetUp":
53
+ with httpdbg_tag("setUp"):
54
+ return method(*args, **kwargs)
55
+ elif getattr(method, "__name__") == "_callTearDown":
56
+ with httpdbg_tag("tearDown"):
57
+ return method(*args, **kwargs)
58
+ else:
59
+ return method(*args, **kwargs)
60
+
61
+ return hook
62
+
63
+
64
+ def set_hook_for_unittest_class_setup(records: HTTPRecords, method: Callable):
65
+
66
+ def hook(*args, **kwargs):
67
+ callargs = getcallargs(method, *args, **kwargs)
68
+
69
+ if "test" in callargs:
70
+ test_case = callargs["test"]
71
+
72
+ test_class_name = test_case.__class__.__name__
73
+ label = f"{test_class_name}::setUpClass"
74
+ full_label = label
75
+
76
+ test_module = inspect.getmodule(test_case.__class__)
77
+ test_module_path = getattr(test_module, "__file__")
78
+ if test_module_path:
79
+ test_module_path = os.path.relpath(test_module_path)
80
+ full_label = f"{test_module_path}::{full_label}"
81
+
82
+ with httpdbg_group(records, label, full_label):
83
+ return method(*args, **kwargs)
84
+ else:
85
+ return method(*args, **kwargs)
86
+
87
+ return hook
88
+
89
+
90
+ def set_hook_for_unittest_class_teardown(records: HTTPRecords, method: Callable):
91
+
92
+ def hook(*args, **kwargs):
93
+ callargs = getcallargs(method, *args, **kwargs)
94
+
95
+ test_result = None
96
+ if "result" in callargs:
97
+ result = callargs["result"]
98
+
99
+ if result.testsRun > 0:
100
+ test_result = result
101
+
102
+ if test_result:
103
+
104
+ label = "tearDownClass (unknown class)"
105
+ full_label = label
106
+
107
+ test_class = getattr(test_result, "_previousTestClass")
108
+ if test_class:
109
+ test_class_name = test_class.__name__
110
+ label = f"{test_class_name}::tearDownClass"
111
+ full_label = label
112
+
113
+ test_module = inspect.getmodule(test_class)
114
+ test_module_path = getattr(test_module, "__file__")
115
+ if test_module_path:
116
+ test_module_path = os.path.relpath(test_module_path)
117
+ full_label = f"{test_module_path}::{full_label}"
118
+
119
+ with httpdbg_group(records, label, full_label):
120
+ return method(*args, **kwargs)
121
+ else:
122
+ return method(*args, **kwargs)
123
+
124
+ return hook
125
+
126
+
127
+ def set_hook_for_unittest_module_setup(records: HTTPRecords, method: Callable):
128
+
129
+ def hook(*args, **kwargs):
130
+ callargs = getcallargs(method, *args, **kwargs)
131
+
132
+ if "test" in callargs:
133
+ test_suite = callargs["test"]
134
+
135
+ test_module = inspect.getmodule(test_suite)
136
+
137
+ label = f"setUpModule ({test_module.__name__})" # may be __main__
138
+ full_label = label
139
+
140
+ test_module_path = getattr(test_module, "__file__")
141
+ if test_module_path:
142
+ # if we execute the tests using the script mode, the module name is __main__
143
+ # this is why we retrieve the module name from the path
144
+ module_name = os.path.splitext(os.path.basename(test_module_path))[0]
145
+ label = f"setUpModule ({module_name})"
146
+
147
+ test_module_path = os.path.relpath(test_module_path)
148
+ full_label = f"{test_module_path}::setUpModule"
149
+
150
+ with httpdbg_group(records, label, full_label):
151
+ return method(*args, **kwargs)
152
+ else:
153
+ return method(*args, **kwargs)
154
+
155
+ return hook
156
+
157
+
158
+ def set_hook_for_unittest_module_teardown(records: HTTPRecords, method: Callable):
159
+
160
+ def hook(*args, **kwargs):
161
+ callargs = getcallargs(method, *args, **kwargs)
162
+
163
+ test_result = None
164
+ if "result" in callargs:
165
+ result = callargs["result"]
166
+
167
+ if result.testsRun > 0:
168
+ test_result = result
169
+
170
+ if test_result:
171
+
172
+ label = "tearDownModule (unknown module)"
173
+ full_label = label
174
+
175
+ test_class = getattr(test_result, "_previousTestClass")
176
+ if test_class:
177
+
178
+ test_module = inspect.getmodule(test_class)
179
+
180
+ label = f"tearDownModule ({test_module.__name__})" # may be __main__
181
+
182
+ test_module_path = getattr(test_module, "__file__")
183
+ if test_module_path:
184
+ # if we execute the tests using the script mode, the module name is __main__
185
+ # this is why we retrieve the module name from the path
186
+ module_name = os.path.splitext(os.path.basename(test_module_path))[
187
+ 0
188
+ ]
189
+ label = f"tearDownModule ({module_name})"
190
+
191
+ test_module_path = os.path.relpath(test_module_path)
192
+ full_label = f"{test_module_path}::tearDownModule"
193
+
194
+ with httpdbg_group(records, label, full_label):
195
+ return method(*args, **kwargs)
196
+ else:
197
+ return method(*args, **kwargs)
198
+
199
+ return hook
200
+
201
+
202
+ @typing.no_type_check
203
+ @contextmanager
204
+ def hook_unittest(records: HTTPRecords) -> Generator[None, None, None]:
205
+ hooks = False
206
+ try:
207
+ import unittest
208
+
209
+ unittest.case.TestCase.run = decorate(
210
+ records, unittest.case.TestCase.run, set_hook_for_unittest_run
211
+ )
212
+ unittest.case.TestCase._callSetUp = decorate(
213
+ records, unittest.case.TestCase._callSetUp, set_hook_for_unittest_fixture
214
+ )
215
+ unittest.case.TestCase._callTearDown = decorate(
216
+ records, unittest.case.TestCase._callTearDown, set_hook_for_unittest_fixture
217
+ )
218
+ unittest.suite.TestSuite._handleClassSetUp = decorate(
219
+ records,
220
+ unittest.suite.TestSuite._handleClassSetUp,
221
+ set_hook_for_unittest_class_setup,
222
+ )
223
+ unittest.suite.TestSuite._tearDownPreviousClass = decorate(
224
+ records,
225
+ unittest.suite.TestSuite._tearDownPreviousClass,
226
+ set_hook_for_unittest_class_teardown,
227
+ )
228
+ unittest.suite.TestSuite._handleModuleFixture = decorate(
229
+ records,
230
+ unittest.suite.TestSuite._handleModuleFixture,
231
+ set_hook_for_unittest_module_setup,
232
+ )
233
+ unittest.suite.TestSuite._handleModuleTearDown = decorate(
234
+ records,
235
+ unittest.suite.TestSuite._handleModuleTearDown,
236
+ set_hook_for_unittest_module_teardown,
237
+ )
238
+ hooks = True
239
+ except ImportError:
240
+ pass
241
+
242
+ yield
243
+
244
+ if hooks:
245
+ unittest.case.TestCase.run = undecorate(unittest.case.TestCase.run)
246
+ unittest.case.TestCase._callSetUp = undecorate(
247
+ unittest.case.TestCase._callSetUp
248
+ )
249
+ unittest.case.TestCase._callTearDown = undecorate(
250
+ unittest.case.TestCase._callTearDown
251
+ )
252
+ unittest.suite.TestSuite._handleClassSetUp = undecorate(
253
+ unittest.suite.TestSuite._handleClassSetUp
254
+ )
255
+ unittest.suite.TestSuite._tearDownPreviousClass = undecorate(
256
+ unittest.suite.TestSuite._tearDownPreviousClass
257
+ )
258
+ unittest.suite.TestSuite._handleModuleFixture = undecorate(
259
+ unittest.suite.TestSuite._handleModuleFixture
260
+ )
261
+ unittest.suite.TestSuite._handleModuleTearDown = undecorate(
262
+ unittest.suite.TestSuite._handleModuleTearDown
263
+ )
@@ -0,0 +1,81 @@
1
+ # -*- coding: utf-8 -*-
2
+ import code
3
+ from typing import List
4
+ from typing import Union
5
+
6
+ from httpdbg.records import HTTPRecords
7
+ from httpdbg.initiator import httpdbg_group
8
+
9
+
10
+ def console_exit():
11
+ raise SystemExit
12
+
13
+
14
+ class InteractiveConsoleWithHistory(code.InteractiveConsole):
15
+
16
+ def __init__(self, records: HTTPRecords, locals=None):
17
+ self.records: HTTPRecords = records
18
+ self.history: List[str] = []
19
+ self.incomplete_block: bool = False
20
+ super().__init__(locals)
21
+
22
+ def push(self, line):
23
+ if line: # if the line is empty, we don't add it in the history
24
+ if (
25
+ self.incomplete_block
26
+ ): # if the latest instruction is part of a block, we group it with the previous one
27
+ self.history[-1] += (
28
+ "\n " + line
29
+ ) # to simulate the block indentation
30
+ else:
31
+ self.history.append(line)
32
+
33
+ # if the latest instruction is part of a block, the label is generic
34
+ label = self.history[-1] if not self.incomplete_block else "(block)"
35
+
36
+ # in the full label, we keep the last 5 instructions/blocks
37
+ latest_history = self.history[-5:]
38
+ if not self.incomplete_block:
39
+ # in case of a block, we don't know which line within the block is the initiator.
40
+ # this is why we do not mark it in that case.
41
+ # this information is displayed in the initiator tooltip of the requests (in console, line X,)
42
+ latest_history[-1] += " <===="
43
+
44
+ full_label = ">>> " + "\n>>> ".join(latest_history)
45
+
46
+ with httpdbg_group(self.records, label, full_label):
47
+ self.incomplete_block = super().push(line)
48
+
49
+ return self.incomplete_block
50
+
51
+
52
+ def run_console(
53
+ records: HTTPRecords, test_mode: bool = False
54
+ ) -> Union[InteractiveConsoleWithHistory, None]:
55
+ try:
56
+ vars = globals()
57
+ vars.update(locals())
58
+
59
+ try:
60
+ # adds history and autocompletion capabilities
61
+ import readline
62
+ import rlcompleter
63
+
64
+ readline.set_completer(rlcompleter.Completer(vars).complete)
65
+ readline.parse_and_bind("tab: complete")
66
+ except ImportError: # pragma: no cover
67
+ # readline is not available on Windows
68
+ pass
69
+
70
+ vars.update({"exit": console_exit})
71
+
72
+ new_console = InteractiveConsoleWithHistory(records, vars)
73
+ if not test_mode:
74
+ new_console.interact() # pragma: no cover
75
+ else:
76
+ new_console.push("print('test_mode is on')")
77
+ return new_console
78
+ except SystemExit: # pragma: no cover
79
+ pass
80
+
81
+ return None
@@ -4,6 +4,8 @@ import sys
4
4
  import traceback
5
5
  from typing import List
6
6
 
7
+ from httpdbg.log import logger
8
+
7
9
 
8
10
  def run_script(argv: List[str]) -> None:
9
11
  sys.argv = argv
@@ -15,6 +17,15 @@ def run_script(argv: List[str]) -> None:
15
17
  except AttributeError:
16
18
  exit("script mode - error - the first argument shall be a python file")
17
19
  try:
20
+ original__main___module = sys.modules["__main__"]
21
+ sys.modules["__main__"] = module # mandatory to execute a unittest test file.
18
22
  spec.loader.exec_module(module) # type: ignore
19
23
  except Exception:
20
24
  traceback.print_exc()
25
+ except SystemExit as ex:
26
+ # in some case, for example when executing unittest tests from a script,
27
+ # the SystemExit exception is raised by the script itself so we catch it.
28
+ # TODO propagate the SystemExit value when httpdbg terminates.
29
+ logger().info(f"run_script - exception SystemExit - {str(ex)}")
30
+ finally:
31
+ sys.modules["__main__"] = original__main___module
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: httpdbg
3
- Version: 0.29.1
3
+ Version: 0.30.0
4
4
  Summary: A very simple tool to debug HTTP(S) client requests
5
5
  Author-email: cle-b <cle@tictac.pm>
6
6
  License: Apache-2.0
@@ -116,6 +116,7 @@ You can use any package as an initiator, this is not limited to HTTP requests.
116
116
  | httpx | supported |
117
117
  | aiohttp | supported |
118
118
  | pytest | supported |
119
+ | unittest | supported |
119
120
  | _your_package_ | yes, with the arg _-i your_package_ |
120
121
 
121
122
  ## configuration
@@ -31,6 +31,7 @@ httpdbg/hooks/httpx.py
31
31
  httpdbg/hooks/pytest.py
32
32
  httpdbg/hooks/requests.py
33
33
  httpdbg/hooks/socket.py
34
+ httpdbg/hooks/unittest.py
34
35
  httpdbg/hooks/urllib3.py
35
36
  httpdbg/hooks/utils.py
36
37
  httpdbg/webapp/__init__.py
@@ -64,7 +65,9 @@ tests/test_cli.py
64
65
  tests/test_context_manager.py
65
66
  tests/test_hook_aiohttp.py
66
67
  tests/test_hook_httpx.py
68
+ tests/test_hook_pytest.py
67
69
  tests/test_hook_requests.py
70
+ tests/test_hook_unittest.py
68
71
  tests/test_hook_urllib3.py
69
72
  tests/test_initiator.py
70
73
  tests/test_mode_console.py
@@ -6,8 +6,8 @@ import pytest
6
6
  from httpdbg.hooks.all import httprecord
7
7
  from httpdbg.server import httpdbg_srv
8
8
 
9
- from utils import get_request_content_up
10
- from utils import get_request_details
9
+ from tests.utils import get_request_content_up
10
+ from tests.utils import get_request_details
11
11
 
12
12
 
13
13
  @pytest.mark.api
@@ -0,0 +1,38 @@
1
+ # -*- coding: utf-8 -*-
2
+ import os
3
+
4
+ import pytest
5
+
6
+ from httpdbg.mode_module import run_module
7
+ from httpdbg.hooks.all import httprecord
8
+
9
+
10
+ @pytest.mark.pytest
11
+ @pytest.mark.group
12
+ @pytest.mark.tag
13
+ def test_run_pytest_tag(httpbin):
14
+ with httprecord() as records:
15
+ os.environ["HTTPDBG_TEST_PYTEST_BASE_URL"] = httpbin.url
16
+ script_to_run = os.path.join(
17
+ os.path.dirname(os.path.realpath(__file__)), "demo_run_pytest.py"
18
+ )
19
+ run_module(["pytest", f"{script_to_run}::test_demo_pytest_fixture_tag"])
20
+
21
+ assert len(records) == 2
22
+ groups = records.groups
23
+
24
+ assert records[0].url.endswith("/post")
25
+ assert groups[records[0].group_id].label == "test_demo_pytest_fixture_tag"
26
+ assert (
27
+ "demo_run_pytest.py::test_demo_pytest_fixture_tag"
28
+ in groups[records[0].group_id].full_label
29
+ )
30
+ assert records[0].tag == "my_fixture"
31
+
32
+ assert records[1].url.endswith("/get")
33
+ assert groups[records[1].group_id].label == "test_demo_pytest_fixture_tag"
34
+ assert (
35
+ "demo_run_pytest.py::test_demo_pytest_fixture_tag"
36
+ in groups[records[1].group_id].full_label
37
+ )
38
+ assert records[1].tag is None
@@ -0,0 +1,132 @@
1
+ # -*- coding: utf-8 -*-
2
+ import os
3
+
4
+ import pytest
5
+
6
+ from httpdbg.mode_module import run_module
7
+ from httpdbg.mode_script import run_script
8
+ from httpdbg.hooks.all import httprecord
9
+
10
+
11
+ @pytest.mark.unittest
12
+ @pytest.mark.group
13
+ @pytest.mark.tag
14
+ def test_run_module_unittest(httpbin):
15
+ with httprecord() as records:
16
+ os.environ["HTTPDBG_TEST_UNITTEST_BASE_URL"] = httpbin.url
17
+ script_to_run = os.path.join(
18
+ os.path.dirname(os.path.realpath(__file__)), "demo_run_unittest.py"
19
+ )
20
+ run_module(["unittest", f"{script_to_run}"])
21
+
22
+ assert_unittest_records(records)
23
+
24
+
25
+ @pytest.mark.unittest
26
+ @pytest.mark.group
27
+ @pytest.mark.tag
28
+ def test_run_script_unittest(httpbin):
29
+ with httprecord() as records:
30
+ os.environ["HTTPDBG_TEST_UNITTEST_BASE_URL"] = httpbin.url
31
+ script_to_run = os.path.join(
32
+ os.path.dirname(os.path.realpath(__file__)), "demo_run_unittest.py"
33
+ )
34
+ run_script([f"{script_to_run}"])
35
+
36
+ assert_unittest_records(records)
37
+
38
+
39
+ def assert_unittest_records(records):
40
+
41
+ assert len(records) == 10
42
+ groups = records.groups
43
+
44
+ i_req = 0
45
+ assert records[i_req].url.endswith("/setup_module")
46
+ assert groups[records[i_req].group_id].label == "setUpModule (demo_run_unittest)"
47
+ assert (
48
+ "demo_run_unittest.py::setUpModule"
49
+ in groups[records[i_req].group_id].full_label
50
+ )
51
+ assert records[i_req].tag is None
52
+
53
+ i_req = 1
54
+ assert records[i_req].url.endswith("/setup_class")
55
+ assert groups[records[i_req].group_id].label == "TestUnittests::setUpClass"
56
+ assert (
57
+ "demo_run_unittest.py::TestUnittests::setUpClass"
58
+ in groups[records[i_req].group_id].full_label
59
+ )
60
+ assert records[i_req].tag is None
61
+
62
+ i_req = 2
63
+ assert records[i_req].url.endswith("/setup")
64
+ assert groups[records[i_req].group_id].label == "TestUnittests::test_case1"
65
+ assert (
66
+ "demo_run_unittest.py::TestUnittests::test_case1"
67
+ in groups[records[i_req].group_id].full_label
68
+ )
69
+ assert records[i_req].tag == "setUp"
70
+
71
+ i_req = 3
72
+ assert records[i_req].url.endswith("/testcase1")
73
+ assert groups[records[i_req].group_id].label == "TestUnittests::test_case1"
74
+ assert (
75
+ "demo_run_unittest.py::TestUnittests::test_case1"
76
+ in groups[records[i_req].group_id].full_label
77
+ )
78
+ assert records[i_req].tag is None
79
+
80
+ i_req = 4
81
+ assert records[i_req].url.endswith("/teardown")
82
+ assert groups[records[i_req].group_id].label == "TestUnittests::test_case1"
83
+ assert (
84
+ "demo_run_unittest.py::TestUnittests::test_case1"
85
+ in groups[records[i_req].group_id].full_label
86
+ )
87
+ assert records[i_req].tag == "tearDown"
88
+
89
+ i_req = 5
90
+ assert records[i_req].url.endswith("/setup")
91
+ assert groups[records[i_req].group_id].label == "TestUnittests::test_case2"
92
+ assert (
93
+ "demo_run_unittest.py::TestUnittests::test_case2"
94
+ in groups[records[i_req].group_id].full_label
95
+ )
96
+ assert records[i_req].tag == "setUp"
97
+
98
+ i_req = 6
99
+ assert records[i_req].url.endswith("/testcase2")
100
+ assert groups[records[i_req].group_id].label == "TestUnittests::test_case2"
101
+ assert (
102
+ "demo_run_unittest.py::TestUnittests::test_case2"
103
+ in groups[records[i_req].group_id].full_label
104
+ )
105
+ assert records[i_req].tag is None
106
+
107
+ i_req = 7
108
+ assert records[i_req].url.endswith("/teardown")
109
+ assert groups[records[i_req].group_id].label == "TestUnittests::test_case2"
110
+ assert (
111
+ "demo_run_unittest.py::TestUnittests::test_case2"
112
+ in groups[records[i_req].group_id].full_label
113
+ )
114
+ assert records[i_req].tag == "tearDown"
115
+
116
+ i_req = 8
117
+ assert records[i_req].url.endswith("/teardown_class")
118
+ assert groups[records[i_req].group_id].label == "TestUnittests::tearDownClass"
119
+ assert (
120
+ "demo_run_unittest.py::TestUnittests::tearDownClass"
121
+ in groups[records[i_req].group_id].full_label
122
+ )
123
+ assert records[i_req].tag is None
124
+
125
+ i_req = 9
126
+ assert records[i_req].url.endswith("/teardown_module")
127
+ assert groups[records[i_req].group_id].label == "tearDownModule (demo_run_unittest)"
128
+ assert (
129
+ "demo_run_unittest.py::tearDownModule"
130
+ in groups[records[i_req].group_id].full_label
131
+ )
132
+ assert records[i_req].tag is None
@@ -102,12 +102,12 @@ def test_initiator_pytest(httpbin):
102
102
  )
103
103
  def test_initiator_add_package_fnc(httpbin):
104
104
  # all cases must be tested in the same test
105
- with httprecord(initiators=["initiator_pck"]) as records:
106
- from initiator_pck.initiator2.mod2 import fnc_in_subpackage
107
- from initiator_pck.mod1 import fnc_in_package
108
- from initiator_pck import fnc_async
109
- from initiator_pck.initiator2.initiator3 import fnc_in_init
110
- from initiator_pck.initiator2.mod2 import FakeClient
105
+ with httprecord(initiators=["tests.initiator_pck"]) as records:
106
+ from tests.initiator_pck.initiator2.mod2 import fnc_in_subpackage
107
+ from tests.initiator_pck.mod1 import fnc_in_package
108
+ from tests.initiator_pck import fnc_async
109
+ from tests.initiator_pck.initiator2.initiator3 import fnc_in_init
110
+ from tests.initiator_pck.initiator2.mod2 import FakeClient
111
111
 
112
112
  fnc_in_package(f"{httpbin.url}/get")
113
113
  fnc_in_subpackage(f"{httpbin.url}/get")
@@ -36,7 +36,7 @@ def test_run_console_from_pyhttpdbg_entry_point_default(
36
36
  def test_run_console(httpbin, httpdbg_host, httpdbg_port):
37
37
  with httpdbg_srv(httpdbg_host, httpdbg_port) as records:
38
38
  with httprecord(records):
39
- new_console = run_console(test_mode=True)
39
+ new_console = run_console(records, test_mode=True)
40
40
  new_console.push("import requests")
41
41
  new_console.push(f"requests.get('{httpbin.url}/get')")
42
42
  with pytest.raises(SystemExit):
@@ -48,3 +48,31 @@ def test_run_console(httpbin, httpdbg_host, httpdbg_port):
48
48
 
49
49
  assert len(reqs) == 1
50
50
  assert reqs[list(reqs.keys())[0]]["url"] == httpbin.url + "/get"
51
+
52
+
53
+ @pytest.mark.group
54
+ def test_run_console_group(httpbin):
55
+
56
+ with httprecord() as records:
57
+ new_console = run_console(records, test_mode=True)
58
+ new_console.push("import requests")
59
+ new_console.push(f"requests.get('{httpbin.url}/get')")
60
+ new_console.push("if True:")
61
+ new_console.push(f" requests.post('{httpbin.url}/post')")
62
+ new_console.push("")
63
+ with pytest.raises(SystemExit):
64
+ new_console.push("exit()")
65
+
66
+ assert len(records) == 2
67
+ assert (
68
+ records.groups[records[0].group_id].label
69
+ == f"requests.get('{httpbin.url}/get')"
70
+ )
71
+ assert records.groups[records[0].group_id].full_label.endswith(
72
+ f">>> requests.get('{httpbin.url}/get') <===="
73
+ )
74
+
75
+ assert records.groups[records[1].group_id].label == "(block)"
76
+ assert records.groups[records[1].group_id].full_label.endswith(
77
+ f">>> if True:\n requests.post('{httpbin.url}/post')"
78
+ )
@@ -112,28 +112,3 @@ def test_run_pytest_group(httpbin, httpdbg_host, httpdbg_port):
112
112
 
113
113
  assert group["label"] == "test_demo_pytest"
114
114
  assert group["full_label"] == "tests/demo_run_pytest.py::test_demo_pytest"
115
-
116
-
117
- @pytest.mark.api
118
- @pytest.mark.pytest
119
- @pytest.mark.tag
120
- def test_run_pytest_tag(httpbin, httpdbg_host, httpdbg_port):
121
- with httpdbg_srv(httpdbg_host, httpdbg_port) as records:
122
- with httprecord(records):
123
- os.environ["HTTPDBG_TEST_PYTEST_BASE_URL"] = httpbin.url
124
- script_to_run = os.path.join(
125
- os.path.dirname(os.path.realpath(__file__)), "demo_run_pytest.py"
126
- )
127
- run_module(["pytest", f"{script_to_run}::test_demo_pytest_fixture_tag"])
128
-
129
- ret = requests.get(f"http://{httpdbg_host}:{httpdbg_port}/requests")
130
-
131
- reqs = ret.json()["requests"]
132
-
133
- assert len(reqs) == 2
134
-
135
- assert reqs[list(reqs.keys())[0]]["url"] == httpbin.url + "/post"
136
- assert reqs[list(reqs.keys())[0]]["tag"] == "my_fixture"
137
-
138
- assert reqs[list(reqs.keys())[1]]["url"] == httpbin.url + "/get"
139
- assert reqs[list(reqs.keys())[1]]["tag"] is None
@@ -1,37 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
- import code
3
- from typing import Union
4
-
5
-
6
- def console_exit():
7
- raise SystemExit
8
-
9
-
10
- def run_console(test_mode: bool = False) -> Union[code.InteractiveConsole, None]:
11
- try:
12
- vars = globals()
13
- vars.update(locals())
14
-
15
- try:
16
- # adds history and autocompletion capabilities
17
- import readline
18
- import rlcompleter
19
-
20
- readline.set_completer(rlcompleter.Completer(vars).complete)
21
- readline.parse_and_bind("tab: complete")
22
- except ImportError: # pragma: no cover
23
- # readline is not available on Windows
24
- pass
25
-
26
- vars.update({"exit": console_exit})
27
-
28
- new_console = code.InteractiveConsole(vars)
29
- if not test_mode:
30
- new_console.interact() # pragma: no cover
31
- else:
32
- new_console.push("print('test_mode is on')")
33
- return new_console
34
- except SystemExit: # pragma: no cover
35
- pass
36
-
37
- return None
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes