Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
feat: implement options key (#529)
  • Loading branch information
uesleicarvalhoo committed Jul 7, 2022
commit bc067f8d12c26766bee65d1e589e183e321e79e4
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]
### Added
- Implement new key `options` to request and endpoint nodes [#529](https://github.com/scanapi/scanapi/pull/552)

## [2.7.0] - 2022-06-02
### Deprecated
Expand Down
23 changes: 23 additions & 0 deletions scanapi/tree/endpoint_node.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import logging
from itertools import chain

from requests import RequestException

from scanapi.errors import InvalidKeyError
from scanapi.evaluators import SpecEvaluator
from scanapi.exit_code import ExitCode
from scanapi.session import session
Expand All @@ -11,6 +13,7 @@
ENDPOINTS_KEY,
HEADERS_KEY,
NAME_KEY,
OPTIONS_KEY,
PARAMS_KEY,
PATH_KEY,
REQUESTS_KEY,
Expand Down Expand Up @@ -45,6 +48,11 @@ class EndpointNode:
REQUESTS_KEY,
DELAY_KEY,
VARS_KEY,
OPTIONS_KEY,
)
ALLOWED_OPTIONS = (
"verify",
"timeout",
)
REQUIRED_KEYS = (NAME_KEY,)
ROOT_REQUIRED_KEYS = ()
Expand Down Expand Up @@ -99,6 +107,21 @@ def path(self):

return self.spec_vars.evaluate(url)

@property
def options(self):
"""Get the keywords arguments used in the endpoint call.
The options of the call include the parent's options.

Returns:
[dict]: the keyword used in the endpoint call.
"""
options = self._get_specs(OPTIONS_KEY)
for option in options:
if option not in self.ALLOWED_OPTIONS:
raise InvalidKeyError(option, OPTIONS_KEY, self.ALLOWED_OPTIONS)

return options

