Skip to content

Commit d5a4e5c

Browse files
authored
Merge pull request #35 from ioet/feature/Create-repository-sql-database#25
Creates repository sql database#25
2 parents fcc0e3c + 2ed13c9 commit d5a4e5c

25 files changed

+597
-209
lines changed

README.md

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,35 @@ automatically [pip](https://pip.pypa.io/en/stable/) as well.
3636
3737
The `stage` can be `dev` or `prod`.
3838
Remember to do it with Python 3.
39+
40+
41+
- Install the [Microsoft ODBC Driver for SQL Server](https://docs.microsoft.com/en-us/sql/connect/odbc/microsoft-odbc-driver-for-sql-server?view=sql-server-ver15)
42+
in your operative system. Then you have to check out what is the name of the SQL Driver installation.
43+
Check it out with:
44+
45+
```bash
46+
vim /usr/local/etc/odbcinst.ini
47+
```
48+
49+
It may display something like
50+
51+
```.ini
52+
[ODBC Driver 17 for SQL Server]
53+
Description=Microsoft ODBC Driver 17 for SQL Server
54+
Driver=/usr/local/lib/libmsodbcsql.17.dylib
55+
UsageCount=2
56+
```
57+
58+
Then specify the driver name, in this case _DBC Driver 17 for SQL Server_ in the `DATABASE_URI`, e.g.:
59+
60+
```.dotenv
61+
DATABASE_URI=mssql+pyodbc://<user>:<password>@time-tracker-srv.database.windows.net/<database>?driver\=ODBC Driver 17 for SQL Server
62+
```
63+
64+
To troubleshoot issues regarding this part please check out:
65+
- [Install the Microsoft ODBC driver for SQL Server (macOS)](https://docs.microsoft.com/en-us/sql/connect/odbc/linux-mac/install-microsoft-odbc-driver-sql-server-macos?view=sql-server-ver15).
66+
- Github issue [odbcinst: SQLRemoveDriver failed with Unable to find component name](https://github.com/Microsoft/homebrew-mssql-preview/issues/2).
67+
- Stack overflow solution to [Can't open lib 'ODBC Driver 13 for SQL Server'? Sym linking issue?](https://stackoverflow.com/questions/44527452/cant-open-lib-odbc-driver-13-for-sql-server-sym-linking-issue).
3968

4069
### How to use it
4170
- Set the env var `FLASK_APP` to `time_tracker_api` and start the app:
@@ -136,7 +165,6 @@ the win.
136165
[API import restrictions and known issues](https://docs.microsoft.com/en-us/azure/api-management/api-management-api-import-restrictions)
137166
in Azure.
138167

139-
140168
## License
141169

142170
Copyright 2020 ioet Inc. All Rights Reserved.

cli.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,11 @@
66
from flask_script import Manager
77

88
from time_tracker_api import create_app
9+
10+
app = create_app('time_tracker_api.config.CLIConfig')
11+
912
from time_tracker_api.api import api
1013

11-
app = create_app()
1214
cli_manager = Manager(app)
1315

1416

@@ -44,6 +46,23 @@ def gen_postman_collection(filename='timetracker-api-postman-collection.json',
4446
save_data(parsed_json, filename)
4547

4648

49+
@cli_manager.command
50+
def seed():
51+
from time_tracker_api.database import seeder as seed
52+
seed()
53+
54+
55+
@cli_manager.command
56+
def re_create_db():
57+
print('This is going to drop all tables and seed again the database')
58+
confirm_answer = input('Do you confirm (Y) you want to remove all your data?\n')
59+
if confirm_answer.upper() == 'Y':
60+
from time_tracker_api.database import seeder
61+
seeder.fresh()
62+
else:
63+
print('\nThis action was cancelled!')
64+
65+
4766
def save_data(data: str, filename: str) -> None:
4867
""" Save text content to a file """
4968
if filename:

requirements/dev.txt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,7 @@ pytest-mock==2.0.0
1212
Faker==4.0.2
1313

1414
# Coverage
15-
coverage==4.5.1
15+
coverage==4.5.1
16+
17+
# The Debug Toolbar
18+
Flask-DebugToolbar==0.11.0

requirements/prod.txt

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,16 @@ Jinja2==2.11.1
1313
gunicorn==20.0.4
1414

1515
#Swagger support for Restful API
16-
flask-restplus==0.13.0
16+
flask-restplus==0.12.1
1717

1818
#Mocking
1919
Faker==4.0.2
2020

2121
#CLI support
22-
Flask-Script==2.0.6
22+
Flask-Script==2.0.6
23+
24+
# SQL database (MS SQL)
25+
flask_sqlalchemy==2.4.1
26+
27+
# Handling requests
28+
requests==2.23.0

tests/conftest.py

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,34 @@
11
import pytest
2+
from _pytest.fixtures import FixtureRequest
23
from flask import Flask
34
from flask.testing import FlaskClient
45

56
from time_tracker_api import create_app
67

8+
CONFIGURATIONS = ['AzureSQLDatabaseDevelopTestConfig']
79

8-
@pytest.fixture(scope='session')
9-
def app() -> Flask:
10-
"""An instance of the app for tests"""
11-
return create_app()
10+
11+
@pytest.fixture(scope='session', params=CONFIGURATIONS)
12+
def app(request: FixtureRequest) -> Flask:
13+
return create_app("time_tracker_api.config.%s" % request.param)
1214

1315

1416
@pytest.fixture
1517
def client(app: Flask) -> FlaskClient:
16-
"""A test client for the app."""
1718
with app.test_client() as c:
1819
return c
20+
21+
22+
@pytest.fixture(scope="module")
23+
def sql_repository():
24+
from .resources import PersonSQLModel
25+
from time_tracker_api.database import seeder
26+
from time_tracker_api.sql_repository import db
27+
28+
seeder.fresh()
29+
30+
from time_tracker_api.sql_repository import SQLRepository
31+
yield SQLRepository(PersonSQLModel)
32+
33+
db.drop_all()
34+
print("Models for test removed!")
Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
11
from flask import json
2+
from flask.testing import FlaskClient
3+
from pytest_mock import MockFixture
24

35

4-
def test_list_should_return_nothing(client):
5-
"""Should return an empty array"""
6+
def test_list_all_elements(client: FlaskClient, mocker: MockFixture):
7+
from time_tracker_api.projects.projects_namespace import project_dao
8+
repository_find_all_mock = mocker.patch.object(project_dao.repository, 'find_all', return_value=[])
9+
610
response = client.get("/projects", follow_redirects=True)
711

812
assert 200 == response.status_code
913

1014
json_data = json.loads(response.data)
1115
assert [] == json_data
16+
repository_find_all_mock.assert_called_once()

tests/resources.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
from time_tracker_api.sql_repository import db, AuditedSQLModel
2+
3+
4+
class PersonSQLModel(db.Model, AuditedSQLModel):
5+
__tablename__ = 'tests'
6+
id = db.Column(db.Integer, primary_key=True)
7+
name = db.Column(db.String(80), unique=False, nullable=False)
8+
email = db.Column(db.String(120), unique=True, nullable=False)
9+
age = db.Column(db.Integer, nullable=False)
10+
11+
def __repr__(self):
12+
return '<Test Model %r>' % self.name

tests/smoke_test.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,2 @@
1-
21
def test_app_exists(app):
3-
"""Does app exists"""
42
assert app is not None

tests/sql_repository_test.py

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
from faker import Faker
2+
3+
fake = Faker()
4+
5+
existing_elements_registry = []
6+
last_deleted_element = None
7+
8+
9+
def test_create(sql_repository):
10+
global sample_element
11+
sample_element = dict(name=fake.name(),
12+
email=fake.safe_email(),
13+
age=fake.pyint(min_value=10, max_value=80))
14+
15+
result = sql_repository.create(sample_element)
16+
17+
assert result is not None
18+
assert result.id is not None
19+
assert result.created_at is not None
20+
assert result.created_by is not None
21+
assert result.updated_at is None
22+
assert result.updated_by is None
23+
24+
existing_elements_registry.append(result)
25+
26+
27+
def test_find(sql_repository):
28+
existing_element = existing_elements_registry[0]
29+
30+
found_element = sql_repository.find(existing_element.id)
31+
32+
assert found_element is not None
33+
assert found_element.id is not None
34+
35+
36+
def test_update(sql_repository):
37+
existing_element = existing_elements_registry[0]
38+
39+
updated_element = sql_repository.update(existing_element.id,
40+
dict(name="Jon Snow", age=34))
41+
42+
assert updated_element is not None
43+
assert updated_element.id == existing_element.id
44+
assert updated_element.name == "Jon Snow"
45+
assert updated_element.age == 34
46+
assert updated_element.updated_at is not None
47+
assert updated_element.updated_at > updated_element.created_at
48+
assert updated_element.updated_by is not None
49+
50+
51+
def test_find_all(sql_repository):
52+
existing_elements = sql_repository.find_all()
53+
54+
assert all(e in existing_elements_registry for e in existing_elements)
55+
56+
57+
def test_find_all_that_contains_property_with_string_case_insensitive(sql_repository):
58+
fake_name = fake.name()
59+
new_element = dict(name="%s Snow" % fake_name,
60+
email=fake.safe_email(),
61+
age=fake.pyint(min_value=10, max_value=80))
62+
sql_repository.create(new_element)
63+
existing_elements_registry.append(new_element)
64+
65+
search_snow_result = sql_repository.find_all_contain_str('name', 'Snow')
66+
assert len(search_snow_result) == 2
67+
68+
search_jon_result = sql_repository.find_all_contain_str('name', 'Jon')
69+
assert len(search_jon_result) == 1
70+
71+
search_ram_result = sql_repository.find_all_contain_str('name', fake_name)
72+
assert search_ram_result[0].name == new_element['name']
73+
74+
75+
def test_delete_existing_element(sql_repository):
76+
existing_element = existing_elements_registry[0]
77+
78+
result = sql_repository.remove(existing_element.id)
79+
80+
assert result is None
81+
82+
global last_deleted_model
83+
last_deleted_model = existing_elements_registry.pop()

tests/time_entries/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)