Skip to content

Commit a49fc5c

Browse files
fix #360 #359 #358 (index operation in variable definition, duplicate variable, string/number/boolean are now accepted as json) (#361)
* fix #360 #359 * fix tests * Bump faker from 35.2.0 to 35.2.2 Bumps [faker](https://github.com/joke2k/faker) from 35.2.0 to 35.2.2. - [Release notes](https://github.com/joke2k/faker/releases) - [Changelog](https://github.com/joke2k/faker/blob/master/CHANGELOG.md) - [Commits](joke2k/faker@v35.2.0...v35.2.2) --- updated-dependencies: - dependency-name: faker dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <[email protected]> * support accepting string/text in json as they are allowed * Fix tests * Array access in variable --------- Signed-off-by: dependabot[bot] <[email protected]> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
1 parent 5f0199b commit a49fc5c

17 files changed

+282
-24
lines changed

Diff for: dothttp/exceptions.py

+6
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,12 @@ class DataFileNotFoundException(HttpFileException):
8686
class PropertyNotFoundException(PropertyFileException):
8787
pass
8888

89+
@exception_wrapper(
90+
"index `{actual_key}` not found in `{indexed_value}` of target: `{target}`"
91+
)
92+
class VariableIndexNotAvailable(PropertyFileException):
93+
pass
94+
8995

9096
@exception_wrapper("error with command line property format, property `{prop}`")
9197
class CommandLinePropError(DotHttpException):

Diff for: dothttp/http.tx

+14-5
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,17 @@ MULTISET: (import_list=IMPORT)? (variables=VAR*)? (allhttps=HTTP+)?;
33

44

55
VAR:
6-
"var" name=ID ("=" ( func=FunctionCall | inter=InterpolatedString | value=Value ))? ';'
6+
"var" name=ID ("=" ( index = Index | func=FunctionCall | inter=InterpolatedString | value=Value ))? ';'
77
;
88

