Skip to content

Commit 7a8df35

Browse files
author
Andrés Soto
committed
refactor: TT-356 Solving merge conflicts
2 parents 193261f + 2c6e148 commit 7a8df35

File tree

15 files changed

+397
-9
lines changed

15 files changed

+397
-9
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
<!--next-version-placeholder-->
44

5+
## v0.39.1 (2021-10-06)
6+
### Fix
7+
* TT-339 skip users with azureioet.onmicrosoft.com extension from user search ([#322](https://github.com/ioet/time-tracker-backend/issues/322)) ([`8b37d4a`](https://github.com/ioet/time-tracker-backend/commit/8b37d4a7a890b9e4880efedd19dc733e60c5e7cf))
8+
59
## v0.39.0 (2021-10-04)
610
### Feature
711
* TT-353 Create V2 Activities DAO ([#320](https://github.com/ioet/time-tracker-backend/issues/320)) ([`328ad43`](https://github.com/ioet/time-tracker-backend/commit/328ad43e3058de3c824b2feec47530bee5b23823))
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
from flask import Flask
2+
from flask_wtf.csrf import CSRFProtect
3+
from flask_restplus import Namespace, Resource, Api
4+
from http import HTTPStatus
5+
from . import activities_endpoints
6+
7+
csrf = CSRFProtect()
8+
9+
10+
def create_app(test_config=None):
11+
app = Flask(__name__)
12+
csrf.init_app(app)
13+
14+
api = Api(
15+
app,
16+
version='1.0',
17+
title='Time Tracker API',
18+
description='API for the TimeTracker project',
19+
)
20+
21+
if test_config is not None:
22+
app.config.from_mapping(test_config)
23+
24+
activities_namespace = Namespace('activities', description='Endpoint for activities')
25+
activities_namespace.route('/')(activities_endpoints.Activities)
26+
activities_namespace.route('/<string:activity_id>')(activities_endpoints.Activity)
27+
28+
api.add_namespace(activities_namespace)
29+
30+
return app
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
from V2.source.daos.activities_json_dao import ActivitiesJsonDao
2+
from V2.source.services.activity_service import ActivityService
3+
from V2.source import use_cases
4+
from flask_restplus import Resource
5+
from http import HTTPStatus
6+
7+
JSON_PATH = './V2/source/activities_data.json'
8+
9+
10+
class Activities(Resource):
11+
def get(self):
12+
activities = use_cases.GetActivitiesUseCase(
13+
create_activity_service(JSON_PATH)
14+
)
15+
return [activity.__dict__ for activity in activities.get_activities()]
16+
17+
18+
class Activity(Resource):
19+
def get(self, activity_id: str):
20+
try:
21+
activity = use_cases.GetActivityUseCase(
22+
create_activity_service(JSON_PATH)
23+
)
24+
return activity.get_activity_by_id(activity_id).__dict__
25+
except AttributeError:
26+
return {'message': 'Activity not found'}, HTTPStatus.NOT_FOUND
27+
28+
29+
def create_activity_service(path: str):
30+
activity_json = ActivitiesJsonDao(path)
31+
return ActivityService(activity_json)

V2/source/use_cases/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
from ._get_activities_use_case import GetActivitiesUseCase
2+
from ._get_activity_by_id_use_case import GetActivityUseCase
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
from V2.source.services.activity_service import ActivityService
2+
from V2.source.dtos.activity import Activity
3+
import typing
4+
5+
6+
class GetActivitiesUseCase:
7+
def __init__(self, activity_service: ActivityService):
8+
self.activity_service = activity_service
9+
10+
def get_activities(self) -> typing.List[Activity]:
11+
return self.activity_service.get_all()
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
from V2.source.services.activity_service import ActivityService
2+
from V2.source.dtos.activity import Activity
3+
4+
5+
class GetActivityUseCase:
6+
def __init__(self, activity_service: ActivityService):
7+
self.activity_service = activity_service
8+
9+
def get_activity_by_id(self, id: str) -> Activity:
10+
return self.activity_service.get_by_id(id)
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
from V2.source.entry_points.flask_api import create_app
2+
import json
3+
import pytest
4+
import typing
5+
from flask.testing import FlaskClient
6+
from http import HTTPStatus
7+
from faker import Faker
8+
import shutil
9+
10+
11+
@pytest.fixture
12+
def client():
13+
app = create_app({'TESTING': True})
14+
with app.test_client() as client:
15+
yield client
16+
17+
18+
@pytest.fixture
19+
def activities_json(tmpdir_factory):
20+
temporary_directory = tmpdir_factory.mktemp("tmp")
21+
json_file = temporary_directory.join("activities.json")
22+
activities = [
23+
{
24+
'id': 'c61a4a49-3364-49a3-a7f7-0c5f2d15072b',
25+
'name': 'Development',
26+
'description': 'Development',
27+
'deleted': 'b4327ba6-9f96-49ee-a9ac-3c1edf525172',
28+
'status': None,
29+
'tenant_id': 'cc925a5d-9644-4a4f-8d99-0bee49aadd05',
30+
},
31+
{
32+
'id': '94ec92e2-a500-4700-a9f6-e41eb7b5507c',
33+
'name': 'Management',
34+
'description': None,
35+
'deleted': '7cf6efe5-a221-4fe4-b94f-8945127a489a',
36+
'status': None,
37+
'tenant_id': 'cc925a5d-9644-4a4f-8d99-0bee49aadd05',
38+
},
39+
{
40+
'id': 'd45c770a-b1a0-4bd8-a713-22c01a23e41b',
41+
'name': 'Operations',
42+
'description': 'Operation activities performed.',
43+
'deleted': '7cf6efe5-a221-4fe4-b94f-8945127a489a',
44+
'status': 'active',
45+
'tenant_id': 'cc925a5d-9644-4a4f-8d99-0bee49aadd05',
46+
},
47+
]
48+
49+
with open(json_file, 'w') as outfile:
50+
json.dump(activities, outfile)
51+
52+
with open(json_file) as outfile:
53+
activities_json = json.load(outfile)
54+
55+
yield activities_json
56+
shutil.rmtree(temporary_directory)
57+
58+
59+
def test_test__activity_endpoint__returns_all_activities(
60+
client: FlaskClient, activities_json: typing.List[dict]
61+
):
62+
response = client.get("/activities/")
63+
json_data = json.loads(response.data)
64+
65+
assert response.status_code == HTTPStatus.OK
66+
assert json_data == activities_json
67+
68+
69+
def test__activity_endpoint__returns_an_activity__when_activity_matches_its_id(
70+
client: FlaskClient, activities_json: typing.List[dict]
71+
):
72+
response = client.get("/activities/%s" % activities_json[0]['id'])
73+
json_data = json.loads(response.data)
74+
75+
assert response.status_code == HTTPStatus.OK
76+
assert json_data == activities_json[0]
77+
78+
79+
def test__activity_endpoint__returns_a_not_found_status__when_no_activity_matches_its_id(
80+
client: FlaskClient,
81+
):
82+
response = client.get("/activities/%s" % Faker().uuid4())
83+
json_data = json.loads(response.data)
84+
85+
assert response.status_code == HTTPStatus.NOT_FOUND
86+
assert json_data['message'] == 'Activity not found'
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
from V2.source.daos.activities_json_dao import ActivitiesJsonDao
2+
from V2.source.dtos.activity import Activity
3+
from faker import Faker
4+
import json
5+
import pytest
6+
import typing
7+
8+
9+
@pytest.fixture(name='create_fake_activities')
10+
def _create_fake_activities(mocker) -> typing.List[Activity]:
11+
def _creator(activities):
12+
read_data = json.dumps(activities)
13+
mocker.patch('builtins.open', mocker.mock_open(read_data=read_data))
14+
return [Activity(**activity) for activity in activities]
15+
16+
return _creator
17+
18+
19+
def test_get_by_id__returns_an_activity_dto__when_found_one_activity_that_matches_its_id(
20+
create_fake_activities,
21+
):
22+
activities_json_dao = ActivitiesJsonDao(Faker().file_path())
23+
activities = create_fake_activities(
24+
[
25+
{
26+
"name": "test_name",
27+
"description": "test_description",
28+
"tenant_id": "test_tenant_id",
29+
"id": "test_id",
30+
"deleted": "test_deleted",
31+
"status": "test_status",
32+
}
33+
]
34+
)
35+
activity_dto = activities.pop()
36+
37+
result = activities_json_dao.get_by_id(activity_dto.id)
38+
39+
assert result == activity_dto
40+
41+
42+
def test__get_by_id__returns_none__when_no_activity_matches_its_id(
43+
create_fake_activities,
44+
):
45+
activities_json_dao = ActivitiesJsonDao(Faker().file_path())
46+
create_fake_activities([])
47+
48+
result = activities_json_dao.get_by_id(Faker().uuid4())
49+
50+
assert result == None
51+
52+
53+
def test__get_all__returns_a_list_of_activity_dto_objects__when_one_or_more_activities_are_found(
54+
create_fake_activities,
55+
):
56+
activities_json_dao = ActivitiesJsonDao(Faker().file_path())
57+
number_of_activities = 3
58+
activities = create_fake_activities(
59+
[
60+
{
61+
"name": "test_name",
62+
"description": "test_description",
63+
"tenant_id": "test_tenant_id",
64+
"id": "test_id",
65+
"deleted": "test_deleted",
66+
"status": "test_status",
67+
}
68+
]
69+
* number_of_activities
70+
)
71+
72+
result = activities_json_dao.get_all()
73+
74+
assert result == activities
75+
76+
77+
def test_get_all__returns_an_empty_list__when_doesnt_found_any_activities(
78+
create_fake_activities,
79+
):
80+
activities_json_dao = ActivitiesJsonDao(Faker().file_path())
81+
activities = create_fake_activities([])
82+
83+
result = activities_json_dao.get_all()
84+
85+
assert result == activities
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
from V2.source.entry_points.flask_api.activities_endpoints import (
2+
Activities,
3+
Activity,
4+
)
5+
from V2.source import use_cases
6+
from V2.source.dtos.activity import Activity as ActivityDTO
7+
from pytest_mock import MockFixture
8+
from faker import Faker
9+
from werkzeug.exceptions import NotFound
10+
11+
fake = Faker()
12+
13+
valid_id = fake.uuid4()
14+
15+
fake_activity = {
16+
"name": fake.company(),
17+
"description": fake.paragraph(),
18+
"tenant_id": fake.uuid4(),
19+
"id": valid_id,
20+
"deleted": fake.date(),
21+
"status": fake.boolean(),
22+
}
23+
fake_activity_dto = ActivityDTO(**fake_activity)
24+
25+
26+
def test__activities_class__uses_the_get_activities_use_case__to_retrieve_activities(
27+
mocker: MockFixture,
28+
):
29+
mocker.patch.object(
30+
use_cases.GetActivitiesUseCase,
31+
'get_activities',
32+
return_value=[],
33+
)
34+
35+
activities_class_endpoint = Activities()
36+
activities = activities_class_endpoint.get()
37+
38+
assert use_cases.GetActivitiesUseCase.get_activities.called
39+
assert [] == activities
40+
41+
42+
def test__activity_class__uses_the_get_activity_by_id_use_case__to_retrieve__an_activity(
43+
mocker: MockFixture,
44+
):
45+
mocker.patch.object(
46+
use_cases.GetActivityUseCase,
47+
'get_activity_by_id',
48+
return_value=fake_activity_dto,
49+
)
50+
51+
activity_class_endpoint = Activity()
52+
activity = activity_class_endpoint.get(valid_id)
53+
54+
assert use_cases.GetActivityUseCase.get_activity_by_id.called
55+
assert fake_activity == activity
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
from V2.source.services.activity_service import ActivityService
2+
from faker import Faker
3+
4+
5+
def test__get_all__uses_the_activity_dao__to_retrieve_activities(mocker):
6+
expected_activities = mocker.Mock()
7+
activity_dao = mocker.Mock(
8+
get_all=mocker.Mock(return_value=expected_activities)
9+
)
10+
activity_service = ActivityService(activity_dao)
11+
12+
actual_activities = activity_service.get_all()
13+
14+
assert activity_dao.get_all.called
15+
assert expected_activities == actual_activities
16+
17+
18+
def test__get_by_id__uses_the_activity_dao__to_retrieve_one_activity(mocker):
19+
expected_activity = mocker.Mock()
20+
activity_dao = mocker.Mock(
21+
get_by_id=mocker.Mock(return_value=expected_activity)
22+
)
23+
activity_service = ActivityService(activity_dao)
24+
25+
actual_activity = activity_service.get_by_id(Faker().uuid4())
26+
27+
assert activity_dao.get_by_id.called
28+
assert expected_activity == actual_activity

0 commit comments

Comments
 (0)