cli2 5.0.3__tar.gz → 5.1.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.
- {cli2-5.0.3/cli2.egg-info → cli2-5.1.0}/PKG-INFO +1 -1
- {cli2-5.0.3 → cli2-5.1.0}/cli2/__init__.py +1 -1
- {cli2-5.0.3 → cli2-5.1.0}/cli2/log.py +78 -14
- {cli2-5.0.3 → cli2-5.1.0/cli2.egg-info}/PKG-INFO +1 -1
- {cli2-5.0.3 → cli2-5.1.0}/cli2.egg-info/SOURCES.txt +2 -0
- {cli2-5.0.3 → cli2-5.1.0}/setup.py +1 -1
- {cli2-5.0.3 → cli2-5.1.0}/tests/test_client.py +104 -22
- cli2-5.1.0/tests/test_client_test.py +17 -0
- cli2-5.1.0/tests/test_log.py +80 -0
- {cli2-5.0.3 → cli2-5.1.0}/MANIFEST.in +0 -0
- {cli2-5.0.3 → cli2-5.1.0}/README.rst +0 -0
- {cli2-5.0.3 → cli2-5.1.0}/classifiers.txt +0 -0
- {cli2-5.0.3 → cli2-5.1.0}/cli2/asyncio.py +0 -0
- {cli2-5.0.3 → cli2-5.1.0}/cli2/cli.py +0 -0
- {cli2-5.0.3 → cli2-5.1.0}/cli2/cli2.py +0 -0
- {cli2-5.0.3 → cli2-5.1.0}/cli2/colors.py +0 -0
- {cli2-5.0.3 → cli2-5.1.0}/cli2/configuration.py +0 -0
- {cli2-5.0.3 → cli2-5.1.0}/cli2/decorators.py +0 -0
- {cli2-5.0.3 → cli2-5.1.0}/cli2/display.py +0 -0
- {cli2-5.0.3 → cli2-5.1.0}/cli2/examples/__init__.py +0 -0
- {cli2-5.0.3 → cli2-5.1.0}/cli2/examples/conf.py +0 -0
- {cli2-5.0.3 → cli2-5.1.0}/cli2/examples/example.py +0 -0
- {cli2-5.0.3 → cli2-5.1.0}/cli2/examples/example_obj.py +0 -0
- {cli2-5.0.3 → cli2-5.1.0}/cli2/examples/nesting.py +0 -0
- {cli2-5.0.3 → cli2-5.1.0}/cli2/examples/obj.py +0 -0
- {cli2-5.0.3 → cli2-5.1.0}/cli2/examples/obj2.py +0 -0
- {cli2-5.0.3 → cli2-5.1.0}/cli2/examples/test.py +0 -0
- {cli2-5.0.3 → cli2-5.1.0}/cli2/lock.py +0 -0
- {cli2-5.0.3 → cli2-5.1.0}/cli2/mask.py +0 -0
- {cli2-5.0.3 → cli2-5.1.0}/cli2/node.py +0 -0
- {cli2-5.0.3 → cli2-5.1.0}/cli2/sphinx.py +0 -0
- {cli2-5.0.3 → cli2-5.1.0}/cli2/table.py +0 -0
- {cli2-5.0.3 → cli2-5.1.0}/cli2/test.py +0 -0
- {cli2-5.0.3 → cli2-5.1.0}/cli2.egg-info/dependency_links.txt +0 -0
- {cli2-5.0.3 → cli2-5.1.0}/cli2.egg-info/entry_points.txt +0 -0
- {cli2-5.0.3 → cli2-5.1.0}/cli2.egg-info/requires.txt +0 -0
- {cli2-5.0.3 → cli2-5.1.0}/cli2.egg-info/top_level.txt +0 -0
- {cli2-5.0.3 → cli2-5.1.0}/setup.cfg +0 -0
- {cli2-5.0.3 → cli2-5.1.0}/tests/test_ansible.py +0 -0
- {cli2-5.0.3 → cli2-5.1.0}/tests/test_ansible_variables.py +0 -0
- {cli2-5.0.3 → cli2-5.1.0}/tests/test_asyncio.py +0 -0
- {cli2-5.0.3 → cli2-5.1.0}/tests/test_cli.py +0 -0
- {cli2-5.0.3 → cli2-5.1.0}/tests/test_command.py +0 -0
- {cli2-5.0.3 → cli2-5.1.0}/tests/test_configuration.py +0 -0
- {cli2-5.0.3 → cli2-5.1.0}/tests/test_decorators.py +0 -0
- {cli2-5.0.3 → cli2-5.1.0}/tests/test_display.py +0 -0
- {cli2-5.0.3 → cli2-5.1.0}/tests/test_entry_point.py +0 -0
- {cli2-5.0.3 → cli2-5.1.0}/tests/test_group.py +0 -0
- {cli2-5.0.3 → cli2-5.1.0}/tests/test_inject.py +0 -0
- {cli2-5.0.3 → cli2-5.1.0}/tests/test_lock.py +0 -0
- {cli2-5.0.3 → cli2-5.1.0}/tests/test_mask.py +0 -0
- {cli2-5.0.3 → cli2-5.1.0}/tests/test_node.py +0 -0
- {cli2-5.0.3 → cli2-5.1.0}/tests/test_restful.py +0 -0
- {cli2-5.0.3 → cli2-5.1.0}/tests/test_table.py +0 -0
|
@@ -37,6 +37,16 @@ variables.
|
|
|
37
37
|
|
|
38
38
|
Setting this to ``INFO``, ``DEBUG``, or any other log level is safe.
|
|
39
39
|
|
|
40
|
+
.. envvar:: LOG_FILE
|
|
41
|
+
|
|
42
|
+
Path to log file to use, with a couple of special values:
|
|
43
|
+
|
|
44
|
+
- if ``LOG_FILE=auto``, then a path will be calculated in
|
|
45
|
+
``~/.local/cli2/log``,
|
|
46
|
+
- if ``LOG_FILE=none``, then there will be no file logging.
|
|
47
|
+
|
|
48
|
+
Default: ``auto``
|
|
49
|
+
|
|
40
50
|
.. envvar:: DEBUG
|
|
41
51
|
|
|
42
52
|
Setting this will set :envvar:`LOG_LEVEL` to `DEBUG`, but also activate
|
|
@@ -54,6 +64,7 @@ import os
|
|
|
54
64
|
import re
|
|
55
65
|
import sys
|
|
56
66
|
import structlog
|
|
67
|
+
import yaml
|
|
57
68
|
from pathlib import Path
|
|
58
69
|
|
|
59
70
|
import cli2.display
|
|
@@ -70,8 +81,15 @@ class YAMLFormatter:
|
|
|
70
81
|
return '\n' + value
|
|
71
82
|
|
|
72
83
|
|
|
73
|
-
def configure():
|
|
84
|
+
def configure(log_file=None):
|
|
85
|
+
"""
|
|
86
|
+
Configure logging.
|
|
87
|
+
|
|
88
|
+
:param log_file: override for :envvar:`LOG_FILE`.
|
|
89
|
+
"""
|
|
74
90
|
LOG_LEVEL = os.getenv('LOG_LEVEL', 'WARNING').upper()
|
|
91
|
+
if log_file is None:
|
|
92
|
+
log_file = os.getenv('LOG_FILE', 'auto')
|
|
75
93
|
|
|
76
94
|
if os.getenv('DEBUG'):
|
|
77
95
|
LOG_LEVEL = 'DEBUG'
|
|
@@ -92,11 +110,21 @@ def configure():
|
|
|
92
110
|
for arg in sys.argv
|
|
93
111
|
])[:155]
|
|
94
112
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
113
|
+
if log_file == 'auto':
|
|
114
|
+
log_dir = Path(os.getenv("HOME")) / '.local/cli2/log'
|
|
115
|
+
log_dir.mkdir(parents=True, exist_ok=True)
|
|
116
|
+
ts = datetime.datetime.now().strftime('%Y-%m-%d-%H-%M-%S')
|
|
117
|
+
file_name = f'{sys.argv[0].split("/")[-1]}-{ts}-{cmd}.log'
|
|
118
|
+
log_file = log_dir / file_name
|
|
119
|
+
elif log_file == 'none' or not log_file:
|
|
120
|
+
log_file = None
|
|
121
|
+
else:
|
|
122
|
+
log_file = Path(log_file)
|
|
123
|
+
|
|
124
|
+
handlers = ['default']
|
|
125
|
+
if log_file:
|
|
126
|
+
handlers.append('file')
|
|
127
|
+
|
|
100
128
|
LOGGING = {
|
|
101
129
|
'version': 1,
|
|
102
130
|
'disable_existing_loggers': True,
|
|
@@ -156,16 +184,10 @@ def configure():
|
|
|
156
184
|
'class': 'logging.StreamHandler',
|
|
157
185
|
'formatter': 'colored',
|
|
158
186
|
},
|
|
159
|
-
'file': {
|
|
160
|
-
'level': 'DEBUG',
|
|
161
|
-
'class': 'logging.handlers.WatchedFileHandler',
|
|
162
|
-
'formatter': 'plain',
|
|
163
|
-
'filename': str(file_path),
|
|
164
|
-
},
|
|
165
187
|
},
|
|
166
188
|
'loggers': {
|
|
167
189
|
'cli2': {
|
|
168
|
-
'handlers':
|
|
190
|
+
'handlers': handlers,
|
|
169
191
|
'level': 'DEBUG',
|
|
170
192
|
'propagate': True,
|
|
171
193
|
}
|
|
@@ -175,12 +197,20 @@ def configure():
|
|
|
175
197
|
if os.getenv('HTTP_DEBUG'):
|
|
176
198
|
LOGGING['loggers'].update({
|
|
177
199
|
key: {
|
|
178
|
-
'handlers':
|
|
200
|
+
'handlers': handlers,
|
|
179
201
|
'level': 'DEBUG',
|
|
180
202
|
'propagate': True,
|
|
181
203
|
} for key in ('httpx', 'httpcore')
|
|
182
204
|
})
|
|
183
205
|
|
|
206
|
+
if log_file:
|
|
207
|
+
LOGGING['handlers']['file'] = {
|
|
208
|
+
'level': 'DEBUG',
|
|
209
|
+
'class': 'logging.handlers.WatchedFileHandler',
|
|
210
|
+
'formatter': 'plain',
|
|
211
|
+
'filename': str(log_file),
|
|
212
|
+
}
|
|
213
|
+
|
|
184
214
|
logging.config.dictConfig(LOGGING)
|
|
185
215
|
|
|
186
216
|
structlog.configure(
|
|
@@ -198,5 +228,39 @@ def configure():
|
|
|
198
228
|
)
|
|
199
229
|
|
|
200
230
|
|
|
231
|
+
def parse(data):
|
|
232
|
+
"""
|
|
233
|
+
Parse log file data into a list of entries.
|
|
234
|
+
|
|
235
|
+
:param data: Contents of a log file.
|
|
236
|
+
"""
|
|
237
|
+
yaml_lines = []
|
|
238
|
+
entries = []
|
|
239
|
+
for line in data.split('\n'):
|
|
240
|
+
if 'event=' in line:
|
|
241
|
+
data = {}
|
|
242
|
+
for token in line.strip().split():
|
|
243
|
+
if match := re.match('^(\\w+)=(.*)', token):
|
|
244
|
+
key = match.group(1)
|
|
245
|
+
data[key] = match.group(2)
|
|
246
|
+
else:
|
|
247
|
+
data[key] += ' ' + token
|
|
248
|
+
|
|
249
|
+
if yaml_lines:
|
|
250
|
+
data['json'] = yaml.safe_load('\n'.join(yaml_lines))
|
|
251
|
+
|
|
252
|
+
if data['event'] == 'request':
|
|
253
|
+
entries.append(dict(request=data))
|
|
254
|
+
elif data['event'] == 'response':
|
|
255
|
+
entries[-1]['response'] = data
|
|
256
|
+
else:
|
|
257
|
+
entries.append(data)
|
|
258
|
+
|
|
259
|
+
yaml_lines = []
|
|
260
|
+
else:
|
|
261
|
+
yaml_lines.append(line)
|
|
262
|
+
return entries
|
|
263
|
+
|
|
264
|
+
|
|
201
265
|
configure()
|
|
202
266
|
log = structlog.get_logger('cli2')
|
|
@@ -36,6 +36,7 @@ tests/test_ansible_variables.py
|
|
|
36
36
|
tests/test_asyncio.py
|
|
37
37
|
tests/test_cli.py
|
|
38
38
|
tests/test_client.py
|
|
39
|
+
tests/test_client_test.py
|
|
39
40
|
tests/test_command.py
|
|
40
41
|
tests/test_configuration.py
|
|
41
42
|
tests/test_decorators.py
|
|
@@ -44,6 +45,7 @@ tests/test_entry_point.py
|
|
|
44
45
|
tests/test_group.py
|
|
45
46
|
tests/test_inject.py
|
|
46
47
|
tests/test_lock.py
|
|
48
|
+
tests/test_log.py
|
|
47
49
|
tests/test_mask.py
|
|
48
50
|
tests/test_node.py
|
|
49
51
|
tests/test_restful.py
|
|
@@ -7,8 +7,10 @@ import mock
|
|
|
7
7
|
import pytest
|
|
8
8
|
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
def _response(**kwargs):
|
|
11
|
+
response = httpx.Response(**kwargs)
|
|
12
|
+
response.request = httpx.Request('POST', '/', json=[1])
|
|
13
|
+
return response
|
|
12
14
|
|
|
13
15
|
|
|
14
16
|
class HandlerSentinel(chttpx.Handler):
|
|
@@ -17,7 +19,11 @@ class HandlerSentinel(chttpx.Handler):
|
|
|
17
19
|
self.calls = []
|
|
18
20
|
|
|
19
21
|
async def __call__(self, client, response, tries, log):
|
|
20
|
-
self.calls.append((
|
|
22
|
+
self.calls.append((
|
|
23
|
+
client,
|
|
24
|
+
getattr(response, 'status_code', response),
|
|
25
|
+
tries,
|
|
26
|
+
))
|
|
21
27
|
return await super().__call__(client, response, tries, log)
|
|
22
28
|
|
|
23
29
|
|
|
@@ -191,6 +197,7 @@ async def test_error_remote(httpx_mock, client_class):
|
|
|
191
197
|
return 'token'
|
|
192
198
|
|
|
193
199
|
client = TokenClient()
|
|
200
|
+
client.handler.tries = 2
|
|
194
201
|
httpx_mock.add_response(url='http://lol', json=[1])
|
|
195
202
|
|
|
196
203
|
async def raises(*a, **k):
|
|
@@ -230,10 +237,11 @@ async def test_client_handler(httpx_mock, client_class):
|
|
|
230
237
|
class Client(client_class):
|
|
231
238
|
def client_factory(self):
|
|
232
239
|
client = super().client_factory()
|
|
233
|
-
client.send = mock.
|
|
240
|
+
client.send = mock.AsyncMock()
|
|
234
241
|
return client
|
|
235
242
|
|
|
236
243
|
client = Client(handler=HandlerSentinel())
|
|
244
|
+
client.handler.tries = 3
|
|
237
245
|
|
|
238
246
|
# test response retry
|
|
239
247
|
client.client.send.side_effect = [
|
|
@@ -247,16 +255,33 @@ async def test_client_handler(httpx_mock, client_class):
|
|
|
247
255
|
(client, 200, 1),
|
|
248
256
|
]
|
|
249
257
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
258
|
+
|
|
259
|
+
@pytest.mark.asyncio
|
|
260
|
+
async def test_client_error_retry(httpx_mock, client_class):
|
|
261
|
+
class Client(client_class):
|
|
262
|
+
calls = 0
|
|
263
|
+
|
|
264
|
+
def client_factory(self):
|
|
265
|
+
self.calls += 1
|
|
266
|
+
|
|
267
|
+
client = super().client_factory()
|
|
268
|
+
client.send = mock.AsyncMock()
|
|
269
|
+
|
|
270
|
+
if self.calls == 1:
|
|
271
|
+
client.send.side_effect = httpx.TransportError('foo')
|
|
272
|
+
elif self.calls == 2:
|
|
273
|
+
client.send.return_value = _response(status_code=200)
|
|
274
|
+
|
|
275
|
+
return client
|
|
276
|
+
|
|
277
|
+
client = Client(handler=HandlerSentinel())
|
|
278
|
+
client.handler.tries = 3
|
|
279
|
+
|
|
280
|
+
response = await client.request('GET', '/')
|
|
255
281
|
assert response.status_code == 200
|
|
256
|
-
assert client.handler.calls
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
]
|
|
282
|
+
assert isinstance(client.handler.calls[0][1], httpx.TransportError)
|
|
283
|
+
assert client.handler.calls[1][1] == 200
|
|
284
|
+
assert len(client.handler.calls) == 2
|
|
260
285
|
|
|
261
286
|
|
|
262
287
|
@pytest.mark.asyncio
|
|
@@ -265,13 +290,19 @@ async def test_handler(client_class):
|
|
|
265
290
|
client = client_class()
|
|
266
291
|
client.client_reset = mock.AsyncMock()
|
|
267
292
|
client.token_reset = mock.AsyncMock()
|
|
268
|
-
handler = chttpx.Handler(
|
|
293
|
+
handler = chttpx.Handler(
|
|
294
|
+
accepts=[201],
|
|
295
|
+
refuses=[218],
|
|
296
|
+
retokens=[418],
|
|
297
|
+
tries=3,
|
|
298
|
+
)
|
|
269
299
|
|
|
270
300
|
response = httpx.Response(status_code=201)
|
|
271
301
|
result = await handler(client, response, 0, log)
|
|
272
302
|
assert result == response
|
|
273
303
|
|
|
274
304
|
response = httpx.Response(status_code=200)
|
|
305
|
+
response.request = httpx.Request('POST', '/', json=[1])
|
|
275
306
|
result = await handler(client, response, 0, log)
|
|
276
307
|
log.info.assert_called_once_with(
|
|
277
308
|
'retry', status_code=200, tries=0, sleep=.0
|
|
@@ -286,7 +317,7 @@ async def test_handler(client_class):
|
|
|
286
317
|
'retry', status_code=200, tries=0, sleep=.0
|
|
287
318
|
)
|
|
288
319
|
|
|
289
|
-
msg = 'Unacceptable response <Response [200 OK]> after
|
|
320
|
+
msg = 'Unacceptable response <Response [200 OK]> after 4 tries\n\x1b[0m\x1b[1mPOST /\x1b[0m\n-\x1b[37m \x1b[39;49;00m1\x1b[37m\x1b[39;49;00m\n\n\x1b[1mHTTP 200\x1b[0m\n-\x1b[37m \x1b[39;49;00m2\x1b[37m\x1b[39;49;00m\n' # noqa
|
|
290
321
|
assert str(exc.value) == msg
|
|
291
322
|
|
|
292
323
|
response = httpx.Response(status_code=200)
|
|
@@ -294,7 +325,7 @@ async def test_handler(client_class):
|
|
|
294
325
|
with pytest.raises(chttpx.RetriesExceededError) as exc:
|
|
295
326
|
await handler(client, response, handler.tries + 1, log)
|
|
296
327
|
|
|
297
|
-
msg = 'Unacceptable response <Response [200 OK]> after
|
|
328
|
+
msg = 'Unacceptable response <Response [200 OK]> after 4 tries\n\x1b[0m\x1b[1mGET /\x1b[0m\n\x1b[1mHTTP 200\x1b[0m' # noqa
|
|
298
329
|
assert str(exc.value) == msg
|
|
299
330
|
|
|
300
331
|
response = httpx.Response(status_code=218)
|
|
@@ -335,9 +366,7 @@ async def test_handler(client_class):
|
|
|
335
366
|
assert not client.token_reset.await_count
|
|
336
367
|
log.warn.reset_mock()
|
|
337
368
|
result = await handler(client, response, 0, log)
|
|
338
|
-
log.warn.assert_called_once_with(
|
|
339
|
-
'retoken', status_code=418, tries=0, sleep=.0
|
|
340
|
-
)
|
|
369
|
+
log.warn.assert_called_once_with('retoken')
|
|
341
370
|
assert not result
|
|
342
371
|
assert client.token_reset.await_count == 1
|
|
343
372
|
|
|
@@ -358,10 +387,11 @@ async def test_retry(httpx_mock, client_class):
|
|
|
358
387
|
|
|
359
388
|
def client_factory(self):
|
|
360
389
|
client = super().client_factory()
|
|
361
|
-
client.send = mock.
|
|
390
|
+
client.send = mock.AsyncMock()
|
|
362
391
|
return client
|
|
363
392
|
|
|
364
393
|
client = Client()
|
|
394
|
+
client.handler.tries = 3
|
|
365
395
|
|
|
366
396
|
current_client = client.client
|
|
367
397
|
client.client.send.side_effect = [
|
|
@@ -645,10 +675,11 @@ def test_descriptor(client_class):
|
|
|
645
675
|
assert model.data['undeclared']['foo'] == 3
|
|
646
676
|
|
|
647
677
|
model = Model()
|
|
648
|
-
assert model.foo
|
|
678
|
+
assert model.foo is None
|
|
679
|
+
assert 'foo' not in model._data
|
|
649
680
|
assert not model.changed_fields
|
|
650
681
|
model.foo = 'bar'
|
|
651
|
-
assert model.changed_fields == dict(foo=
|
|
682
|
+
assert model.changed_fields == dict(foo=None)
|
|
652
683
|
model = Model(foo='bar')
|
|
653
684
|
model.foo = 'foo'
|
|
654
685
|
assert model.changed_fields == dict(foo='bar')
|
|
@@ -1246,3 +1277,54 @@ def test_client_command(client_class, httpx_mock):
|
|
|
1246
1277
|
cmd = Client.cli['request']
|
|
1247
1278
|
cmd()
|
|
1248
1279
|
assert not Client.post_call_called
|
|
1280
|
+
|
|
1281
|
+
|
|
1282
|
+
def test_field_callback(client_class):
|
|
1283
|
+
class TestModel(client_class.Model):
|
|
1284
|
+
foo = chttpx.Field()
|
|
1285
|
+
bar = chttpx.Field()
|
|
1286
|
+
other = chttpx.Field()
|
|
1287
|
+
|
|
1288
|
+
@other.factory
|
|
1289
|
+
def other_factory(self):
|
|
1290
|
+
return 'nice'
|
|
1291
|
+
|
|
1292
|
+
@bar.factory(foo)
|
|
1293
|
+
def bar_factory(self):
|
|
1294
|
+
return f'bar{self.foo}'
|
|
1295
|
+
|
|
1296
|
+
model = client_class().TestModel(foo='test')
|
|
1297
|
+
assert model.bar == 'bartest'
|
|
1298
|
+
assert model.other == 'nice'
|
|
1299
|
+
|
|
1300
|
+
model = client_class().TestModel(foo='test')
|
|
1301
|
+
assert model.data['bar'] == 'bartest'
|
|
1302
|
+
assert model.data['other'] == 'nice'
|
|
1303
|
+
|
|
1304
|
+
model = client_class().TestModel()
|
|
1305
|
+
assert model.data == dict(other='nice')
|
|
1306
|
+
|
|
1307
|
+
model = client_class().TestModel(bar='lol')
|
|
1308
|
+
assert model.bar == 'lol'
|
|
1309
|
+
assert model.data['bar'] == 'lol'
|
|
1310
|
+
|
|
1311
|
+
|
|
1312
|
+
def test_field_callback_recursion(client_class):
|
|
1313
|
+
class TestModel(client_class.Model):
|
|
1314
|
+
final = chttpx.Field()
|
|
1315
|
+
req2 = chttpx.Field()
|
|
1316
|
+
req1 = chttpx.Field()
|
|
1317
|
+
|
|
1318
|
+
@final.factory(req2)
|
|
1319
|
+
def final_factory(self):
|
|
1320
|
+
return f'{self.req2}val3'
|
|
1321
|
+
|
|
1322
|
+
@req2.factory(req1)
|
|
1323
|
+
def req2_factory(self):
|
|
1324
|
+
return f'{self.req1}val2'
|
|
1325
|
+
|
|
1326
|
+
model = client_class().TestModel(req1='lol')
|
|
1327
|
+
assert model.data['final'] == 'lolval2val3'
|
|
1328
|
+
|
|
1329
|
+
model = client_class().TestModel(req1='lol')
|
|
1330
|
+
assert model.final == 'lolval2val3'
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import chttpx
|
|
2
|
+
import cli2
|
|
3
|
+
import pytest
|
|
4
|
+
from chttpx.example import APIClient
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@pytest.mark.chttpx_mock
|
|
8
|
+
def test_object_story():
|
|
9
|
+
test_name = 'test33312'
|
|
10
|
+
obj = APIClient.cli['object']['create'](f'name={test_name}')
|
|
11
|
+
assert obj.name == test_name
|
|
12
|
+
|
|
13
|
+
cli2.log.info('bogus')
|
|
14
|
+
|
|
15
|
+
with pytest.raises(chttpx.RefusedResponseError):
|
|
16
|
+
APIClient.cli['object']['create'](f'name={test_name}')
|
|
17
|
+
result = APIClient.cli['object']['delete'](f'{obj.id}')
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import cli2
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def test_log_parse():
|
|
5
|
+
logs = '''
|
|
6
|
+
|
|
7
|
+
name: tes2980898zzzyzy7
|
|
8
|
+
method=POST url=http://localhost:8000/objects/ event=request level=debug timestamp=2025-03-21 10:09:40
|
|
9
|
+
|
|
10
|
+
data: {}
|
|
11
|
+
id: 103
|
|
12
|
+
name: tes2980898zzzyzy7
|
|
13
|
+
method=POST url=http://localhost:8000/objects/ status_code=201 event=response level=info timestamp=2025-03-21 10:09:40
|
|
14
|
+
event=bogus level=info
|
|
15
|
+
|
|
16
|
+
name: tes2980898zzzyzy7
|
|
17
|
+
method=POST url=http://localhost:8000/objects/ event=request level=debug timestamp=2025-03-21 10:09:40
|
|
18
|
+
|
|
19
|
+
name:
|
|
20
|
+
- object with this name already exists.
|
|
21
|
+
method=POST url=http://localhost:8000/objects/ status_code=400 event=response level=info timestamp=2025-03-21 10:09:40
|
|
22
|
+
'''
|
|
23
|
+
|
|
24
|
+
result = cli2.parse(logs)
|
|
25
|
+
assert result == [
|
|
26
|
+
{
|
|
27
|
+
'request': {
|
|
28
|
+
'event': 'request',
|
|
29
|
+
'json': {
|
|
30
|
+
'name': 'tes2980898zzzyzy7',
|
|
31
|
+
},
|
|
32
|
+
'level': 'debug',
|
|
33
|
+
'method': 'POST',
|
|
34
|
+
'timestamp': '2025-03-21 10:09:40',
|
|
35
|
+
'url': 'http://localhost:8000/objects/',
|
|
36
|
+
},
|
|
37
|
+
'response': {
|
|
38
|
+
'event': 'response',
|
|
39
|
+
'json': {
|
|
40
|
+
'data': {},
|
|
41
|
+
'id': 103,
|
|
42
|
+
'name': 'tes2980898zzzyzy7',
|
|
43
|
+
},
|
|
44
|
+
'level': 'info',
|
|
45
|
+
'method': 'POST',
|
|
46
|
+
'status_code': '201',
|
|
47
|
+
'timestamp': '2025-03-21 10:09:40',
|
|
48
|
+
'url': 'http://localhost:8000/objects/',
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
'event': 'bogus',
|
|
53
|
+
'level': 'info',
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
'request': {
|
|
57
|
+
'event': 'request',
|
|
58
|
+
'json': {
|
|
59
|
+
'name': 'tes2980898zzzyzy7',
|
|
60
|
+
},
|
|
61
|
+
'level': 'debug',
|
|
62
|
+
'method': 'POST',
|
|
63
|
+
'timestamp': '2025-03-21 10:09:40',
|
|
64
|
+
'url': 'http://localhost:8000/objects/',
|
|
65
|
+
},
|
|
66
|
+
'response': {
|
|
67
|
+
'event': 'response',
|
|
68
|
+
'json': {
|
|
69
|
+
'name': [
|
|
70
|
+
'object with this name already exists.',
|
|
71
|
+
],
|
|
72
|
+
},
|
|
73
|
+
'level': 'info',
|
|
74
|
+
'method': 'POST',
|
|
75
|
+
'status_code': '400',
|
|
76
|
+
'timestamp': '2025-03-21 10:09:40',
|
|
77
|
+
'url': 'http://localhost:8000/objects/',
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
]
|
|
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
|
|
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
|
|
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
|