@property
def headers(self):
"""Get the headers used in the endpoint call. The headers of the
Expand Down
17 changes: 16 additions & 1 deletion scanapi/tree/request_node.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import time

from scanapi.console import console, write_result
from scanapi.errors import HTTPMethodNotAllowedError
from scanapi.errors import HTTPMethodNotAllowedError, InvalidKeyError
from scanapi.hide_utils import hide_sensitive_info
from scanapi.settings import settings
from scanapi.test_status import TestStatus
Expand All @@ -12,6 +12,7 @@
HEADERS_KEY,
METHOD_KEY,
NAME_KEY,
OPTIONS_KEY,
PARAMS_KEY,
PATH_KEY,
RETRY_KEY,
Expand Down Expand Up @@ -43,7 +44,9 @@ class RequestNode:
VARS_KEY,
DELAY_KEY,
RETRY_KEY,
OPTIONS_KEY,
)
ALLOWED_OPTIONS = ("verify", "timeout")
ALLOWED_HTTP_METHODS = (
"GET",
"POST",
Expand Down Expand Up @@ -86,6 +89,17 @@ def full_url_path(self):

return self.endpoint.spec_vars.evaluate(full_url)

@property
def options(self):
endpoint_options = self.endpoint.options
options = self.spec.get(OPTIONS_KEY, {})

for option in options:
if option not in self.ALLOWED_OPTIONS:
raise InvalidKeyError(option, OPTIONS_KEY, self.ALLOWED_OPTIONS)

return self.endpoint.spec_vars.evaluate({**endpoint_options, **options})

@property
def headers(self):
endpoint_headers = self.endpoint.headers
Expand Down Expand Up @@ -149,6 +163,7 @@ def run(self):
params=self.params,
json=self.body,
allow_redirects=False,
**self.options,
)

extras = dict(self.endpoint.spec_vars)
Expand Down
1 change: 1 addition & 0 deletions scanapi/tree/tree_keys.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@
DELAY_KEY = "delay"
RETRY_KEY = "retry"
MAX_RETRIES_KEY = "max_retries"
OPTIONS_KEY = "options"
66 changes: 66 additions & 0 deletions tests/unit/tree/endpoint_node/test_options.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
from pytest import mark, raises

from scanapi.errors import InvalidKeyError
from scanapi.tree import EndpointNode


@mark.describe("endpoint node")
@mark.describe("options")
class TestOptions:
@mark.context("when parent spec has no options defined")
@mark.it("should have only node options")
def test_when_parent_has_no_options(self):
options = {"verify": False}

node = EndpointNode(
{"name": "child-node", "options": options},
parent=EndpointNode({"name": "root"}),
)

assert node.options == options

@mark.context("when parent spec has options defined")
@mark.it("should append parent options")
def test_when_parent_has_name(self):
parent_options = {"timeout": 1.0}
child_options = {"timeout": 2.0}

parent = EndpointNode({"name": "root", "options": parent_options})
node = EndpointNode(
{"name": "child-node", "options": child_options}, parent=parent
)

assert node.options == {**parent_options, **child_options}

@mark.context("when parent and node spec has options defined")
@mark.it("should keep node options")
def test_when_both_was_same_options(self):
parent_options = {"timeout": 1.0}
child_options = {"timeout": 2.0}

parent = EndpointNode({"name": "root", "options": parent_options})
node = EndpointNode(
{"name": "child-node", "options": child_options}, parent=parent
)

assert node.options == child_options

@mark.context("when node spec has and invalid options key defined")
@mark.it("should raise an exception")
def test_when_request_option_has_invalid_key(self):
node = EndpointNode(
{
"name": "root",
"options": {"foo": "bar"},
},
)

with raises(InvalidKeyError) as excinfo:
node.options

expected = (
f"Invalid key 'foo' at 'options' scope. "
f"Available keys are: {node.ALLOWED_OPTIONS}"
)

assert str(excinfo.value) == expected
1 change: 1 addition & 0 deletions tests/unit/tree/endpoint_node/test_validate.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ def test_should_call_validate_keys(self, mock_validate_keys):
"requests",
"delay",
tree_keys.VARS_KEY,
"options",
),
("name",),
"endpoint",
Expand Down
71 changes: 71 additions & 0 deletions tests/unit/tree/request_node/test_options.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
from pytest import fixture, mark, raises

from scanapi.errors import InvalidKeyError
from scanapi.tree import EndpointNode, RequestNode


@mark.describe("request node")
@mark.describe("name")
class TestOptions:
@fixture
def mock_evaluate(self, mocker):
mock_func = mocker.patch(
"scanapi.tree.request_node.SpecEvaluator.evaluate"
)
mock_func.return_value = ""

return mock_func

@mark.context("when request spec has a options defined")
@mark.it("should set the options attribute accordingly")
def test_when_request_has_options(self):
options = {"timeout": 1.1}
request = RequestNode(
{"name": "list-users", "path": "http:foo.com", "options": options},
endpoint=EndpointNode({"name": "foo", "requests": []}),
)

assert request.options == options

@mark.context(
"when request and enpoint spec has a options with same keys defined"
)
@mark.it("should priorize the options attribute values of request")
def test_when_request_and_endpoint_has_options(self):
endpoint_options = {"timeout": 1.2}
request_options = {"timeout": 1.3}

request = RequestNode(
{
"name": "list-users",
"path": "http:foo.com",
"options": request_options,
},
endpoint=EndpointNode(
{"name": "foo", "options": endpoint_options, "requests": []}
),
)

assert request.options == request_options

@mark.context("when request spec has and invalid options key defined")
@mark.it("should raise an exception")
def test_when_request_option_has_invalid_key(self):
request = RequestNode(
{
"name": "list-users",
"path": "http:foo.com",
"options": {"foo": "bar"},
},
endpoint=EndpointNode({"name": "foo", "requests": []}),
)

with raises(InvalidKeyError) as excinfo:
request.options

expected = (
f"Invalid key 'foo' at 'options' scope. "
f"Available keys are: {request.ALLOWED_OPTIONS}"
)

assert str(excinfo.value) == expected
15 changes: 13 additions & 2 deletions tests/unit/tree/request_node/test_run.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,18 @@ def mock_console_write_result(self, mocker):
@mark.it("should call the request method")
def test_calls_request(self, mock_session, mock_time_sleep):
request = RequestNode(
{"path": "http://foo.com", "name": "request_name"},
{
"path": "http://foo.com",
"name": "request_name",
"options": {"timeout": 2.3},
},
endpoint=EndpointNode(
{"name": "endpoint_name", "requests": [{}], "delay": 1}
{
"name": "endpoint_name",
"requests": [{}],
"delay": 1,
"options": {"verify": False},
}
),
)
result = request.run()
Expand All @@ -41,6 +50,8 @@ def test_calls_request(self, mock_session, mock_time_sleep):
params=request.params,
json=request.body,
allow_redirects=False,
verify=False,
timeout=2.3,
)

assert result == {
Expand Down
1 change: 1 addition & 0 deletions tests/unit/tree/request_node/test_validate.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ def test_should_call_validate_keys(self, mock_validate_keys):
tree_keys.VARS_KEY,
"delay",
"retry",
"options",
),
("name",),
"request",
Expand Down