Skip to content
Open
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
8 changes: 8 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
version: 2
updates:
- package-ecosystem: "pip"
directory: "/"
schedule:
interval: "weekly"
pull-request-branch-name:
separator: "-"
3 changes: 0 additions & 3 deletions Jenkinsfile

This file was deleted.

51 changes: 51 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
NAME:="tr-05-rabbitmq"
PORT:="9090"
PM_PATH:="code/tests/functional/postman_collection.json"

all: stop build test test_pm scout

run: # app locally
cd code; python -m main; cd -

# Docker
build: stop_name
docker build -q -t $(NAME) .;
docker run -dp $(PORT):$(PORT) --name $(NAME) $(NAME)
stop:
docker stop $(shell docker ps | tail -1 | grep :9090 | awk '{ print $$1 }')
stop_name:
docker stop $(NAME); docker rm $(NAME); true

# Tools
env:
pip3 install --no-cache-dir --upgrade pipenv && pipenv install --dev && pipenv shell
brew install newman
black:
black code/ -l 120 -t py311 --exclude=payloads_for_tests.py
lint: black
flake8 code/

# Tests
test:
cd code; coverage run --source api/ -m pytest --verbose tests/unit/ && coverage report --fail-under=80; cd -
test_lf:
cd code; coverage run --source api/ -m pytest --verbose -vv --lf tests/unit/ && coverage report -m --fail-under=80; cd -
test_pm:
newman run $(PM_PATH)
scout:
curl -sSfL https://raw.githubusercontent.com/docker/scout-cli/main/install.sh | sh -s --
docker scout cves $(NAME) --only-fixed
pip-audit

# ---------------------------------------------------------------- #
# If ngrok can be used by you then you can run below make commands #
# ---------------------------------------------------------------- #
up: down build expose
down: unexpose stop_name

expose:
ngrok http $(PORT) > /dev/null &
echo_ngrok:
curl -s localhost:4040/api/tunnels | jq -r ".tunnels[0].public_url"
unexpose:
pkill ngrok; true
22 changes: 22 additions & 0 deletions Pipfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
[[source]]
url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"

[packages]
cryptography = "==44.0.1"
Flask = "==3.0.3"
marshmallow = "==3.22.0"
PyJWT = "==2.6.0"
requests = "==2.32.4"
pika = "==1.3.2"

[dev-packages]
black = "==24.8.0"
coverage = "==7.6.1"
flake8 = "==7.1.1"
pip-audit = "==2.7.3"
pytest = "==8.3.2"

[requires]
python_version = "3.11"
1,002 changes: 1,002 additions & 0 deletions Pipfile.lock

Large diffs are not rendered by default.

19 changes: 0 additions & 19 deletions code/Pipfile

This file was deleted.

395 changes: 0 additions & 395 deletions code/Pipfile.lock

This file was deleted.

Empty file added code/tests/__init__.py
Empty file.
Empty file added code/tests/unit/__init__.py
Empty file.
Empty file added code/tests/unit/api/__init__.py
Empty file.
182 changes: 182 additions & 0 deletions code/tests/unit/api/test_authorization.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
from pytest import fixture
from http import HTTPStatus

from .utils import get_headers
from unittest.mock import patch
from api.errors import AUTH_ERROR
from ..conftest import mock_api_response
from api.utils import (
WRONG_PAYLOAD_STRUCTURE,
WRONG_KEY,
WRONG_AUDIENCE,
KID_NOT_FOUND,
JWKS_HOST_MISSING
)


def routes():
yield '/health'
yield '/deliberate/observables'
yield '/observe/observables'
yield '/refer/observables'
yield '/respond/observables'
yield '/respond/trigger'
yield '/tiles'
yield '/tiles/tile'
yield '/tiles/tile-data'


@fixture(scope='module', params=routes(), ids=lambda route: f'POST {route}')
def route(request):
return request.param


@fixture(scope='module')
def wrong_jwt_structure():
return 'wrong_jwt_structure'


@fixture(scope='module')
def authorization_errors_expected_payload(route):
def _make_payload_message(message):
payload = {
'errors': [{
'code': AUTH_ERROR,
'message': f'Authorization failed: {message}',
'type': 'fatal'}]

}
return payload

return _make_payload_message


def test_call_with_authorization_header_failure(
route, client,
authorization_errors_expected_payload
):
response = client.post(route)

assert response.status_code == HTTPStatus.OK
assert response.json == authorization_errors_expected_payload(
'Authorization header is missing'
)


def test_call_with_wrong_authorization_type(
route, client, valid_jwt,
authorization_errors_expected_payload
):
response = client.post(
route, headers=get_headers(valid_jwt(), auth_type='wrong_type')
)

assert response.status_code == HTTPStatus.OK
assert response.json == authorization_errors_expected_payload(
'Wrong authorization type'
)


def test_call_with_wrong_jwt_structure(
route, client, wrong_jwt_structure,
authorization_errors_expected_payload
):
response = client.post(route, headers=get_headers(wrong_jwt_structure))

assert response.status_code == HTTPStatus.OK
assert response.json == authorization_errors_expected_payload(
'Wrong JWT structure'
)


