Add more tests

Also, run SCA and update test.sh(s)
This commit is contained in:
Santiago Lo Coco 2023-12-07 00:25:43 -03:00
parent 2df601ddce
commit 3faa1cb12f
22 changed files with 392 additions and 76 deletions

View File

@ -1,5 +1,4 @@
exclude_dirs:
- src/tests
#tests: ['B201', 'B301']
#skips: ['B101', 'B601']
- .venv
skips: ['B105', 'B106']

View File

@ -1,15 +1,28 @@
#!/bin/bash -e
printModule() {
section_name_length=${#1}
separator_length=$((80 - ${section_name_length} - 2))
mid_separator="$(printf "%$((${separator_length} / 2))s" "" | tr ' ' '|')"
separator=$(printf "%80s" "" | tr ' ' '|')
printf "\n${separator}\n"
printf "${mid_separator} $1 ${mid_separator}"
((separator_length % 2 == 0)) || printf "|"
printf "\n${separator}\n\n"
}
if [ "${TEST_TARGET:-}" = "INTEGRATION" ]; then
/usr/src/app/.venv/bin/gunicorn manage:app
else
printModule "TESTS"
python -m pytest "src/tests" --junitxml=report.xml
python -m pytest "src/tests" -p no:warnings --cov="src" --cov-report xml
printModule "LINTING"
flake8 src --extend-ignore E221 --extend-ignore E501
black src --check
isort . --src-path src --check --profile black
# bandit -c .bandit.yml -r .
printModule "SECURITY SCAN"
bandit -c .bandit.yml -r .
fi

View File

@ -0,0 +1,2 @@
[pytest]
filterwarnings = ignore::DeprecationWarning

View File

@ -1,5 +1,4 @@
exclude_dirs:
- src/tests
#tests: ['B201', 'B301']
#skips: ['B101', 'B601']
- .venv
skips: ['B105', 'B106']

View File

@ -1,14 +1,28 @@
#!/bin/bash -e
printModule() {
section_name_length=${#1}
separator_length=$((80 - ${section_name_length} - 2))
mid_separator="$(printf "%$((${separator_length} / 2))s" "" | tr ' ' '|')"
separator=$(printf "%80s" "" | tr ' ' '|')
printf "\n${separator}\n"
printf "${mid_separator} $1 ${mid_separator}"
((separator_length % 2 == 0)) || printf "|"
printf "\n${separator}\n\n"
}
if [ "${TEST_TARGET:-}" = "INTEGRATION" ]; then
/usr/src/app/.venv/bin/gunicorn src.api.main:app --worker-class uvicorn.workers.UvicornWorker
else
printModule "TESTS"
python -m pytest "src/tests" --junitxml=report.xml
python -m pytest "src/tests" -p no:warnings --cov="src" --cov-report xml
printModule "LINTING"
flake8 src --extend-ignore E221 --extend-ignore E501
black src --check
isort . --src-path src --check --profile black
# bandit -c .bandit.yml -r .
printModule "SECURITY SCAN"
bandit -c .bandit.yml -r .
fi

View File

@ -0,0 +1,2 @@
[pytest]
filterwarnings = ignore::DeprecationWarning

View File

@ -1,46 +0,0 @@
from fastapi.testclient import TestClient
import src.api.cruds.flight
from src.api.main import app
client = TestClient(app)
mocked_flight = {
"id": 1,
"flight_code": "ABC125",
"status": "En ruta",
"origin": "Ciudad B",
"destination": "Ciudad A",
"departure_time": "2023-10-10 10:00 AM",
"arrival_time": "2023-10-10 12:00 PM",
"gate": "A2",
"user_id": 1,
}
class AttrDict(dict):
def __init__(self, *args, **kwargs):
super(AttrDict, self).__init__(*args, **kwargs)
self.__dict__ = self
def test_not_found_flight(monkeypatch):
def mock_get_flight_by_id(db, id):
return None
monkeypatch.setattr(src.api.cruds.flight, "get_flight_by_id", mock_get_flight_by_id)
resp = client.get("/flights/1")
assert resp.status_code == 404
def test_successful_get_flight(monkeypatch):
def mock_get_flight_by_id(db, id):
return mocked_flight
monkeypatch.setattr(src.api.cruds.flight, "get_flight_by_id", mock_get_flight_by_id)
response = client.get("/flights/1")
assert response.status_code == 200
assert response.json() == mocked_flight

View File

@ -4,6 +4,8 @@ import src.api.cruds.flight
from src.api.main import app
client = TestClient(app)
mocked_flight = {
"id": 1,
"flight_code": "ABC125",

View File

@ -1,5 +1,4 @@
exclude_dirs:
- src/tests
#tests: ['B201', 'B301']
#skips: ['B101', 'B601']
- .venv
skips: ['B105', 'B106']

View File

@ -3,6 +3,7 @@ pytest==7.2.2
pytest-cov==4.0.0
pytest-xdist==3.2.0
pytest-watch==4.2.0
pytest-asyncio==0.23.2
flake8==6.0.0
black==23.1.0
isort==5.12.0

View File

@ -1,17 +1,28 @@
#!/bin/bash -e
printModule() {
section_name_length=${#1}
separator_length=$((80 - ${section_name_length} - 2))
mid_separator="$(printf "%$((${separator_length} / 2))s" "" | tr ' ' '|')"
separator=$(printf "%80s" "" | tr ' ' '|')
printf "\n${separator}\n"
printf "${mid_separator} $1 ${mid_separator}"
((separator_length % 2 == 0)) || printf "|"
printf "\n${separator}\n\n"
}
if [ "${TEST_TARGET:-}" = "INTEGRATION" ]; then
/usr/src/app/.venv/bin/gunicorn src.api.main:app --worker-class uvicorn.workers.UvicornWorker --bind=0.0.0.0:5002
else
# python -m pytest "src/tests" --junitxml=report.xml
touch report.xml
# python -m pytest "src/tests" -p no:warnings --cov="src" --cov-report xml
touch coverage.xml
printModule "TESTS"
python -m pytest "src/tests" --junitxml=report.xml
python -m pytest "src/tests" -p no:warnings --cov="src" --cov-report xml
printModule "LINTING"
flake8 src --extend-ignore E221 --extend-ignore E501
black src --check
isort . --src-path src --check --profile black
# bandit -c .bandit.yml -r .
printModule "SECURITY SCAN"
bandit -c .bandit.yml -r .
fi

View File

@ -1,4 +1,5 @@
import logging
import logging.config
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware

View File

@ -0,0 +1,2 @@
[pytest]
filterwarnings = ignore::DeprecationWarning

View File

@ -0,0 +1,78 @@
from unittest.mock import ANY, Mock, patch
import pytest
from fastapi import HTTPException
from fastapi.testclient import TestClient
import src.api.routes.auth
from src.api.config import API_AUTH
from src.api.main import app
from src.api.routes.auth import checkAuth
client = TestClient(app)
@pytest.fixture(scope="module")
def mock_request():
with patch.object(src.api.routes.auth, "request", autospec=True) as m:
yield m
def test_status_endpoint(mock_request):
mock_request.return_value = (
{"role": "user", "id": 1},
200,
"Mock headers",
)
response = client.get("/auth/status")
assert response.status_code == 200
assert response.json() == {"role": "user", "id": 1}
mock_request.assert_called_once_with(
f"{API_AUTH}/status",
"GET",
headers={"Authorization": "", "x-api-request-id": ANY},
)
@pytest.fixture(scope="function")
def mock_status():
with patch.object(src.api.routes.auth, "status", autospec=True) as m:
yield m
@pytest.mark.asyncio
async def test_checkAuth_with_invalid_roles(mock_status):
mock_status.return_value = {"id": 1, "role": "airline"}
request_mock = Mock(state=Mock(request_id="mock_request_id"))
with pytest.raises(HTTPException) as exc_info:
await checkAuth(request_mock, roles=["admin"])
assert exc_info.value.status_code == 403
assert str(exc_info.value.detail) == "You don't have the required permissions."
mock_status.assert_called_once_with(request_mock, None)
@pytest.mark.asyncio
async def test_checkAuth_with_valid_user_id(mock_status):
mock_status.return_value = {"id": 1, "role": "airline"}
request_mock = Mock(state=Mock(request_id="mock_request_id"))
response = await checkAuth(request_mock, userId="1")
assert response is None
mock_status.assert_called_once_with(request_mock, None)
@pytest.mark.asyncio
async def test_checkAuth_with_invalid_user_id(mock_status):
mock_status.return_value = {"id": 1, "role": "airline"}
request_mock = Mock(state=Mock(request_id="mock_request_id"))
with pytest.raises(HTTPException) as exc_info:
await checkAuth(request_mock, userId="2")
assert exc_info.value.status_code == 403
assert str(exc_info.value.detail) == "You don't have the required permissions."
mock_status.assert_called_once_with(request_mock, None)

View File

@ -0,0 +1,102 @@
from unittest.mock import ANY, patch
import pytest
from fastapi.testclient import TestClient
import src.api.routes.auth
import src.api.routes.flights
from src.api.config import API_FLIGHTS
from src.api.main import app
client = TestClient(app)
@pytest.fixture(scope="function")
def mock_request():
with patch.object(src.api.routes.flights, "request", autospec=True) as m:
yield m
def test_get_flight_by_id(mock_request):
mock_request.return_value = (
{
"id": 1,
"flight_code": "AOZ789",
"status": "Scheduled",
"origin": "Frankfurt",
"destination": "Paris",
"departure_time": "2023-11-04 07:58 PM",
"arrival_time": "2023-11-05 03:43 AM",
"gate": "X8",
"user_id": 1,
},
200,
"Mock headers",
)
response = client.get("/flights/1")
assert response.status_code == 200
assert response.json() == {
"id": 1,
"flight_code": "AOZ789",
"status": "Scheduled",
"origin": "Frankfurt",
"destination": "Paris",
"departure_time": "2023-11-04 07:58 PM",
"arrival_time": "2023-11-05 03:43 AM",
"gate": "X8",
"user_id": 1,
}
mock_request.assert_called_once_with(
f"{API_FLIGHTS}/1", "GET", headers={"x-api-request-id": ANY}
)
@pytest.fixture(scope="function")
def mock_status():
with patch.object(src.api.routes.auth, "status", autospec=True) as m:
yield m
def test_create_flight(mock_request, mock_status):
mock_status.return_value = {"id": 1, "role": "airline"}
mock_request.return_value = (
{
"id": 1,
"flight_code": "AOZ789",
"status": "Scheduled",
"origin": "Frankfurt",
"destination": "Paris",
"departure_time": "2023-11-04 07:58 PM",
"arrival_time": "2023-11-05 03:43 AM",
"gate": "X8",
},
200,
"Mock headers",
)
response = client.post(
"/flights",
json={
"id": 1,
"flight_code": "AOZ789",
"status": "Scheduled",
"origin": "Frankfurt",
"destination": "Paris",
"departure_time": "2023-11-04 07:58 PM",
"arrival_time": "2023-11-05 03:43 AM",
"gate": "X8",
},
headers={"Authorization": "mock_token"},
)
assert response.status_code == 200
assert response.json() == {
"id": 1,
"flight_code": "AOZ789",
"status": "Scheduled",
"origin": "Frankfurt",
"destination": "Paris",
"departure_time": "2023-11-04 07:58 PM",
"arrival_time": "2023-11-05 03:43 AM",
"gate": "X8",
}

9
run.sh
View File

@ -91,7 +91,6 @@ elif [ -n "$domain" ] && [ -z "$down" ]; then
case "$domain" in
'auth')
export USER_MANAGER=auth-domain/user-manager
docker build $USER_MANAGER -f $USER_MANAGER/Dockerfile.prod -t $USER/user-manager:prod
if [ -n "$tests" ]; then
docker build $USER_MANAGER -f $USER_MANAGER/Dockerfile.test -t $USER/user-manager:test
@ -106,6 +105,7 @@ elif [ -n "$domain" ] && [ -z "$down" ]; then
docker compose -f auth-domain/docker-compose.dev.yml --env-file auth-domain/.env.dev up --abort-on-container-exit
fi
else
docker build $USER_MANAGER -f $USER_MANAGER/Dockerfile.prod -t $USER/user-manager:prod
export API_IMAGE=$USER/user-manager:prod
docker compose -f auth-domain/docker-compose.yml --env-file auth-domain/.env.prod down
docker compose -f auth-domain/docker-compose.yml --env-file auth-domain/.env.prod up -d
@ -115,7 +115,6 @@ elif [ -n "$domain" ] && [ -z "$down" ]; then
;;
'flights')
export FLIGHTS_INFORMATION=flights-domain/flights-information
docker build $FLIGHTS_INFORMATION -f $FLIGHTS_INFORMATION/Dockerfile.prod -t $USER/flights-information:prod
if [ -n "$tests" ]; then
docker build $FLIGHTS_INFORMATION -f $FLIGHTS_INFORMATION/Dockerfile.test -t $USER/flights-information:test
@ -128,6 +127,7 @@ elif [ -n "$domain" ] && [ -z "$down" ]; then
docker compose -f flights-domain/docker-compose.dev.yml --env-file flights-domain/.env.dev up --abort-on-container-exit
fi
else
docker build $FLIGHTS_INFORMATION -f $FLIGHTS_INFORMATION/Dockerfile.prod -t $USER/flights-information:prod
export API_IMAGE=$USER/flights-information:prod
docker compose -f flights-domain/docker-compose.yml --env-file flights-domain/.env.prod down
docker compose -f flights-domain/docker-compose.yml --env-file flights-domain/.env.prod up -d
@ -135,7 +135,6 @@ elif [ -n "$domain" ] && [ -z "$down" ]; then
;;
'subscription')
export SUBSCRIPTION_MANAGER=subscription-domain/subscription-manager
docker build $SUBSCRIPTION_MANAGER -f $SUBSCRIPTION_MANAGER/Dockerfile.prod -t $USER/subs-manager:prod
if [ -n "$tests" ]; then
docker build $SUBSCRIPTION_MANAGER -f $SUBSCRIPTION_MANAGER/Dockerfile.test -t $USER/subs-manager:test
@ -148,14 +147,13 @@ elif [ -n "$domain" ] && [ -z "$down" ]; then
docker compose -f subscription-domain/docker-compose.dev.yml --env-file subscription-domain/.env.dev up --abort-on-container-exit
fi
else
docker build $SUBSCRIPTION_MANAGER -f $SUBSCRIPTION_MANAGER/Dockerfile.prod -t $USER/subs-manager:prod
export API_IMAGE=$USER/subs-manager:prod
docker compose -f subscription-domain/docker-compose.yml --env-file subscription-domain/.env.prod down
docker compose -f subscription-domain/docker-compose.yml --env-file subscription-domain/.env.prod up -d
fi
;;
'gateway')
docker build gateway -f gateway/Dockerfile.prod -t $USER/gateway:prod
if [ -n "$tests" ]; then
docker build gateway -f gateway/Dockerfile.test -t $USER/gateway:test
export API_IMAGE=$USER/gateway:test
@ -167,6 +165,7 @@ elif [ -n "$domain" ] && [ -z "$down" ]; then
docker compose -f gateway/docker-compose.dev.yml --env-file gateway/.env.dev up --abort-on-container-exit
fi
else
docker build gateway -f gateway/Dockerfile.prod -t $USER/gateway:prod
export API_IMAGE=$USER/gateway:prod
docker compose -f gateway/docker-compose.yml --env-file gateway/.env.prod down
docker compose -f gateway/docker-compose.yml --env-file gateway/.env.prod up -d

