diff --git a/.env.template b/.env.template new file mode 100644 index 00000000..3fda8aff --- /dev/null +++ b/.env.template @@ -0,0 +1,6 @@ +# Package where the app is located +export FLASK_APP=time_tracker_api + +# The database connection URI. Check out the README.md for more details +DATABASE_URI=mssql+pyodbc://:@time-tracker-srv.database.windows.net/?driver\=ODBC Driver 17 for SQL Server + diff --git a/.gitignore b/.gitignore index 23ff50ef..e5f5178c 100644 --- a/.gitignore +++ b/.gitignore @@ -27,3 +27,6 @@ htmlcov/ .env timetracker-api-postman-collection.json swagger.json + +# Ignore any SQLite generated database +*.db diff --git a/README.md b/README.md index 0f999373..c012b5f0 100644 --- a/README.md +++ b/README.md @@ -87,15 +87,34 @@ a link to the swagger.json with the definition of the api. ## Development ### Test -We are using Pytest](https://docs.pytest.org/en/latest/index.html) for tests. The tests are located in the package +We are using [Pytest](https://docs.pytest.org/en/latest/index.html) for tests. The tests are located in the package `tests` and use the [conventions for python test discovery](https://docs.pytest.org/en/latest/goodpractices.html#test-discovery). -To run the tests just execute: +#### Integration tests +The [integrations tests](https://en.wikipedia.org/wiki/Integration_testing) verifies that all the components of the app +are working well together. These are the default tests we should run: -```bash +```dotenv +python3 -m pytest -v --ignore=tests/sql_repository_test.py +``` + +As you may have noticed we are ignoring the tests related with the repository. + + +#### System tests +In addition to the integration testing we might include tests to the data access layer in order to verify that the +persisted data is being managed the right way, i.e. it actually works. We may classify the execution of all the existing +tests as [system testing](https://en.wikipedia.org/wiki/System_testing): + +```dotenv python3 -m pytest -v ``` +The database tests will be done in the table `tests` of the database specified by the variable `DATABASE_URI`. If this +variable is not specified it will automatically connect to `sqlite:///tests.db`. This will do, because we are using SQL +Alchemy to be able connect to any SQL database maintaining the same codebase. + + The option `-v` shows which tests failed or succeeded. Have into account that you can also debug each test (test_* files) with the help of an IDE like PyCharm. diff --git a/run.py b/run.py index bec4799b..a99707a3 100644 --- a/run.py +++ b/run.py @@ -4,5 +4,5 @@ """ from time_tracker_api import create_app -app = create_app() -print("TimeTracker API server was created") +app = create_app('time_tracker_api.config.ProductionConfig') +print("TimeTracker API server created!") diff --git a/tests/conftest.py b/tests/conftest.py index 49bf0892..679c7ee8 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -5,12 +5,10 @@ from time_tracker_api import create_app -CONFIGURATIONS = ['AzureSQLDatabaseDevelopTestConfig'] - -@pytest.fixture(scope='session', params=CONFIGURATIONS) +@pytest.fixture(scope='session') def app(request: FixtureRequest) -> Flask: - return create_app("time_tracker_api.config.%s" % request.param) + return create_app("time_tracker_api.config.TestConfig") @pytest.fixture diff --git a/tests/projects/projects_namespace_test.py b/tests/projects/projects_namespace_test.py index 8ca3533d..283d85c4 100644 --- a/tests/projects/projects_namespace_test.py +++ b/tests/projects/projects_namespace_test.py @@ -106,7 +106,7 @@ def test_get_project_should_return_422_for_invalid_id_format(client: FlaskClient repository_find_mock.assert_called_once_with(str(invalid_id)) -def update_project_should_succeed_with_valid_data(client: FlaskClient, mocker: MockFixture): +def test_update_project_should_succeed_with_valid_data(client: FlaskClient, mocker: MockFixture): from time_tracker_api.projects.projects_namespace import project_dao repository_update_mock = mocker.patch.object(project_dao.repository, @@ -118,7 +118,7 @@ def update_project_should_succeed_with_valid_data(client: FlaskClient, mocker: M assert 200 == response.status_code fake_project == json.loads(response.data) - repository_update_mock.assert_called_once_with(valid_id, valid_project_data) + repository_update_mock.assert_called_once_with(str(valid_id), valid_project_data) def test_update_project_should_reject_bad_request(client: FlaskClient, mocker: MockFixture): @@ -147,7 +147,9 @@ def test_update_project_should_return_not_found_with_invalid_id(client: FlaskCli 'update', side_effect=NotFound) - response = client.put("/projects/%s" % invalid_id, json=valid_project_data, follow_redirects=True) + response = client.put("/projects/%s" % invalid_id, + json=valid_project_data, + follow_redirects=True) assert 404 == response.status_code repository_update_mock.assert_called_once_with(str(invalid_id), valid_project_data) @@ -192,8 +194,8 @@ def test_delete_project_should_return_422_for_invalid_id_format(client: FlaskCli invalid_id = fake.company() repository_remove_mock = mocker.patch.object(project_dao.repository, - 'remove', - side_effect=UnprocessableEntity) + 'remove', + side_effect=UnprocessableEntity) response = client.delete("/projects/%s" % invalid_id, follow_redirects=True) diff --git a/time_tracker_api/__init__.py b/time_tracker_api/__init__.py index 8a7da1ee..cca09e77 100644 --- a/time_tracker_api/__init__.py +++ b/time_tracker_api/__init__.py @@ -46,7 +46,6 @@ def init_app(app: Flask): add_debug_toolbar(app) - def add_debug_toolbar(app): app.config['DEBUG_TB_PANELS'] = ( 'flask_debugtoolbar.panels.versions.VersionDebugPanel', diff --git a/time_tracker_api/config.py b/time_tracker_api/config.py index 79ac9990..0dd96511 100644 --- a/time_tracker_api/config.py +++ b/time_tracker_api/config.py @@ -10,37 +10,44 @@ class Config: RESTPLUS_VALIDATE = True -class DevelopConfig(Config): +class DevelopmentConfig(Config): DEBUG = True FLASK_DEBUG = True - FLASK_ENV = "develop" + FLASK_ENV = "development" -class TestConfig(Config): +class SQLConfig(Config): + SQLALCHEMY_DATABASE_URI = Config.DATABASE_URI + SQLALCHEMY_COMMIT_ON_TEARDOWN = True + SQLALCHEMY_TRACK_MODIFICATIONS = False + + +class TestConfig(SQLConfig): TESTING = True FLASK_DEBUG = True TEST_TABLE = 'tests' + DATABASE_URI = os.environ.get('DATABASE_URI', 'sqlite:///tests.db') + SQLALCHEMY_DATABASE_URI = DATABASE_URI -class SQLConfig(Config): - SQLALCHEMY_DATABASE_URI = Config.DATABASE_URI - SQLALCHEMY_COMMIT_ON_TEARDOWN = True - SQLALCHEMY_TRACK_MODIFICATIONS = False +class ProductionConfig(Config): + FLASK_ENV = 'production' class AzureConfig(SQLConfig): pass -class AzureSQLDatabaseDevelopConfig(DevelopConfig, AzureConfig): +class AzureDevelopmentConfig(DevelopmentConfig, AzureConfig): pass -class AzureSQLDatabaseDevelopTestConfig(TestConfig, AzureSQLDatabaseDevelopConfig): +class AzureProductionConfig(ProductionConfig, AzureConfig): pass -DefaultConfig = AzureSQLDatabaseDevelopConfig +DefaultConfig = AzureDevelopmentConfig +ProductionConfig = AzureProductionConfig class CLIConfig(DefaultConfig): diff --git a/time_tracker_api/database.py b/time_tracker_api/database.py index 845735fe..f71d6354 100644 --- a/time_tracker_api/database.py +++ b/time_tracker_api/database.py @@ -6,8 +6,6 @@ To know more about protocols and subtyping check out PEP-0544 """ import abc -import enum -from datetime import datetime from flask import Flask @@ -75,4 +73,3 @@ def init_app(app: Flask) -> None: init_app(app) global seeder seeder = SQLSeeder() -