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
1 change: 0 additions & 1 deletion commons/data_access_layer/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ def delete(self, id):


def init_app(app: Flask) -> None:
init_sql(app) # TODO Delete after the migration to Cosmos DB has finished.
init_cosmos_db(app)


Expand Down
12 changes: 0 additions & 12 deletions commons/data_access_layer/sql.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,8 @@
from flask_sqlalchemy import SQLAlchemy

from commons.data_access_layer.database import CRUDDao, ID_MAX_LENGTH
from time_tracker_api.security import current_user_id

db: SQLAlchemy = None
AuditedSQLModel = None


def handle_commit_issues(f):
Expand All @@ -25,16 +23,6 @@ def init_app(app: Flask) -> None:
global db
db = SQLAlchemy(app)

global AuditedSQLModel

class AuditedSQLModelClass():
created_at = db.Column(db.DateTime, server_default=db.func.now())
updated_at = db.Column(db.DateTime, onupdate=datetime.utcnow)
created_by = db.Column(db.String(ID_MAX_LENGTH), default=current_user_id)
updated_by = db.Column(db.String(ID_MAX_LENGTH), onupdate=current_user_id)

AuditedSQLModel = AuditedSQLModelClass


class SQLRepository():
def __init__(self, model_type: type):
Expand Down
7 changes: 5 additions & 2 deletions requirements/time_tracker_api/prod.txt
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,11 @@ Flask-Script==2.0.6
#Semantic versioning
python-semantic-release==5.2.0

# The Debug Toolbar
#The Debug Toolbar
Flask-DebugToolbar==0.11.0

#CORS
flask-cors==3.0.8
flask-cors==3.0.8

#JWT
PyJWT==1.7.1
7 changes: 0 additions & 7 deletions tests/commons/data_access_layer/sql_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,6 @@ def test_create(sql_repository):

assert result is not None
assert result.id is not None
assert result.created_at is not None
assert result.created_by is not None
assert result.updated_at is None
assert result.updated_by is None

existing_elements_registry.append(result)

Expand All @@ -43,9 +39,6 @@ def test_update(sql_repository):
assert updated_element.id == existing_element.id
assert updated_element.name == "Jon Snow"
assert updated_element.age == 34
assert updated_element.updated_at is not None
assert updated_element.updated_at > updated_element.created_at
assert updated_element.updated_by is not None


def test_find_all(sql_repository):
Expand Down
33 changes: 28 additions & 5 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
from datetime import datetime, timedelta

import jwt
import pytest
from faker import Faker
from flask import Flask
from flask import Flask, url_for
from flask.testing import FlaskClient

from commons.data_access_layer.cosmos_db import CosmosDBRepository, datetime_str, current_datetime
from commons.data_access_layer.database import init_sql
from time_tracker_api import create_app
from time_tracker_api.security import get_or_generate_dev_secret_key
from time_tracker_api.time_entries.time_entries_model import TimeEntryCosmosDBRepository

fake = Faker()
Expand All @@ -23,9 +28,12 @@ def client(app: Flask) -> FlaskClient:


@pytest.fixture(scope="module")
def sql_model_class():
from commons.data_access_layer.sql import db, AuditedSQLModel
class PersonSQLModel(db.Model, AuditedSQLModel):
def sql_model_class(app: Flask):
with app.app_context():
init_sql(app)

