Skip to content

Commit 8843766

Browse files
authored
Merge pull request #43 from ioet/feature/Persist-api-ns-for-time-entries#13
Implementation and test of api namespaces for time entries. Close #13
2 parents 8346198 + b4d11ac commit 8843766

17 files changed

+459
-136
lines changed

README.md

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ automatically [pip](https://pip.pypa.io/en/stable/) as well.
1313
- A virtual environment, namely [venv](https://docs.python.org/3/library/venv.html).
1414

1515
### Setup
16-
1716
- Create and activate the environment,
1817

1918
In Windows:
@@ -111,8 +110,9 @@ python3 -m pytest -v
111110
```
112111

113112
The database tests will be done in the table `tests` of the database specified by the variable `DATABASE_URI`. If this
114-
variable is not specified it will automatically connect to `sqlite:///tests.db`. This will do, because we are using SQL
115-
Alchemy to be able connect to any SQL database maintaining the same codebase.
113+
variable is not specified it will automatically connect to SQLite database in-memory. This will do, because we are using
114+
[SQL Alchemy](https://www.sqlalchemy.org/features.html) to be able connect to any SQL database maintaining the same
115+
codebase.
116116

117117

118118
The option `-v` shows which tests failed or succeeded. Have into account that you can also debug each test
@@ -132,10 +132,12 @@ To get a report table
132132
```
133133

134134
To get a full report in html
135+
135136
```bash
136137
coverage html
137138
```
138-
Then check in the [htmlcov/index.html](./htmlcov/index.html) to see it
139+
140+
Then check in the [htmlcov/index.html](./htmlcov/index.html) to see it.
139141

140142
If you want that previously collected coverage data is erased, you can execute:
141143

@@ -144,7 +146,6 @@ coverage erase
144146
```
145147

146148
### CLI
147-
148149
There are available commands, aware of the API, that can be very helpful to you. You
149150
can check them out by running
150151

@@ -160,7 +161,6 @@ python cli.py gen_swagger_json -f ~/Downloads/swagger.json
160161
```
161162

162163
## Run as docker container
163-
164164
1. Build image
165165
```bash
166166
docker build -t time_tracker_api:local .
@@ -178,8 +178,8 @@ docker run -p 5000:5000 time_tracker_api:local
178178
the win.
179179
- [Flask](http://flask.pocoo.org/) as the micro framework of choice.
180180
- [Flask RestPlus](https://flask-restplus.readthedocs.io/en/stable/) for building Restful APIs with Swagger.
181-
- [Pytest](https://docs.pytest.org/en/latest/index.html) for tests
182-
- [Coverage](https://coverage.readthedocs.io/en/coverage-4.5.4/) for coverage
181+
- [Pytest](https://docs.pytest.org/en/latest/index.html) for tests.
182+
- [Coverage](https://coverage.readthedocs.io/en/coverage-4.5.4/) for coverage.
183183
- [Swagger](https://swagger.io/) for documentation and standardization, taking into account the
184184
[API import restrictions and known issues](https://docs.microsoft.com/en-us/azure/api-management/api-management-api-import-restrictions)
185185
in Azure.

requirements/prod.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ Flask-Script==2.0.6
2323

2424
# SQL database (MS SQL)
2525
pyodbc==4.0.30
26+
SQLAlchemy==1.3.15
27+
SQLAlchemy-Utils==0.36.3
2628
flask_sqlalchemy==2.4.1
2729

2830
# Handling requests

tests/projects/projects_namespace_test.py

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from faker import Faker
22
from flask import json
33
from flask.testing import FlaskClient
4+
from flask_restplus._http import HTTPStatus
45
from pytest_mock import MockFixture
56

67
from time_tracker_api.projects.projects_model import PROJECT_TYPE
@@ -25,7 +26,7 @@ def test_create_project_should_succeed_with_valid_request(client: FlaskClient, m
2526

2627
response = client.post("/projects", json=valid_project_data, follow_redirects=True)
2728

28-
assert 201 == response.status_code
29+
assert HTTPStatus.CREATED == response.status_code
2930
repository_create_mock.assert_called_once_with(valid_project_data)
3031

3132

@@ -40,7 +41,7 @@ def test_create_project_should_reject_bad_request(client: FlaskClient, mocker: M
4041

4142
response = client.post("/projects", json=invalid_project_data, follow_redirects=True)
4243

43-
assert 400 == response.status_code
44+
assert HTTPStatus.BAD_REQUEST == response.status_code
4445
repository_create_mock.assert_not_called()
4546

4647

@@ -52,24 +53,21 @@ def test_list_all_projects(client: FlaskClient, mocker: MockFixture):
5253

5354
response = client.get("/projects", follow_redirects=True)
5455

55-
assert 200 == response.status_code
56-
json_data = json.loads(response.data)
57-
assert [] == json_data
56+
assert HTTPStatus.OK == response.status_code
57+
assert [] == json.loads(response.data)
5858
repository_find_all_mock.assert_called_once()
5959

6060

6161
def test_get_project_should_succeed_with_valid_id(client: FlaskClient, mocker: MockFixture):
6262
from time_tracker_api.projects.projects_namespace import project_dao
63-
6463
valid_id = fake.random_int(1, 9999)
65-
6664
repository_find_mock = mocker.patch.object(project_dao.repository,
6765
'find',
6866
return_value=fake_project)
6967

7068
response = client.get("/projects/%s" % valid_id, follow_redirects=True)
7169

72-
assert 200 == response.status_code
70+
assert HTTPStatus.OK == response.status_code
7371
fake_project == json.loads(response.data)
7472
repository_find_mock.assert_called_once_with(str(valid_id))
7573

@@ -86,11 +84,12 @@ def test_get_project_should_return_not_found_with_invalid_id(client: FlaskClient
8684

8785
response = client.get("/projects/%s" % invalid_id, follow_redirects=True)
8886

89-
assert 404 == response.status_code
87+
assert HTTPStatus.NOT_FOUND == response.status_code
9088
repository_find_mock.assert_called_once_with(str(invalid_id))
9189

9290

93-
def test_get_project_should_return_422_for_invalid_id_format(client: FlaskClient, mocker: MockFixture):
91+
def test_get_project_should_response_with_unprocessable_entity_for_invalid_id_format(client: FlaskClient,
92+
mocker: MockFixture):
9493
from time_tracker_api.projects.projects_namespace import project_dao
9594
from werkzeug.exceptions import UnprocessableEntity
9695

@@ -102,7 +101,7 @@ def test_get_project_should_return_422_for_invalid_id_format(client: FlaskClient
102101

103102
response = client.get("/projects/%s" % invalid_id, follow_redirects=True)
104103

105-
assert 422 == response.status_code
104+
assert HTTPStatus.UNPROCESSABLE_ENTITY == response.status_code
106105
repository_find_mock.assert_called_once_with(str(invalid_id))
107106

108107

@@ -116,7 +115,7 @@ def test_update_project_should_succeed_with_valid_data(client: FlaskClient, mock
116115
valid_id = fake.random_int(1, 9999)
117116
response = client.put("/projects/%s" % valid_id, json=valid_project_data, follow_redirects=True)
118117

119-
assert 200 == response.status_code
118+
assert HTTPStatus.OK == response.status_code
120119
fake_project == json.loads(response.data)
121120
repository_update_mock.assert_called_once_with(str(valid_id), valid_project_data)
122121

@@ -133,7 +132,7 @@ def test_update_project_should_reject_bad_request(client: FlaskClient, mocker: M
133132
valid_id = fake.random_int(1, 9999)
134133
response = client.put("/projects/%s" % valid_id, json=invalid_project_data, follow_redirects=True)
135134

136-
assert 400 == response.status_code
135+
assert HTTPStatus.BAD_REQUEST == response.status_code
137136
repository_update_mock.assert_not_called()
138137

139138

@@ -151,7 +150,7 @@ def test_update_project_should_return_not_found_with_invalid_id(client: FlaskCli
151150
json=valid_project_data,
152151
follow_redirects=True)
153152

154-
assert 404 == response.status_code
153+
assert HTTPStatus.NOT_FOUND == response.status_code
155154
repository_update_mock.assert_called_once_with(str(invalid_id), valid_project_data)
156155

157156

@@ -166,7 +165,7 @@ def test_delete_project_should_succeed_with_valid_id(client: FlaskClient, mocker
166165

167166
response = client.delete("/projects/%s" % valid_id, follow_redirects=True)
168167

169-
assert 204 == response.status_code
168+
assert HTTPStatus.NO_CONTENT == response.status_code
170169
assert b'' == response.data
171170
repository_remove_mock.assert_called_once_with(str(valid_id))
172171

@@ -183,11 +182,12 @@ def test_delete_project_should_return_not_found_with_invalid_id(client: FlaskCli
183182

184183
response = client.delete("/projects/%s" % invalid_id, follow_redirects=True)
185184

186-
assert 404 == response.status_code
185+
assert HTTPStatus.NOT_FOUND == response.status_code
187186
repository_remove_mock.assert_called_once_with(str(invalid_id))
188187

189188

190-
def test_delete_project_should_return_422_for_invalid_id_format(client: FlaskClient, mocker: MockFixture):
189+
def test_delete_project_should_return_unprocessable_entity_for_invalid_id_format(client: FlaskClient,
190+
mocker: MockFixture):
191191
from time_tracker_api.projects.projects_namespace import project_dao
192192
from werkzeug.exceptions import UnprocessableEntity
193193

@@ -199,5 +199,5 @@ def test_delete_project_should_return_422_for_invalid_id_format(client: FlaskCli
199199

200200
response = client.delete("/projects/%s" % invalid_id, follow_redirects=True)
201201

202-
assert 422 == response.status_code
202+
assert HTTPStatus.UNPROCESSABLE_ENTITY == response.status_code
203203
repository_remove_mock.assert_called_once_with(str(invalid_id))

tests/resources.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33

44
class PersonSQLModel(db.Model, AuditedSQLModel):
5-
__tablename__ = 'tests'
5+
__tablename__ = 'test'
66
id = db.Column(db.Integer, primary_key=True)
77
name = db.Column(db.String(80), unique=False, nullable=False)
88
email = db.Column(db.String(120), unique=True, nullable=False)

tests/smoke_test.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,25 @@
1+
import pyodbc
2+
3+
import pytest
4+
from flask.testing import FlaskClient
5+
from flask_restplus._http import HTTPStatus
6+
from pytest_mock import MockFixture
7+
8+
19
def test_app_exists(app):
210
assert app is not None
11+
12+
13+
unexpected_errors_to_be_handled = [pyodbc.OperationalError]
14+
15+
16+
@pytest.mark.parametrize("error_type", unexpected_errors_to_be_handled)
17+
def test_exceptions_are_handled(error_type, client: FlaskClient, mocker: MockFixture):
18+
from time_tracker_api.time_entries.time_entries_namespace import time_entries_dao
19+
mocker.patch.object(time_entries_dao,
20+
"get_all",
21+
side_effect=error_type)
22+
23+
response = client.get('/time-entries', follow_redirects=True)
24+
25+
assert HTTPStatus.INTERNAL_SERVER_ERROR != response.status_code

tests/time_entries/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)