-
Notifications
You must be signed in to change notification settings - Fork 0
Persist and test API namespace for projects #40
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| # 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://<user>:<password>@time-tracker-srv.database.windows.net/<database>?driver\=ODBC Driver 17 for SQL Server |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -27,3 +27,6 @@ htmlcov/ | |
| .env | ||
| timetracker-api-postman-collection.json | ||
| swagger.json | ||
|
|
||
| # Ignore any SQLite generated database | ||
| *.db | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,16 +1,203 @@ | ||
| from faker import Faker | ||
| from flask import json | ||
| from flask.testing import FlaskClient | ||
| from pytest_mock import MockFixture | ||
|
|
||
| from time_tracker_api.projects.projects_model import PROJECT_TYPE | ||
|
|
||
| def test_list_all_elements(client: FlaskClient, mocker: MockFixture): | ||
| fake = Faker() | ||
|
|
||
| valid_project_data = { | ||
| "name": fake.company(), | ||
| "description": fake.paragraph(), | ||
| "type": fake.word(PROJECT_TYPE.valid_type_values()), | ||
| } | ||
| fake_project = ({ | ||
| "id": fake.random_int(1, 9999) | ||
| }).update(valid_project_data) | ||
|
|
||
|
|
||
| def test_create_project_should_succeed_with_valid_request(client: FlaskClient, mocker: MockFixture): | ||
| from time_tracker_api.projects.projects_namespace import project_dao | ||
| repository_create_mock = mocker.patch.object(project_dao.repository, | ||
| 'create', | ||
| return_value=fake_project) | ||
|
|
||
| response = client.post("/projects", json=valid_project_data, follow_redirects=True) | ||
|
|
||
| assert 201 == response.status_code | ||
| repository_create_mock.assert_called_once_with(valid_project_data) | ||
|
|
||
|
|
||
| def test_create_project_should_reject_bad_request(client: FlaskClient, mocker: MockFixture): | ||
| from time_tracker_api.projects.projects_namespace import project_dao | ||
| repository_find_all_mock = mocker.patch.object(project_dao.repository, 'find_all', return_value=[]) | ||
| invalid_project_data = valid_project_data.copy().update({ | ||
| "type": 'anything', | ||
| }) | ||
| repository_create_mock = mocker.patch.object(project_dao.repository, | ||
| 'create', | ||
| return_value=fake_project) | ||
|
|
||
| response = client.post("/projects", json=invalid_project_data, follow_redirects=True) | ||
|
|
||
| assert 400 == response.status_code | ||
| repository_create_mock.assert_not_called() | ||
|
|
||
|
|
||
| def test_list_all_projects(client: FlaskClient, mocker: MockFixture): | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What about
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The description in the comments says list. As long as it makes sense and it is not ambiguous we are Ok. |
||
| from time_tracker_api.projects.projects_namespace import project_dao | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Notice, you are calling
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, if I do it before the project_dao would be broken because it has to be constructed after the app instance is ready. This is a necessary evil. |
||
| repository_find_all_mock = mocker.patch.object(project_dao.repository, | ||
| 'find_all', | ||
| return_value=[]) | ||
|
|
||
| response = client.get("/projects", follow_redirects=True) | ||
|
|
||
| assert 200 == response.status_code | ||
|
|
||
| json_data = json.loads(response.data) | ||
| assert [] == json_data | ||
| repository_find_all_mock.assert_called_once() | ||
|
|
||
|
|
||
| def test_get_project_should_succeed_with_valid_id(client: FlaskClient, mocker: MockFixture): | ||
| from time_tracker_api.projects.projects_namespace import project_dao | ||
|
|
||
| valid_id = fake.random_int(1, 9999) | ||
|
|
||
| repository_find_mock = mocker.patch.object(project_dao.repository, | ||
| 'find', | ||
| return_value=fake_project) | ||
|
|
||
| response = client.get("/projects/%s" % valid_id, follow_redirects=True) | ||
|
|
||
| assert 200 == response.status_code | ||
| fake_project == json.loads(response.data) | ||
| repository_find_mock.assert_called_once_with(str(valid_id)) | ||
|
|
||
|
|
||
| def test_get_project_should_return_not_found_with_invalid_id(client: FlaskClient, mocker: MockFixture): | ||
| from time_tracker_api.projects.projects_namespace import project_dao | ||
| from werkzeug.exceptions import NotFound | ||
|
|
||
| invalid_id = fake.random_int(1, 9999) | ||
|
|
||
| repository_find_mock = mocker.patch.object(project_dao.repository, | ||
| 'find', | ||
| side_effect=NotFound) | ||
|
|
||
| response = client.get("/projects/%s" % invalid_id, follow_redirects=True) | ||
|
|
||
| assert 404 == response.status_code | ||
| repository_find_mock.assert_called_once_with(str(invalid_id)) | ||
|
|
||
|
|
||
| def test_get_project_should_return_422_for_invalid_id_format(client: FlaskClient, mocker: MockFixture): | ||
| from time_tracker_api.projects.projects_namespace import project_dao | ||
| from werkzeug.exceptions import UnprocessableEntity | ||
|
|
||
| invalid_id = fake.company() | ||
|
|
||
| repository_find_mock = mocker.patch.object(project_dao.repository, | ||
| 'find', | ||
| side_effect=UnprocessableEntity) | ||
|
|
||
| response = client.get("/projects/%s" % invalid_id, follow_redirects=True) | ||
|
|
||
| assert 422 == response.status_code | ||
| repository_find_mock.assert_called_once_with(str(invalid_id)) | ||
|
|
||
|
|
||
| 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, | ||
| 'update', | ||
| return_value=fake_project) | ||
|
|
||
| valid_id = fake.random_int(1, 9999) | ||
| response = client.put("/projects/%s" % valid_id, json=valid_project_data, follow_redirects=True) | ||
|
|
||
| assert 200 == response.status_code | ||
| fake_project == json.loads(response.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): | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we can use the name
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Bad request is better |
||
| from time_tracker_api.projects.projects_namespace import project_dao | ||
| invalid_project_data = valid_project_data.copy().update({ | ||
| "type": 'anything', | ||
| }) | ||
| repository_update_mock = mocker.patch.object(project_dao.repository, | ||
| 'update', | ||
| return_value=fake_project) | ||
|
|
||
| valid_id = fake.random_int(1, 9999) | ||
| response = client.put("/projects/%s" % valid_id, json=invalid_project_data, follow_redirects=True) | ||
|
|
||
| assert 400 == response.status_code | ||
| repository_update_mock.assert_not_called() | ||
|
|
||
|
|
||
| def test_update_project_should_return_not_found_with_invalid_id(client: FlaskClient, mocker: MockFixture): | ||
| from time_tracker_api.projects.projects_namespace import project_dao | ||
| from werkzeug.exceptions import NotFound | ||
|
|
||
| invalid_id = fake.random_int(1, 9999) | ||
|
|
||
| repository_update_mock = mocker.patch.object(project_dao.repository, | ||
| 'update', | ||
| side_effect=NotFound) | ||
|
|
||
| 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) | ||
|
|
||
|
|
||
| def test_delete_project_should_succeed_with_valid_id(client: FlaskClient, mocker: MockFixture): | ||
| from time_tracker_api.projects.projects_namespace import project_dao | ||
|
|
||
| valid_id = fake.random_int(1, 9999) | ||
|
|
||
| repository_remove_mock = mocker.patch.object(project_dao.repository, | ||
| 'remove', | ||
| return_value=None) | ||
|
|
||
| response = client.delete("/projects/%s" % valid_id, follow_redirects=True) | ||
|
|
||
| assert 204 == response.status_code | ||
| assert b'' == response.data | ||
| repository_remove_mock.assert_called_once_with(str(valid_id)) | ||
|
|
||
|
|
||
| def test_delete_project_should_return_not_found_with_invalid_id(client: FlaskClient, mocker: MockFixture): | ||
| from time_tracker_api.projects.projects_namespace import project_dao | ||
| from werkzeug.exceptions import NotFound | ||
|
|
||
| invalid_id = fake.random_int(1, 9999) | ||
|
|
||
| repository_remove_mock = mocker.patch.object(project_dao.repository, | ||
| 'remove', | ||
| side_effect=NotFound) | ||
|
|
||
| response = client.delete("/projects/%s" % invalid_id, follow_redirects=True) | ||
|
|
||
| assert 404 == response.status_code | ||
| repository_remove_mock.assert_called_once_with(str(invalid_id)) | ||
|
|
||
|
|
||
| def test_delete_project_should_return_422_for_invalid_id_format(client: FlaskClient, mocker: MockFixture): | ||
| from time_tracker_api.projects.projects_namespace import project_dao | ||
| from werkzeug.exceptions import UnprocessableEntity | ||
|
|
||
| invalid_id = fake.company() | ||
|
|
||
| repository_remove_mock = mocker.patch.object(project_dao.repository, | ||
| 'remove', | ||
| side_effect=UnprocessableEntity) | ||
|
|
||
| response = client.delete("/projects/%s" % invalid_id, follow_redirects=True) | ||
|
|
||
| assert 422 == response.status_code | ||
| repository_remove_mock.assert_called_once_with(str(invalid_id)) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,4 @@ | ||
| import logging | ||
| import os | ||
|
|
||
| from flask import Flask | ||
|
|
@@ -34,13 +35,14 @@ def init_app_config(app: Flask, config_path: str, config_data: dict = None): | |
|
|
||
|
|
||
| def init_app(app: Flask): | ||
| from .database import init_app as init_database | ||
| from time_tracker_api.database import init_app as init_database | ||
| init_database(app) | ||
|
|
||
| from .api import api | ||
| from time_tracker_api.api import api | ||
| api.init_app(app) | ||
|
|
||
| if app.config.get('DEBUG'): | ||
| app.logger.setLevel(logging.INFO) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. should not be
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. IMHO |
||
| add_debug_toolbar(app) | ||
|
|
||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,39 +7,47 @@ class Config: | |
| SECRET_KEY = generate_dev_secret_key() | ||
| DATABASE_URI = os.environ.get('DATABASE_URI') | ||
| PROPAGATE_EXCEPTIONS = True | ||
| RESTPLUS_VALIDATE = True | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good. |
||
|
|
||
|
|
||
| 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): | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we can use the name
test_create_project_should_fail_with_invalid_requestfor consistency with the previous request.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would rather to leave bad request because that is the verbose name of the HTTP status code
400.