@patch('requests.get')
def test_call_with_jwt_encoded_by_wrong_key(
mock_request, route,
client, valid_jwt,
authorization_errors_expected_payload,
test_keys_and_token
):
mock_request.return_value = \
mock_api_response(payload=test_keys_and_token["jwks"])

token = valid_jwt(private_key=test_keys_and_token["wrong_private_key"])
response = client.post(route, headers=get_headers(token))

assert response.status_code == HTTPStatus.OK
assert response.json == authorization_errors_expected_payload(WRONG_KEY)


@patch('requests.get')
def test_call_with_wrong_jwt_payload_structure(
mock_request,
route, client, valid_jwt,
authorization_errors_expected_payload,
test_keys_and_token
):
mock_request.return_value = \
mock_api_response(payload=test_keys_and_token["jwks"])
response = \
client.post(route,
headers=get_headers(valid_jwt(wrong_structure=True)))

assert response.status_code == HTTPStatus.OK
assert response.json == authorization_errors_expected_payload(
"Client Token/Password missing."
)


@patch('requests.get')
def test_call_with_wrong_audience(
mock_request, route, client, valid_jwt,
authorization_errors_expected_payload,
test_keys_and_token
):
mock_request.return_value = \
mock_api_response(payload=test_keys_and_token["jwks"])

response = client.post(
route,
headers=get_headers(valid_jwt(aud='wrong_aud'))
)
assert response.status_code == HTTPStatus.OK
assert response.json == authorization_errors_expected_payload(
WRONG_AUDIENCE
)


@patch('requests.get')
def test_call_with_wrong_kid(
mock_request, route, client, valid_jwt,
authorization_errors_expected_payload,
test_keys_and_token
):
mock_request.return_value = \
mock_api_response(payload=test_keys_and_token["jwks"])

response = client.post(
route,
headers=get_headers(valid_jwt(kid='wrong_kid'))
)
assert response.status_code == HTTPStatus.OK
assert response.json == authorization_errors_expected_payload(
KID_NOT_FOUND
)


@patch('requests.get')
def test_call_with_missing_jwks_host(
mock_request, route, client, valid_jwt,
authorization_errors_expected_payload,
test_keys_and_token
):
mock_request.return_value = \
mock_api_response(payload=test_keys_and_token["jwks"])

response = client.post(
route,
headers=get_headers(valid_jwt(wrong_jwks_host=True))
)
assert response.status_code == HTTPStatus.OK
assert response.json == authorization_errors_expected_payload(
JWKS_HOST_MISSING
)
107 changes: 107 additions & 0 deletions code/tests/unit/api/test_dashboard.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@


from pytest import fixture
from http import HTTPStatus
from .utils import get_headers
from unittest.mock import patch
from collections import namedtuple
from api.errors import INVALID_ARGUMENT
from ..conftest import mock_api_response

WrongCall = namedtuple('WrongCall', ('endpoint', 'payload', 'message'))


def wrong_calls():
yield WrongCall(
'/tiles/tile',
{'tile-id': 'some_value'},
"{'tile_id': ['Missing data for required field.'], "
"'tile-id': ['Unknown field.']}"
)
yield WrongCall(
'/tiles/tile',
{'tile_id': ''},
"{'tile_id': ['Field may not be blank.']}"
)
yield WrongCall(
'/tiles/tile-data',
{'tile-id': 'some_value', 'period': 'some_period'},
"{'tile_id': ['Missing data for required field.'], "
"'tile-id': ['Unknown field.']}"
)
yield WrongCall(
'/tiles/tile-data',
{'tile_id': '', 'period': 'some_period'},
"{'tile_id': ['Field may not be blank.']}"
)
yield WrongCall(
'/tiles/tile-data',
{'tile_id': 'some_value', 'not_period': 'some_period'},
"{'period': ['Missing data for required field.'], "
"'not_period': ['Unknown field.']}"
)
yield WrongCall(
'/tiles/tile-data',
{'tile_id': 'some_value', 'period': ''},
"{'period': ['Field may not be blank.']}"
)


@fixture(
scope='module',
params=wrong_calls(),
ids=lambda wrong_payload: f'{wrong_payload.endpoint}, '
f'{wrong_payload.payload}'
)
def wrong_call(request):
return request.param


@fixture(scope='module')
def invalid_argument_expected_payload():
def _make_message(message):
return {
'errors': [{
'code': INVALID_ARGUMENT,
'message': message,
'type': 'fatal'
}]
}

return _make_message


@patch('requests.get')
def test_dashboard_call_with_wrong_payload(mock_request,
wrong_call, client, valid_jwt,
invalid_argument_expected_payload,
test_keys_and_token):

mock_request.return_value = \
mock_api_response(payload=test_keys_and_token["jwks"])

response = client.post(
path=wrong_call.endpoint,
headers=get_headers(valid_jwt()),
json=wrong_call.payload
)
assert response.status_code == HTTPStatus.OK
assert response.json == invalid_argument_expected_payload(
wrong_call.message
)


def routes():
yield '/tiles'
yield '/tiles/tile'
yield '/tiles/tile-data'


@fixture(scope='module', params=routes(), ids=lambda route: f'POST {route}')
def route(request):
return request.param


def test_dashboard_call_success(route, client, valid_jwt):
response = client.post(route, headers=get_headers(valid_jwt()))
assert response.status_code == HTTPStatus.OK
Loading