Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PONY-29 Refactor operation to step #16

Merged
merged 1 commit into from
May 18, 2022
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
2 changes: 1 addition & 1 deletion src/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@
from .response import Response
from .schema import Schema
from .singleton import Singleton
from .operation import Operation
from .step import Step
22 changes: 11 additions & 11 deletions src/models/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,28 +18,28 @@ class Context(metaclass=Singleton):
The global context object will be used as a singleton across the entire system.
"""

_operations = SimpleNamespace()
_steps = SimpleNamespace()

@property
def operations(self):
return self._operations
def steps(self):
return self._steps

def add_operations(self, operation):
def add_steps(self, step):
"""
Adds a Step object as an attribute of `self.operations`
Adds a Step object as an attribute of `self.steps`

Args:
operation (Operation): the Operation object to add
step (Step): the Step object to add
"""

setattr(self.operations, operation.name, operation)
setattr(self.steps, step.name, step)

def clear_operations(self):
def clear_steps(self):
"""
Clears all Operations objects from attributes of `self.operations`
Clears all Steps objects from attributes of `self.steps`
"""

self._operations = SimpleNamespace()
self._steps = SimpleNamespace()

# noinspection PyMethodMayBeStatic
def evaluate(self, expression: any) -> any:
Expand Down Expand Up @@ -87,7 +87,7 @@ def evaluate(self, expression: any) -> any:
result = os.environ.get(value.split(".", 1)[1])
if result is None:
raise EnvironmentVariableError(value)
elif base == "operations":
elif base == "steps":
try:
result = eval("self." + value)
except AttributeError as e:
Expand Down
4 changes: 2 additions & 2 deletions src/models/operation.py → src/models/step.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
"""operation.py:
"""step.py:

Encapsulates operation data from the step file, including name, request, response, and schema for easy access.
"""
Expand All @@ -14,7 +14,7 @@


@dataclass
class Operation:
class Step:
"""
Operations object manages request, response, and schema.
"""
Expand Down
2 changes: 1 addition & 1 deletion src/preprocessing.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ def get_operation_coverage(spec: dict, step: dict) -> OperationCoverage:

# Get all operations in the step file
step_operations = set()
for operation in step['operations']:
for operation in step['steps']:
step_operations.add(operation['operation_id'])

return OperationCoverage(
Expand Down
8 changes: 4 additions & 4 deletions src/steps_schema.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,14 @@ properties:
format: uri
auth:
"$ref": "#/$defs/auth"
operations:
steps:
type: array
items:
"$ref": "#/$defs/operation"
"$ref": "#/$defs/step"
additionalProperties: false
required:
- base_url
- operations
- steps
additionalProperties: false

"$defs":
Expand All @@ -32,7 +32,7 @@ additionalProperties: false
- username
- password
additionalProperties: false
operation:
step:
type: object
properties:
name:
Expand Down
47 changes: 23 additions & 24 deletions src/validate.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
ResponseValidationError,
UndocumentedOperationError,
)
from .models import Context, Request, Response, Schema, Operation
from .models import Context, Request, Response, Schema, Step
from .parsing import parse_spec, parse_steps
from .preprocessing import get_operation_coverage