from commons.data_access_layer.sql import db
class PersonSQLModel(db.Model):
__tablename__ = 'test'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(80), unique=False, nullable=False)
Expand Down Expand Up @@ -146,5 +154,20 @@ def running_time_entry(time_entry_repository: TimeEntryCosmosDBRepository,

yield created_time_entry

time_entry_repository.delete(id=created_time_entry.id,
time_entry_repository.delete_permanently(id=created_time_entry.id,
partition_key_value=tenant_id)


@pytest.fixture(scope="session")
def valid_jwt(app: Flask, tenant_id: str, owner_id: str) -> str:
expiration_time = datetime.utcnow() + timedelta(seconds=3600)
return jwt.encode({
"iss": "https://securityioet.b2clogin.com/%s/v2.0/" % tenant_id,
"oid": owner_id,
'exp': expiration_time
}, key=get_or_generate_dev_secret_key()).decode("UTF-8")


@pytest.fixture(scope="session")
def valid_header(valid_jwt: str) -> dict:
return {'Authorization': "Bearer %s" % valid_jwt}
124 changes: 88 additions & 36 deletions tests/time_tracker_api/activities/activities_namespace_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@
from flask_restplus._http import HTTPStatus
from pytest_mock import MockFixture

from time_tracker_api.security import current_user_tenant_id

fake = Faker()

valid_activity_data = {
Expand All @@ -19,45 +17,63 @@
}).update(valid_activity_data)


def test_create_activity_should_succeed_with_valid_request(client: FlaskClient, mocker: MockFixture):
def test_create_activity_should_succeed_with_valid_request(client: FlaskClient,
mocker: MockFixture,
valid_header: dict):
from time_tracker_api.activities.activities_namespace import activity_dao
repository_create_mock = mocker.patch.object(activity_dao.repository,
'create',
return_value=fake_activity)

response = client.post("/activities", json=valid_activity_data, follow_redirects=True)
response = client.post("/activities",
headers=valid_header,
json=valid_activity_data,
follow_redirects=True)

assert HTTPStatus.CREATED == response.status_code
repository_create_mock.assert_called_once()


def test_create_activity_should_reject_bad_request(client: FlaskClient, mocker: MockFixture):
def test_create_activity_should_reject_bad_request(client: FlaskClient,
mocker: MockFixture,
valid_header: dict):
from time_tracker_api.activities.activities_namespace import activity_dao
repository_create_mock = mocker.patch.object(activity_dao.repository,
'create',
return_value=fake_activity)

response = client.post("/activities", json=None, follow_redirects=True)
response = client.post("/activities",
headers=valid_header,
json=None,
follow_redirects=True)

assert HTTPStatus.BAD_REQUEST == response.status_code
repository_create_mock.assert_not_called()


def test_list_all_activities(client: FlaskClient, mocker: MockFixture):
def test_list_all_activities(client: FlaskClient,
mocker: MockFixture,
tenant_id: str,
valid_header: dict):
from time_tracker_api.activities.activities_namespace import activity_dao
repository_find_all_mock = mocker.patch.object(activity_dao.repository,
'find_all',
return_value=[])

response = client.get("/activities", follow_redirects=True)
response = client.get("/activities",
headers=valid_header,
follow_redirects=True)

assert HTTPStatus.OK == response.status_code
json_data = json.loads(response.data)
assert [] == json_data
repository_find_all_mock.assert_called_once()
repository_find_all_mock.assert_called_once_with(partition_key_value=tenant_id)


def test_get_activity_should_succeed_with_valid_id(client: FlaskClient, mocker: MockFixture):
def test_get_activity_should_succeed_with_valid_id(client: FlaskClient,
mocker: MockFixture,
tenant_id: str,
valid_header: dict):
from time_tracker_api.activities.activities_namespace import activity_dao

valid_id = fake.random_int(1, 9999)
Expand All @@ -66,15 +82,19 @@ def test_get_activity_should_succeed_with_valid_id(client: FlaskClient, mocker:
'find',
return_value=fake_activity)

response = client.get("/activities/%s" % valid_id, follow_redirects=True)
response = client.get("/activities/%s" % valid_id,
headers=valid_header,
follow_redirects=True)

assert HTTPStatus.OK == response.status_code
fake_activity == json.loads(response.data)
repository_find_mock.assert_called_once_with(str(valid_id),
partition_key_value=current_user_tenant_id())
repository_find_mock.assert_called_once_with(str(valid_id), partition_key_value=tenant_id)


def test_get_activity_should_return_not_found_with_invalid_id(client: FlaskClient, mocker: MockFixture):
def test_get_activity_should_return_not_found_with_invalid_id(client: FlaskClient,
mocker: MockFixture,
tenant_id: str,
valid_header: dict):
from time_tracker_api.activities.activities_namespace import activity_dao
from werkzeug.exceptions import NotFound

Expand All @@ -84,14 +104,17 @@ def test_get_activity_should_return_not_found_with_invalid_id(client: FlaskClien
'find',
side_effect=NotFound)

response = client.get("/activities/%s" % invalid_id, follow_redirects=True)
response = client.get("/activities/%s" % invalid_id,
headers=valid_header,
follow_redirects=True)

assert HTTPStatus.NOT_FOUND == response.status_code
repository_find_mock.assert_called_once_with(str(invalid_id),
partition_key_value=current_user_tenant_id())
partition_key_value=tenant_id)


def test_get_activity_should_return_422_for_invalid_id_format(client: FlaskClient, mocker: MockFixture):
def test_get_activity_should_return_422_for_invalid_id_format(client: FlaskClient,
mocker: MockFixture):
from time_tracker_api.activities.activities_namespace import activity_dao
from werkzeug.exceptions import UnprocessableEntity

Expand All @@ -104,41 +127,54 @@ def test_get_activity_should_return_422_for_invalid_id_format(client: FlaskClien
response = client.get("/activities/%s" % invalid_id, follow_redirects=True)

assert HTTPStatus.UNPROCESSABLE_ENTITY == response.status_code
repository_find_mock.assert_called_once_with(str(invalid_id),
partition_key_value=current_user_tenant_id())
repository_find_mock.assert_not_called()


def test_update_activity_should_succeed_with_valid_data(client: FlaskClient, mocker: MockFixture):
def test_update_activity_should_succeed_with_valid_data(client: FlaskClient,
tenant_id: str,
mocker: MockFixture,
valid_header: dict):
from time_tracker_api.activities.activities_namespace import activity_dao

repository_update_mock = mocker.patch.object(activity_dao.repository,
'partial_update',
return_value=fake_activity)

valid_id = fake.random_int(1, 9999)
response = client.put("/activities/%s" % valid_id, json=valid_activity_data, follow_redirects=True)
valid_id = fake.uuid4()
response = client.put("/activities/%s" % valid_id,
headers=valid_header,
json=valid_activity_data,
follow_redirects=True)

assert HTTPStatus.OK == response.status_code
fake_activity == json.loads(response.data)
repository_update_mock.assert_called_once_with(str(valid_id),
changes=valid_activity_data,
partition_key_value=current_user_tenant_id())
partition_key_value=tenant_id)


