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
10 changes: 10 additions & 0 deletions V2/serverless.yml
Original file line number Diff line number Diff line change
Expand Up @@ -96,3 +96,13 @@ functions:
- DELETE
route: time-entries/{id}
authLevel: anonymous

create_customer:
handler: time_tracker/customers/interface.create_customer
events:
- http: true
x-azure-settings:
methods:
- POST
route: customers/
authLevel: anonymous
49 changes: 49 additions & 0 deletions V2/tests/api/azure/customer_azure_endpoints_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import json
from faker import Faker

import azure.functions as func

import time_tracker.customers._application._customers as azure_customers

CUSTOMER_URL = "/api/customers/"


def test__customer_azure_endpoint__creates_a_customer__when_customer_has_all_necesary_attributes(
customer_factory
):
customer_body = customer_factory().__dict__

body = json.dumps(customer_body).encode("utf-8")
req = func.HttpRequest(
method='POST',
body=body,
url=CUSTOMER_URL,
)

response = azure_customers._create_customer.create_customer(req)
customer_json_data = json.loads(response.get_body())
customer_body['id'] = customer_json_data['id']

assert response.status_code == 201
assert customer_json_data == customer_body


def test__customer_azure_endpoint__returns_a_status_400__when_dont_recieve_all_necessary_attributes():
customer_to_insert = {
"id": None,
"name": Faker().user_name(),
"deleted": False,
"status": 1
}

body = json.dumps(customer_to_insert).encode("utf-8")
req = func.HttpRequest(
method='POST',
body=body,
url=CUSTOMER_URL,
)

response = azure_customers._create_customer.create_customer(req)

assert response.status_code == 400
assert response.get_body() == b'Invalid format or structure of the attributes of the customer'
1 change: 1 addition & 0 deletions V2/tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# flake8: noqa
from fixtures import _activity_factory, _test_db, _insert_activity
from fixtures import _time_entry_factory
from fixtures import _customer_factory
23 changes: 22 additions & 1 deletion V2/tests/fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@
from faker import Faker

import time_tracker.activities._domain as activities_domain
import time_tracker.activities._infrastructure as activities_infrastructure
import time_tracker.time_entries._domain as time_entries_domain
import time_tracker.customers._domain as customers_domain
import time_tracker.activities._infrastructure as activities_infrastructure
from time_tracker._infrastructure import DB


Expand Down Expand Up @@ -73,3 +74,23 @@ def _new_activity(activity: activities_domain.Activity, database: DB):
new_activity = dao.create(activity)
return new_activity
return _new_activity


@pytest.fixture(name='customer_factory')
def _customer_factory() -> customers_domain.Customer:
def _make_customer(
name: str = Faker().name(),
description: str = Faker().sentence(),
deleted: bool = False,
status: int = 1,
):
customer = customers_domain.Customer(
id=None,
name=name,
description=description,
deleted=deleted,
status=status
)
return customer

return _make_customer
35 changes: 35 additions & 0 deletions V2/tests/integration/daos/customers_dao_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import pytest

import time_tracker.customers._domain as domain
import time_tracker.customers._infrastructure as infrastructure
from time_tracker._infrastructure import DB


@pytest.fixture(name='create_fake_dao')
def _fake_dao() -> domain.CustomersDao:
def _create_fake_dao(db_fake: DB) -> domain.CustomersDao:
dao = infrastructure.CustomersSQLDao(db_fake)
return dao
return _create_fake_dao


@pytest.fixture(name='clean_database', autouse=True)
def _clean_database():
yield
db_fake = DB()
dao = infrastructure.CustomersSQLDao(db_fake)
query = dao.customer.delete()
dao.db.get_session().execute(query)


def test__customer_dao__returns_a_customer_dto__when_saves_correctly_with_sql_database(
test_db, customer_factory, create_fake_dao
):
dao = create_fake_dao(test_db)

customer_to_insert = customer_factory()

inserted_customer = dao.create(customer_to_insert)

assert isinstance(inserted_customer, domain.Customer)
assert inserted_customer == customer_to_insert
14 changes: 14 additions & 0 deletions V2/tests/unit/services/customer_service_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from time_tracker.customers._domain import CustomerService


def test__create_customer__uses_the_customer_dao__to_create_a_customer(mocker, customer_factory):
expected_customer = mocker.Mock()
customer_dao = mocker.Mock(
create=mocker.Mock(return_value=expected_customer)
)
customer_service = CustomerService(customer_dao)

new_customer = customer_service.create(customer_factory())

assert customer_dao.create.called
assert expected_customer == new_customer
18 changes: 18 additions & 0 deletions V2/tests/unit/use_cases/customers_use_case_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from pytest_mock import MockFixture

from time_tracker.customers._domain import _use_cases


def test__create_customer_function__uses_the_customer_service__to_create_a_customer(
mocker: MockFixture, customer_factory
):
expected_customer = mocker.Mock()
customer_service = mocker.Mock(
create=mocker.Mock(return_value=expected_customer)
)

customer_use_case = _use_cases.CreateCustomerUseCase(customer_service)
new_customer = customer_use_case.create_customer(customer_factory())