Expand Down Expand Up @@ -66,7 +66,6 @@ def check_operation_coverage(spec_data: dict, steps_data: dict):
"""

operation_coverage = get_operation_coverage(spec_data, steps_data)
# inspect(operation_coverage)

# If any undocumented operations, immediately halt
if operation_coverage.has_undocumented_operations():
Expand Down Expand Up @@ -107,8 +106,8 @@ def make_requests(spec_data: dict, steps_data: dict, fail_fast: bool, verbose: b
if 'auth' in steps_data.keys():
global_auth: dict = steps_data["auth"]

# Get operations list
operations: list = steps_data["operations"]
# Get steps list
steps: list = steps_data["steps"]

# Create responses dict for easier parsing
operation_responses: dict = {}
Expand All @@ -117,27 +116,27 @@ def make_requests(spec_data: dict, steps_data: dict, fail_fast: bool, verbose: b
op_id = spec_data['paths'][path][method]['operationId']
operation_responses[op_id] = spec_data['paths'][path][method]['responses']

# Go through each operation
operation_data: dict
for operation_data in operations:
# Go through each step
step_data: dict
for step_data in steps:
try:
# Get operation name
operation_name = operation_data.pop("name")
# Get step name
step_name = step_data.pop("name")

# Create Request object
path_url = operation_data.pop("url")
request = Request(url=(base_url + path_url), global_auth=global_auth, **operation_data)
path_url = step_data.pop("url")
request = Request(url=(base_url + path_url), global_auth=global_auth, **step_data)

# TODO: something isnt right here - setting request body as application/xml
# in the spec but json in the step doesnt cause a failure
# Evaluate expressions
request.evaluate_all()

# Create Operation object
operation = Operation(operation_name, request)
# Create Step object
step = Step(step_name, request)

# Send the request
operation.response = Response(
step.response = Response(
requests.request(
method=request.method,
url=request.url,
Expand All @@ -148,16 +147,16 @@ def make_requests(spec_data: dict, steps_data: dict, fail_fast: bool, verbose: b
)
)

response = operation.response
status_code = operation.response.status_code
response = step.response
status_code = step.response.status_code

# Fetch schema
try:
schema = to_json_schema(operation_responses[operation_data['operation_id']][str(operation_data['status_code'])])
schema = to_json_schema(operation_responses[step_data['operation_id']][str(step_data['status_code'])])
except (AttributeError, KeyError):
raise ResponseMatchError(
operation_responses[operation_data['operation_id']].keys(),
operation.response,
operation_responses[step_data['operation_id']].keys(),
step.response,
)

# delete the $schema key that `to_json_schema` creates, it causes issues in the Schema class
Expand All @@ -166,20 +165,20 @@ def make_requests(spec_data: dict, steps_data: dict, fail_fast: bool, verbose: b
except KeyError:
pass

operation.schema = Schema(schema)
step.schema = Schema(schema)

# Save the step to further use
context.add_operations(operation)
context.add_steps(step)

# Verify the response
if 'application/json' in schema['content'].keys():
verification_result = operation.verify()
verification_result = step.verify()
else:
print('deez')

# TODO: make this nicer using a rich table
if verbose:
print(f'Operation: {operation_name}')
print(f'Step: {step_name}')
print('--------------------')
print('Request:')
print(f'{request.method} {request.url}')
Expand All @@ -203,7 +202,7 @@ def make_requests(spec_data: dict, steps_data: dict, fail_fast: bool, verbose: b
raise ResponseValidationError(
errors=verification_result.output('basic')["errors"],
url=path_url,
method=operation_data['method'],
method=step_data['method'],
status_code=status_code,
)

Expand Down
46 changes: 23 additions & 23 deletions test/context_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

import pytest

from src.models import Context, Operation, Request
from src.models import Context, Step, Request
from src.models.errors import BaseContextError, EnvironmentVariableError


Expand All @@ -14,21 +14,21 @@ def context():

@pytest.fixture(autouse=True)
def cleanup(context):
context.clear_operations()
context.clear_steps()
yield
context.clear_operations()
context.clear_steps()


def _env(value):
return "${{ env." + value + " }}"


def _operations(value):
return "${{ operations." + value + " }}"
def _steps(value):
return "${{ steps." + value + " }}"


def test_step_add(context):
operation = Operation(
step = Step(
name="createUser",
request=Request(
operation_id="createUser",
Expand All @@ -37,15 +37,15 @@ def test_step_add(context):
status_code=200
)
)
context.add_operations(operation)
context.add_steps(step)

assert context.operations.createUser == operation
assert context.steps.createUser == step


def test_step_add_duplicated(context):
for i in range(5):
context.add_operations(
Operation(
context.add_steps(
Step(
name="createUser",
request=Request(
operation_id="createUser",
Expand All @@ -56,12 +56,12 @@ def test_step_add_duplicated(context):
)
)

assert len(vars(context.operations)) == 1
assert len(vars(context.steps)) == 1


def test_step_clear(context):
context.add_operations(
Operation(
context.add_steps(
Step(
name="createUser",
request=Request(
operation_id="createUser",
Expand All @@ -71,8 +71,8 @@ def test_step_clear(context):
)
)
)
context.add_operations(
Operation(
context.add_steps(
Step(
name="getUsers",
request=Request(
operation_id="getUsers",
Expand All @@ -82,10 +82,10 @@ def test_step_clear(context):
)
)
)
assert len(vars(context.operations)) == 2
assert len(vars(context.steps)) == 2

context.clear_operations()
assert len(vars(context.operations)) == 0
context.clear_steps()
assert len(vars(context.steps)) == 0


# Expression.evaluate() delegates Context.evaluate()
Expand Down Expand Up @@ -122,8 +122,8 @@ def test_evaluate_multiple(context):
def test_evaluate_url(context):
user_id = str(uuid.uuid4())
account_id = str(uuid.uuid4())
context.add_operations(
Operation(
context.add_steps(
Step(
name="createUser",
request=Request(
operation_id="createUser",
Expand All @@ -133,8 +133,8 @@ def test_evaluate_url(context):
)
)
)
context.add_operations(
Operation(
context.add_steps(
Step(
name="createAccount",
request=Request(
operation_id="createAccount",
Expand All @@ -148,7 +148,7 @@ def test_evaluate_url(context):

assert (
context.evaluate(
f"/users/{_operations('createUser.request.body.id')}/accounts/{_operations('createAccount.request.body.id')}"
f"/users/{_steps('createUser.request.body.id')}/accounts/{_steps('createAccount.request.body.id')}"
)
== f"/users/{user_id}/accounts/{account_id}"
)
Expand Down
2 changes: 1 addition & 1 deletion test/fixtures/steps/invalid/scanner/invalid_yaml.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
base_url: "https://example.com"
# invalid yaml on line 2, operations is missing ':'
operations
steps
- name: createPersonSuccessful
operation_id: createPerson
method: POST
Expand Down
2 changes: 1 addition & 1 deletion test/fixtures/steps/invalid/schema/missing_property.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
base_url: "https://example.com"
# no operation_id on the fetchPersonInfoUnsSuccessful operation
operations:
steps:
- name: createPersonSuccessful
operation_id: createPerson
method: POST
Expand Down
2 changes: 1 addition & 1 deletion test/fixtures/steps/valid/coverage_steps.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
coverage_threshold: 0.0
base_url: http://127.0.0.1:8000
operations:
steps:
- name: coveredOperation
operation_id: coveredOperation
method: GET
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
coverage_threshold: 0.99
base_url: http://127.0.0.1:8000
operations:
steps:
- name: coveredOperation
operation_id: coveredOperation
method: GET
Expand Down
2 changes: 1 addition & 1 deletion test/fixtures/steps/valid/jservice-multiple.steps.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
base_url: https://jservice.io
operations:
steps:
- name: getAllCluesSuccessful1
operation_id: getClues
method: GET
Expand Down
Loading