def test_update_activity_should_reject_bad_request(client: FlaskClient, mocker: MockFixture):
def test_update_activity_should_reject_bad_request(client: FlaskClient,
mocker: MockFixture,
valid_header: dict):
from time_tracker_api.activities.activities_namespace import activity_dao
repository_update_mock = mocker.patch.object(activity_dao.repository,
'partial_update',
return_value=fake_activity)

valid_id = fake.random_int(1, 9999)
response = client.put("/activities/%s" % valid_id, json=None, follow_redirects=True)
response = client.put("/activities/%s" % valid_id,
headers=valid_header,
json=None,
follow_redirects=True)

assert HTTPStatus.BAD_REQUEST == response.status_code
repository_update_mock.assert_not_called()


def test_update_activity_should_return_not_found_with_invalid_id(client: FlaskClient, mocker: MockFixture):
def test_update_activity_should_return_not_found_with_invalid_id(client: FlaskClient,
tenant_id: str,
mocker: MockFixture,
valid_header: dict):
from time_tracker_api.activities.activities_namespace import activity_dao
from werkzeug.exceptions import NotFound

Expand All @@ -149,16 +185,20 @@ def test_update_activity_should_return_not_found_with_invalid_id(client: FlaskCl
side_effect=NotFound)

response = client.put("/activities/%s" % invalid_id,
headers=valid_header,
json=valid_activity_data,
follow_redirects=True)

assert HTTPStatus.NOT_FOUND == response.status_code
repository_update_mock.assert_called_once_with(str(invalid_id),
changes=valid_activity_data,
partition_key_value=current_user_tenant_id())
partition_key_value=tenant_id)


def test_delete_activity_should_succeed_with_valid_id(client: FlaskClient, mocker: MockFixture):
def test_delete_activity_should_succeed_with_valid_id(client: FlaskClient,
mocker: MockFixture,
tenant_id: str,
valid_header: dict):
from time_tracker_api.activities.activities_namespace import activity_dao

valid_id = fake.random_int(1, 9999)
Expand All @@ -167,15 +207,20 @@ def test_delete_activity_should_succeed_with_valid_id(client: FlaskClient, mocke
'delete',
return_value=None)

response = client.delete("/activities/%s" % valid_id, follow_redirects=True)
response = client.delete("/activities/%s" % valid_id,
headers=valid_header,
follow_redirects=True)

assert HTTPStatus.NO_CONTENT == response.status_code
assert b'' == response.data
repository_remove_mock.assert_called_once_with(str(valid_id),
partition_key_value=current_user_tenant_id())
partition_key_value=tenant_id)


def test_delete_activity_should_return_not_found_with_invalid_id(client: FlaskClient, mocker: MockFixture):
def test_delete_activity_should_return_not_found_with_invalid_id(client: FlaskClient,
mocker: MockFixture,
tenant_id: str,
valid_header: dict):
from time_tracker_api.activities.activities_namespace import activity_dao
from werkzeug.exceptions import NotFound

Expand All @@ -185,14 +230,19 @@ def test_delete_activity_should_return_not_found_with_invalid_id(client: FlaskCl
'delete',
side_effect=NotFound)

response = client.delete("/activities/%s" % invalid_id, follow_redirects=True)
response = client.delete("/activities/%s" % invalid_id,
headers=valid_header,
follow_redirects=True)

assert HTTPStatus.NOT_FOUND == response.status_code
repository_remove_mock.assert_called_once_with(str(invalid_id),
partition_key_value=current_user_tenant_id())
partition_key_value=tenant_id)


def test_delete_activity_should_return_422_for_invalid_id_format(client: FlaskClient, mocker: MockFixture):
def test_delete_activity_should_return_422_for_invalid_id_format(client: FlaskClient,
mocker: MockFixture,
tenant_id: str,
valid_header: dict):
from time_tracker_api.activities.activities_namespace import activity_dao
from werkzeug.exceptions import UnprocessableEntity

Expand All @@ -202,8 +252,10 @@ def test_delete_activity_should_return_422_for_invalid_id_format(client: FlaskCl
'delete',
side_effect=UnprocessableEntity)

response = client.delete("/activities/%s" % invalid_id, follow_redirects=True)
response = client.delete("/activities/%s" % invalid_id,
headers=valid_header,
follow_redirects=True)

assert HTTPStatus.UNPROCESSABLE_ENTITY == response.status_code
repository_remove_mock.assert_called_once_with(str(invalid_id),
partition_key_value=current_user_tenant_id())
partition_key_value=tenant_id)
Loading