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: technologies endpoint #200
  • Loading branch information
Angeluz-07 committed Aug 14, 2020
commit 36f239aa23f2259ac4f9a00cebe1ba593536f70a
26 changes: 26 additions & 0 deletions migrations/03-add-technologies.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
def up():
from commons.data_access_layer.cosmos_db import cosmos_helper
import azure.cosmos.exceptions as exceptions
from . import app

app.logger.info("Creating technology container...")

try:
app.logger.info('- Technologies')
from time_tracker_api.technologies.technologies_model import (
container_definition as technologies_definition,
)

cosmos_helper.create_container(technologies_definition)

except exceptions.CosmosResourceExistsError as e:
app.logger.warning(
"Unexpected error while creating initial database schema: %s"
% e.message
)

app.logger.info("Done!")


def down():
print("Not implemented!")
Empty file.
192 changes: 192 additions & 0 deletions tests/time_tracker_api/technologies/technologies_namespace_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
from unittest.mock import ANY, Mock

import pytest

from faker import Faker
from flask.testing import FlaskClient
from flask_restplus._http import HTTPStatus

from werkzeug.exceptions import NotFound, UnprocessableEntity, HTTPException


@pytest.fixture
def technology_dao():
from time_tracker_api.technologies.technologies_namespace import (
technology_dao,
)

return technology_dao


def test_list_all_technologies(
client: FlaskClient, valid_header: dict, technology_dao
):
technology_dao.repository.find_all = Mock(return_value=[])
response = client.get("/technologies", headers=valid_header)
assert HTTPStatus.OK == response.status_code


def test_get_technology_suceeds_with_valid_id(
client: FlaskClient, valid_header: dict, technology_dao
):
technology_dao.repository.find = Mock(return_value={})
id: str = Faker().uuid4()

response = client.get(f"/technologies/{id}", headers=valid_header)

assert HTTPStatus.OK == response.status_code
technology_dao.repository.find.assert_called_once_with(id, ANY)


def test_create_technology_suceeds_with_valid_input(
client: FlaskClient, valid_header: dict, technology_dao
):
technology_dao.repository.create = Mock(return_value={})
payload = {
'name': 'neo4j',
'creation_date': '2020-04-01T05:00:00+00:00',
'first_use_time_entry_id': Faker().uuid4(),
}

response = client.post("/technologies", headers=valid_header, json=payload)

assert HTTPStatus.CREATED == response.status_code
technology_dao.repository.create.assert_called_once()


def test_create_technology_fails_with_invalid_input(
client: FlaskClient, valid_header: dict, technology_dao
):
technology_dao.repository.create = Mock(return_value={})

response = client.post("/technologies", headers=valid_header, json={})

assert HTTPStatus.BAD_REQUEST == response.status_code
technology_dao.repository.create.assert_not_called()


def test_update_technology_succeeds_with_valid_input(
client: FlaskClient, valid_header: dict, technology_dao
):

technology_dao.repository.partial_update = Mock({})
id: str = Faker().uuid4()
payload = {
'name': 'neo4j',
'creation_date': '2020-04-01T05:00:00+00:00',
'first_use_time_entry_id': Faker().uuid4(),
}

response = client.put(
f"/technologies/{id}", headers=valid_header, json=payload
)

assert HTTPStatus.OK == response.status_code
technology_dao.repository.partial_update.assert_called_once_with(
id, payload, ANY
)


def test_update_technology_fails_with_invalid_input(
client: FlaskClient, valid_header: dict, technology_dao
):

technology_dao.repository.partial_update = Mock({})
id: str = Faker().uuid4()

response = client.put(
f"/technologies/{id}", headers=valid_header, json=None
)

assert HTTPStatus.BAD_REQUEST == response.status_code
technology_dao.repository.partial_update.assert_not_called()


def test_delete_technology_suceeds(
client: FlaskClient, valid_header: dict, technology_dao
):
technology_dao.repository.delete = Mock(None)
id: str = Faker().uuid4()

response = client.delete(f'/technologies/{id}', headers=valid_header)

assert HTTPStatus.NO_CONTENT == response.status_code
assert b'' == response.data
technology_dao.repository.delete.assert_called_once_with(id, ANY)


@pytest.mark.parametrize(
'http_exception,http_status',
[
(NotFound, HTTPStatus.NOT_FOUND),
(UnprocessableEntity, HTTPStatus.UNPROCESSABLE_ENTITY),
],
)
def test_get_technology_raise_http_exception_on_error(
client: FlaskClient,
valid_header: dict,
http_exception: HTTPException,
http_status: tuple,
technology_dao,
):

technology_dao.repository.find = Mock(side_effect=http_exception)
id: str = Faker().uuid4()

response = client.get(f"/technologies/{id}", headers=valid_header)

assert http_status == response.status_code
technology_dao.repository.find.assert_called_once_with(id, ANY)


@pytest.mark.parametrize(
'http_exception,http_status',
[
(NotFound, HTTPStatus.NOT_FOUND),
(UnprocessableEntity, HTTPStatus.UNPROCESSABLE_ENTITY),
],
)
def test_delete_technology_raise_http_exception_on_error(
client: FlaskClient,
valid_header: dict,
http_exception: HTTPException,
http_status: tuple,
technology_dao,
):