99
InterpolatedString:
1010
'$"' /[^"]*/ '"'
1111
;
1212

13+
Index:
14+
target=ID ('[' ( (key = ID) | (key=STRING) | (key = INT) ) ']')+
15+
;
16+
1317
FunctionCall:
1418
name=ID '(' (args=INT)? ')'
1519
;
@@ -179,8 +183,8 @@ PAYLOAD:
179183
(
180184
// reordered to provide max performance
181185
// most http requests has json input,or urlencoded as input
182-
('json'? '('? json=JSON ')'? )
183-
| ('data' | 'urlencoded') '(' datajson=JSON (',' type=STRING)? ','? ')'
186+
('json' '(' json=JSON ')' )
187+
| ('data' | 'urlencoded') '(' datajson=DATAJSON (',' type=STRING)? ','? ')'
184188
| ('data'| 'text') '(' data+=TRIPLE_OR_DOUBLE_STRING ((','|';') type=STRING)? ','? ')'
185189
| (fileswrap=FILES)
186190
| '<' file=STRING (';' type=STRING)?
@@ -212,16 +216,21 @@ TOFILE:
212216
)
213217
;
214218

219+
DATAJSON:
220+
object=Object | id = ID | var=VarString
221+
;
222+
223+
215224
JSON:
216-
array=Array | object=Object | var = VarString
225+
flt=Float | int=Int | bl=Bool | null="null" | id = ID | strs += TRIPLE_OR_DOUBLE_STRING | var=VarString | expr=Expression | array=Array | object=Object
217226
;
218227

219228
Array:
220229
"[" (values*=Value[','] ) ','? "]"
221230
;
222231

223232
Value:
224-
flt=Float | int=Int | bl=Bool | null="null" | strs += TRIPLE_OR_DOUBLE_STRING | var=VarString | object=Object | array=Array | expr=Expression
233+
flt=Float | int=Int | bl=Bool | null="null" | id = ID | strs += TRIPLE_OR_DOUBLE_STRING | var=VarString | object=Object | array=Array | expr=Expression
225234
;
226235

227236
Expression:

Diff for: dothttp/parse/__init__.py

+24
Original file line numberDiff line numberDiff line change
@@ -395,6 +395,30 @@ def __getitem__(self, key):
395395
raise KeyError(key)
396396
var_value = variable.inter[2:-1].format_map(PropertyResolver())
397397
property_util.add_infile_property_from_var(variable.name, var_value)
398+
elif index_sub := variable.index:
399+
# index operation applied on target
400+
indexed_value = property_util.resolve_property_object(index_sub.target)
401+
for key in index_sub.key:
402+
# figure out actual_key
403+
if key in property_util.command_line_properties:
404+
evaulated_key = property_util.command_line_properties[key]
405+
elif key in property_util.env_properties:
406+
evaulated_key = property_util.env_properties[key]
407+
elif key in property_util.infile_properties and property_util.infile_properties[key].value is not None:
408+
evaulated_key = property_util.infile_properties[key].value
409+
else:
410+
evaulated_key = key
411+
# complain if actual_key is not available in indexed_value
412+
if isinstance(indexed_value, list):
413+
if isinstance(evaulated_key, int):
414+
indexed_value = indexed_value[int(evaulated_key)]
415+
continue
416+
elif isinstance(indexed_value, dict) and evaulated_key in indexed_value:
417+
indexed_value = indexed_value[evaulated_key]
418+
continue
419+
raise VariableIndexNotAvailable(actual_key=evaulated_key, indexed_value=indexed_value, target=index_sub.target)
420+
property_util.add_infile_property_from_var(variable.name, indexed_value)
421+
398422

399423

400424
class HttpDefBase(BaseModelProcessor):

Diff for: dothttp/parse/dsl_jsonparser.py

+23-11
Original file line numberDiff line numberDiff line change
@@ -15,23 +15,31 @@ def __init__(self, property_util: PropertyProvider):
1515
def json_or_array_to_json(self, model) -> Union[Dict, List]:
1616
if isinstance(model, Dict) or isinstance(model, List):
1717
return model
18-
if array := model.array:
19-
return [self.jsonmodel_to_json(value) for value in array.values]
20-
elif json_object := model.object:
18+
if json_object := model.object:
2119
return {
2220
self.get_key(member): self.jsonmodel_to_json(member.value)
2321
for member in json_object.members
2422
}
23+
elif array := model.array:
24+
return [self.jsonmodel_to_json(value) for value in array.values]
2525
elif var_value := model.var:
2626
return self.property_util.get_updated_obj_content(var_value)
27+
else:
28+
return self.get_simple_non_iterative(model)
2729

2830
def get_key(self, member):
2931
if member.key:
3032
return triple_or_double_tostring(member.key, self.property_util.get_updated_content)
3133
elif member.var:
3234
return self.property_util.get_updated_obj_content(member.var)
3335

34-
def jsonmodel_to_json(self, model):
36+
def get_simple_non_iterative(self, model):
37+
if id := model.id:
38+
try:
39+
return self.property_util.get_updated_obj_content("{{%s}}" % id)
40+
except:
41+
# in case variable not found,
42+
return id
3543
if str_value := model.strs:
3644
return triple_or_double_tostring(str_value, self.property_util.get_updated_content)
3745
elif var_value := model.var:
@@ -42,13 +50,6 @@ def jsonmodel_to_json(self, model):
4250
return flt.value
4351
elif bl := model.bl:
4452
return bl.value
45-
elif json_object := model.object:
46-
return {
47-
self.get_key(member): self.jsonmodel_to_json(member.value)
48-
for member in json_object.members
49-
}
50-
elif array := model.array:
51-
return [self.jsonmodel_to_json(value) for value in array.values]
5253
elif model == "null":
5354
return None
5455
elif expr := model.expr:
@@ -68,6 +69,17 @@ def jsonmodel_to_json(self, model):
6869
base_logger.error(f"error in evaluating expression {expr}, new expression {new_expression}")
6970
return 0
7071

72+
def jsonmodel_to_json(self, model):
73+
if json_object := model.object:
74+
return {
75+
self.get_key(member): self.jsonmodel_to_json(member.value)
76+
for member in json_object.members
77+
}
78+
elif array := model.array:
79+
return [self.jsonmodel_to_json(value) for value in array.values]
80+
else:
81+
return self.get_simple_non_iterative(model)
82+
7183

7284
# Supporting function
7385
def json_or_array_to_json(model, property_util: Optional[PropertyProvider]=None) -> Union[Dict, List]:

Diff for: poetry.lock

+4-4
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: pyproject.toml

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "dothttp-req"
3-
version = "0.0.44a14"
3+
version = "0.0.44a15"
44
description = "Dothttp is Simple http client for testing and development"
55
authors = ["Prasanth <[email protected]>"]
66
license = "MIT"
@@ -30,7 +30,7 @@ parsys-requests-unixsocket = "0.3.2"
3030
requests-aws4auth = "1.3.1"
3131
requests-ntlm = "1.3.0"
3232
restrictedpython = "7.4"
33-
faker = "35.2.0"
33+
faker = "35.2.2"
3434
requests-hawk = "1.2.1"
3535
msal = "1.31.1"
3636
pyyaml = "6.0.2"

Diff for: test/core/requests/json_variations.http

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
var a = 10;
2+
@name('json_number')
3+
POST "https://httpbin.org/post"
4+
json(1)
5+
6+
@name('json_bool')
7+
POST "https://httpbin.org/post"
8+
json(true)
9+
10+
11+
@name('json_false')
12+
POST "https://httpbin.org/post"
13+
json(false)
14+
15+
16+
@name('json_float')
17+
POST "https://httpbin.org/post"
18+
json(1.1)
19+
20+
21+
@name('json_string')
22+
POST "https://httpbin.org/post"
23+
json("ram")
24+
25+
26+
@name('json_var')
27+
POST "https://httpbin.org/post"
28+
json(a)

Diff for: test/core/substitution/duplicate_var.http

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
var a = 10;
2+
var b = a;
3+
4+
POST "https://httpbin.org/post"
5+
6+
json({ b:a})

Diff for: test/core/substitution/index/.dothttp.json

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"sample": {
3+
"propindex": "name"
4+
}
5+
}