assert customer_service.create.called
assert expected_customer == new_customer
2 changes: 2 additions & 0 deletions V2/time_tracker/customers/_application/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# flake8: noqa
from ._customers import create_customer
2 changes: 2 additions & 0 deletions V2/time_tracker/customers/_application/_customers/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# flake8: noqa
from ._create_customer import create_customer
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import dataclasses
import json
import typing

import azure.functions as func

from ... import _domain
from ... import _infrastructure
from time_tracker._infrastructure import DB


def create_customer(req: func.HttpRequest) -> func.HttpResponse:
try:
database = DB()
customer_dao = _infrastructure.CustomersSQLDao(database)
customer_service = _domain.CustomerService(customer_dao)
use_case = _domain._use_cases.CreateCustomerUseCase(customer_service)
customer_data = req.get_json()

customer_is_valid = _validate_customer(customer_data)
if not customer_is_valid:
raise ValueError

customer_to_create = _domain.Customer(
id=None,
deleted=None,
status=None,
name=str(customer_data["name"]).strip(),
description=str(customer_data["description"]),
)
created_customer = use_case.create_customer(customer_to_create)

if created_customer:
body = json.dumps(created_customer.__dict__)
status_code = 201
else:
body = b'This customer already exists'
status_code = 409

return func.HttpResponse(
body=body,
status_code=status_code,
mimetype="application/json"
)
except ValueError:
return func.HttpResponse(
body=b'Invalid format or structure of the attributes of the customer',
status_code=400,
mimetype="application/json"
)


def _validate_customer(customer_data: dict) -> bool:
if [field.name for field in dataclasses.fields(_domain.Customer)
if (field.name not in customer_data) and (field.type != typing.Optional[field.type])]:
return False
return True
7 changes: 7 additions & 0 deletions V2/time_tracker/customers/_domain/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# flake8: noqa
from ._entities import Customer
from ._persistence_contracts import CustomersDao
from ._services import CustomerService
from ._use_cases import (
CreateCustomerUseCase,
)
2 changes: 2 additions & 0 deletions V2/time_tracker/customers/_domain/_entities/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# flake8: noqa
from ._customer import Customer
11 changes: 11 additions & 0 deletions V2/time_tracker/customers/_domain/_entities/_customer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from dataclasses import dataclass
import typing


@dataclass(frozen=True)
class Customer:
id: typing.Optional[int]
name: str
description: str
deleted: typing.Optional[bool]
status: typing.Optional[int]
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# flake8: noqa
from ._customers_dao import CustomersDao
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import abc

from time_tracker.customers._domain import Customer


class CustomersDao(abc.ABC):
@abc.abstractmethod
def create(self, data: Customer) -> Customer:
pass
2 changes: 2 additions & 0 deletions V2/time_tracker/customers/_domain/_services/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# flake8: noqa
from ._customer import CustomerService
10 changes: 10 additions & 0 deletions V2/time_tracker/customers/_domain/_services/_customer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from time_tracker.customers._domain import Customer, CustomersDao


class CustomerService:

def __init__(self, customer_dao: CustomersDao):
self.customer_dao = customer_dao

def create(self, data: Customer) -> Customer:
return self.customer_dao.create(data)
2 changes: 2 additions & 0 deletions V2/time_tracker/customers/_domain/_use_cases/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# flake8: noqa
from ._create_customer_use_case import CreateCustomerUseCase
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from time_tracker.customers._domain import Customer, CustomerService


class CreateCustomerUseCase:

def __init__(self, customer_service: CustomerService):
self.customer_service = customer_service

def create_customer(self, data: Customer) -> Customer:
return self.customer_service.create(data)
2 changes: 2 additions & 0 deletions V2/time_tracker/customers/_infrastructure/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# flake8: noqa
from ._data_persistence import CustomersSQLDao
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# flake8: noqa
from ._customer_dao import CustomersSQLDao
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import dataclasses

import sqlalchemy as sq

import time_tracker.customers._domain as domain
from time_tracker._infrastructure import _db


class CustomersSQLDao(domain.CustomersDao):

def __init__(self, database: _db.DB):
self.customer_key = [field.name for field in dataclasses.fields(domain.Customer)]
self.db = database
self.customer = sq.Table(
'customer',
self.db.metadata,
sq.Column('id', sq.Integer, primary_key=True, autoincrement=True),
sq.Column('name', sq.String, unique=True, nullable=False),
sq.Column('description', sq.String),
sq.Column('deleted', sq.Boolean),
sq.Column('status', sq.Integer),
extend_existing=True,
)

def create(self, data: domain.Customer) -> domain.Customer:
try:
new_customer = data.__dict__
new_customer.pop('id', None)
new_customer['deleted'] = False
new_customer['status'] = 1

query = self.customer.insert().values(new_customer).return_defaults()
customer = self.db.get_session().execute(query)
new_customer.update({"id": customer.inserted_primary_key[0]})
return self.__create_customer_dto(new_customer)
except sq.exc.IntegrityError:
return None

def __create_customer_dto(self, customer: dict) -> domain.Customer:
customer = {key: customer.get(key) for key in self.customer_key}
return domain.Customer(**customer)
2 changes: 2 additions & 0 deletions V2/time_tracker/customers/interface.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# flake8: noqa
from ._application import create_customer