technology_dao.repository.delete = Mock(side_effect=http_exception)
id: str = Faker().uuid4()

response = client.delete(f"/technologies/{id}", headers=valid_header)

assert http_status == response.status_code
technology_dao.repository.delete.assert_called_once_with(id, ANY)


@pytest.mark.parametrize(
'http_exception,http_status',
[
(NotFound, HTTPStatus.NOT_FOUND),
(UnprocessableEntity, HTTPStatus.UNPROCESSABLE_ENTITY),
],
)
def test_update_technology_raise_http_exception_on_error(
client: FlaskClient,
valid_header: dict,
http_exception: HTTPException,
http_status: tuple,
technology_dao,
):

technology_dao.repository.partial_update = Mock(side_effect=http_exception)
id: str = Faker().uuid4()
payload = {}

response = client.put(
f"/technologies/{id}", headers=valid_header, json=payload
)

assert http_status == response.status_code
technology_dao.repository.partial_update.assert_called_once_with(
id, payload, ANY
)
46 changes: 35 additions & 11 deletions time_tracker_api/api.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
from azure.cosmos.exceptions import CosmosResourceExistsError, CosmosResourceNotFoundError, CosmosHttpResponseError
from azure.cosmos.exceptions import (
CosmosResourceExistsError,
CosmosResourceNotFoundError,
CosmosHttpResponseError,
)
from faker import Faker
from flask import current_app as app, Flask
from flask_restplus import Api, fields, Model
Expand Down Expand Up @@ -30,17 +34,24 @@ def remove_required_constraint(model: Model):
return result


def create_attributes_filter(ns: namespace, model: Model, filter_attrib_names: list) -> RequestParser:
def create_attributes_filter(
ns: namespace, model: Model, filter_attrib_names: list
) -> RequestParser:
attribs_parser = ns.parser()
model_attributes = model.resolved
for attrib in filter_attrib_names:
if attrib not in model_attributes:
raise ValueError(f"{attrib} is not a valid filter attribute for {model.name}")

attribs_parser.add_argument(attrib, required=False,
store_missing=False,
help="(Filter) %s " % model_attributes[attrib].description,
location='args')
raise ValueError(
f"{attrib} is not a valid filter attribute for {model.name}"
)

attribs_parser.add_argument(
attrib,
required=False,
store_missing=False,
help="(Filter) %s " % model_attributes[attrib].description,
location='args',
)

return attribs_parser

Expand Down Expand Up @@ -103,6 +114,10 @@ def init_app(app: Flask):

api.add_namespace(customers_namespace.ns)

from time_tracker_api.technologies import technologies_namespace

api.add_namespace(technologies_namespace.ns)


"""
Error handlers
Expand All @@ -122,12 +137,18 @@ def handle_not_found_errors(error):

@api.errorhandler(CosmosHttpResponseError)
def handle_cosmos_http_response_error(error):
return {'message': 'Invalid request. Please verify your data.'}, HTTPStatus.BAD_REQUEST
return (
{'message': 'Invalid request. Please verify your data.'},
HTTPStatus.BAD_REQUEST,
)


@api.errorhandler(AttributeError)
def handle_attribute_error(error):
return {'message': "There are missing attributes"}, HTTPStatus.UNPROCESSABLE_ENTITY
return (
{'message': "There are missing attributes"},
HTTPStatus.UNPROCESSABLE_ENTITY,
)


@api.errorhandler(CustomError)
Expand All @@ -138,4 +159,7 @@ def handle_custom_error(error):
@api.errorhandler
def default_error_handler(error):
app.logger.error(error)
return {'message': 'An unhandled exception occurred.'}, HTTPStatus.INTERNAL_SERVER_ERROR
return (
{'message': 'An unhandled exception occurred.'},
HTTPStatus.INTERNAL_SERVER_ERROR,
)
52 changes: 52 additions & 0 deletions time_tracker_api/technologies/technologies_model.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
from dataclasses import dataclass

from azure.cosmos import PartitionKey

from commons.data_access_layer.cosmos_db import (
CosmosDBModel,
CosmosDBDao,
CosmosDBRepository,
)
from time_tracker_api.database import CRUDDao, APICosmosDBDao


class TechnologyDao(CRUDDao):
pass


container_definition = {
'id': 'technology',
'partition_key': PartitionKey(path='/tenant_id'),
'unique_key_policy': {'uniqueKeys': [{'paths': ['/name']},]},
}


@dataclass()
class TechnologyCosmosDBModel(CosmosDBModel):
id: str
name: str
first_use_time_entry_id: str
creation_date: str
deleted: str
tenant_id: str

def __init__(self, data):
super(TechnologyCosmosDBModel, self).__init__(data) # pragma: no cover

def __repr__(self):
return '<Technology %r>' % self.name # pragma: no cover

def __str___(self):
return "the Technology \"%s\"" % self.name # pragma: no cover


def create_dao() -> TechnologyDao:
repository = CosmosDBRepository.from_definition(
container_definition, mapper=TechnologyCosmosDBModel
)

class TechnologyCosmosDBDao(APICosmosDBDao, TechnologyDao):
def __init__(self):
CosmosDBDao.__init__(self, repository)

return TechnologyCosmosDBDao()
Loading