Diff for: test/core/substitution/index/nested.http

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
var details = {
2+
"name": "pras",
3+
"grades": {
4+
"math": 90,
5+
"science": 80
6+
},
7+
"parents": {
8+
"father": "John",
9+
"mother": "Jane",
10+
"sibling": {
11+
"brother": "Tom",
12+
"sister": "Alice"
13+
}
14+
},
15+
"attendance":[
16+
{
17+
"date": "2020-01-01",
18+
"status": "present"
19+
},
20+
{
21+
"date": "2020-01-02",
22+
"status": "absent"
23+
}
24+
]
25+
};
26+
var math_grade = details["grades"]["math"];
27+
var sibling = details["parents"]["sibling"];
28+
var attendance_status = details["attendance"][0]["status"];
29+
POST "https://httpbin.org/post"
30+
json({
31+
"math_grade": {{math_grade}},
32+
"sibling": {{sibling}},
33+
"attendance_status": {{attendance_status}}
34+
})
35+

Diff for: test/core/substitution/index/property_index.http

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
var details = {"name": "pras"};
2+
var name = details[propindex];
3+
4+
POST "https://httpbin.org/post"
5+
json({
6+
"name": "{{name}}"
7+
})
8+

Diff for: test/core/substitution/index/ref.http

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
var details = {"name": "pras"};
2+
var propindex = "name";
3+
var name = details[propindex];
4+
5+
POST "https://httpbin.org/post"
6+
json({
7+
"name": "{{name}}"
8+
})
9+

Diff for: test/core/substitution/index/simple_index.http

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
var details = {"name": "pras"};
2+
var name = details["name"];
3+
4+
POST "https://httpbin.org/post"
5+
json({
6+
"name": "pras"
7+
})
8+

Diff for: test/core/substitution/index/simple_index2.http

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
var details = {"name": "pras"};
2+
var name = details[name];
3+
4+
POST "https://httpbin.org/post"
5+
json({
6+
"name": "{{name}}"
7+
})
8+

Diff for: test/core/test_exceptions.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ def test_commmand_line_not_found(self):
3737
self.get_request(f"{sub_base_path}/multipleprop.http", properties=["ranga"])
3838

3939
def test_data_payload_not_valid(self):
40-
with self.assertRaises(PayloadDataNotValidException):
40+
with self.assertRaises(Exception):
4141
self.get_request(f"{neg_base_path}/datapayload.http")
4242

4343
def test_data_file_not_found(self):

Diff for: test/core/test_request.py

+29
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import json
12
import os
23
import sys
34
import tempfile
@@ -378,5 +379,33 @@ def test_hawk_auth_integration(self):
378379
self.assertEqual(200, resp.status_code)
379380

380381

382+
# @skip("feature is not yet implemented, lot of issues once we enable this")
383+
def test_json_str_int_bool(self):
384+
# This is an integration test
385+
filename = f"{base_dir}/json_variations.http"
386+
req_comp = self.get_req_comp(filename, target="json_number")
387+
self.assertEqual(json.loads(req_comp.get_request().body), 1)
388+
389+
req_comp = self.get_req_comp(filename, target="json_bool")
390+
self.assertEqual(json.loads(req_comp.get_request().body), True)
391+
392+
393+
394+
req_comp = self.get_req_comp(filename, target="json_false")
395+
self.assertEqual(json.loads(req_comp.get_request().body), False)
396+
397+
398+
req_comp = self.get_req_comp(filename, target="json_float")
399+
self.assertEqual(json.loads(req_comp.get_request().body), 1.1)
400+
401+
402+
req_comp = self.get_req_comp(filename, target="json_string")
403+
self.assertEqual(json.loads(req_comp.get_request().body), "ram")
404+
405+
406+
407+
req_comp = self.get_req_comp(filename, target="json_var")
408+
self.assertEqual(json.loads(req_comp.get_request().body), 10)
409+
381410
if __name__ == "__main__":
382411
unittest.main()

0 commit comments

Comments
 (0)