View File

@ -1,5 +1,4 @@
exclude_dirs:
- src/tests
#tests: ['B201', 'B301']
#skips: ['B101', 'B601']
- .venv
skips: ['B105', 'B106']

View File

@ -1,17 +1,28 @@
#!/bin/bash -e
printModule() {
section_name_length=${#1}
separator_length=$((80 - ${section_name_length} - 2))
mid_separator="$(printf "%$((${separator_length} / 2))s" "" | tr ' ' '|')"
separator=$(printf "%80s" "" | tr ' ' '|')
printf "\n${separator}\n"
printf "${mid_separator} $1 ${mid_separator}"
((separator_length % 2 == 0)) || printf "|"
printf "\n${separator}\n\n"
}
if [ "${TEST_TARGET:-}" = "INTEGRATION" ]; then
/usr/src/app/.venv/bin/gunicorn src.api.main:app --worker-class uvicorn.workers.UvicornWorker
else
# python -m pytest "src/tests" --junitxml=report.xml
touch report.xml
# python -m pytest "src/tests" -p no:warnings --cov="src" --cov-report xml
touch coverage.xml
printModule "TESTS"
python -m pytest "src/tests" --junitxml=report.xml
python -m pytest "src/tests" -p no:warnings --cov="src" --cov-report xml
printModule "LINTING"
flake8 src --extend-ignore E221 --extend-ignore E501
black src --check
isort . --src-path src --check --profile black
# bandit -c .bandit.yml -r .
printModule "SECURITY SCAN"
bandit -c .bandit.yml -r .
fi

View File

@ -0,0 +1,68 @@
import random
import pytest
from src.api.db import Base, SessionLocal, engine
from src.api.models.subscription import Subscription
session = SessionLocal()
base = Base
@pytest.fixture(scope="module")
def test_database():
base.metadata.drop_all(bind=engine)
base.metadata.create_all(bind=engine)
yield session
session.close()
base.metadata.drop_all(bind=engine)
@pytest.fixture(scope="module")
def create_subscription():
def create_subscription(subscription: Subscription):
db_subscription = Subscription(
user_id=subscription.user_id,
flight_id=subscription.flight_id,
)
session.add(db_subscription)
session.commit()
session.refresh(db_subscription)
return db_subscription
return create_subscription
@pytest.fixture(scope="module")
def get_subscription():
def get_subscription(user_id: int, flight_id: int):
return (
session.query(Subscription)
.filter(
Subscription.user_id == user_id, Subscription.flight_id == flight_id
)
.first()
)
return get_subscription
@pytest.fixture(scope="module")
def create_subscription_on_database(test_database, create_subscription):
test_database.query(Subscription).delete()
db_subscriptions = []
for subscription in subscriptions:
db_subscriptions.append(create_subscription(subscription))
return db_subscriptions
@pytest.fixture(scope="module")
def subscription_to_create():
return random.choice(subscriptions)
subscriptions = [
Subscription(user_id=1, flight_id=1),
Subscription(user_id=1, flight_id=2),
Subscription(user_id=1, flight_id=3),
]

View File

@ -0,0 +1,26 @@
import json
from fastapi.testclient import TestClient
from src.api.main import app
from src.api.models.subscription import Subscription
client = TestClient(app)
mocked_subscription = {"user_id": 1, "flight_id": 1}
def test_post_subscription(test_database, get_subscription):
test_database.query(Subscription).delete()
api_call_retrieved_subscription = client.post(
"/subscriptions", data=json.dumps(mocked_subscription)
)
api_call_retrieved_subscription_data = api_call_retrieved_subscription.json()
db_retrieved_subscription = get_subscription(
api_call_retrieved_subscription_data["user_id"],
api_call_retrieved_subscription_data["flight_id"],
)
assert db_retrieved_subscription.user_id == mocked_subscription["user_id"]

View File

@ -0,0 +1,2 @@
[pytest]
filterwarnings = ignore::DeprecationWarning

View File

@ -0,0 +1,32 @@
from fastapi.testclient import TestClient
import src.api.cruds.subscription
from src.api.main import app
client = TestClient(app)
mocked_subscription = {"user_id": 1, "flight_id": 1}
def test_not_found_subscription(monkeypatch):
def mock_get_subscription(db, user_id: int, flight_id: int):
return None
monkeypatch.setattr(
src.api.cruds.subscription, "get_subscription", mock_get_subscription
)
resp = client.get("/subscriptions", params={"user_id": 3, "flight_id": 1})
assert resp.status_code == 404
def test_successful_get_subscription(monkeypatch):
def mock_get_subscription(db, user_id: int, flight_id: int):
return mocked_subscription
monkeypatch.setattr(
src.api.cruds.subscription, "get_subscription", mock_get_subscription
)
response = client.get("/subscriptions?user_id=1&flight_id=1")
assert response.status_code == 200
assert response.json() == mocked_subscription