diff --git a/.coveragerc b/.coveragerc index 6a83082..aed15a8 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1,4 +1,5 @@ [run] omit = - *tests/* + *conftest.py + *tests* *.html diff --git a/.editorconfig b/.editorconfig index 5b0f668..a0a5379 100644 --- a/.editorconfig +++ b/.editorconfig @@ -14,12 +14,8 @@ charset = utf-8 [*.py] max_line_length = 119 -# Use 2 spaces for the HTML files -[*{.css,.js,.html,.tpl}] -indent_size = 2 - -# The JSON files contain newlines inconsistently -[*.json] +# Use 2 spaces for most of the assets +[*{.css,.js,.json,.html,.tpl,.yaml}] indent_size = 2 # Makefiles always use tabs for indentation diff --git a/Makefile b/Makefile index 0a1d17e..2d4315d 100644 --- a/Makefile +++ b/Makefile @@ -19,6 +19,7 @@ help: @echo "migrate-down .............................. Run the database migration (downgrade)" @echo "migrate-up................................. Run the database migration (upgrade)" @echo "test ...................................... Run frontend and backend tests" + @echo "test-it ................................... Run integration tests" @echo "test-javascript ........................... Run the frontend tests" @echo "test-python ............................... Run the backend tests" @echo @@ -95,3 +96,5 @@ test-javascript: test-python: pipenv run test +test-it: + pipenv run test_integration diff --git a/Pipfile b/Pipfile index 5ae7f54..b385155 100644 --- a/Pipfile +++ b/Pipfile @@ -17,6 +17,7 @@ pytest-flask = "*" pytest-cov = "==2.7.1" python-coveralls = "==2.9.2" pytest-mock = "*" +requests = "*" [packages] gunicorn = "*" @@ -42,3 +43,4 @@ run = "flask run --host=0.0.0.0 --port=8080" security = "bandit -r ." test = "pytest ." test_coverage = "pytest --cov=tomaco --cov-report term-missing tomaco" +test_integration = "pytest . -m 'integration'" diff --git a/Pipfile.lock b/Pipfile.lock index 3b1b905..2c18da4 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "02aa5e9ff2f90059867b662ba88d2b4861e13e8b641bbe354289b75feb6318a3" + "sha256": "df5145e0c05dd51fbe85a26abaa57233f0976422d90ba18c4f45f99046b7d6bf" }, "pipfile-spec": 6, "requires": { @@ -18,16 +18,16 @@ "default": { "alembic": { "hashes": [ - "sha256:cdb7d98bd5cbf65acd38d70b1c05573c432e6473a82f955cdea541b5c153b0cc" + "sha256:9f907d7e8b286a1cfb22db9084f9ce4fde7ad7956bb496dc7c952e10ac90e36a" ], - "version": "==1.0.11" + "version": "==1.2.1" }, "certifi": { "hashes": [ - "sha256:046832c04d4e752f37383b628bc601a7ea7211496b4638f6514d0e5b9acc4939", - "sha256:945e3ba63a0b9f577b1395204e13c3a231f9bc0223888be653286534e5873695" + "sha256:e4f3620cfea4f83eedc95b24abd9cd56f3c4b146dd0177e83a21b4eb49e21e50", + "sha256:fd7c7c74727ddcf00e9acd26bba8da604ffec95bf1c2144e67aff7a8b50e6cef" ], - "version": "==2019.6.16" + "version": "==2019.9.11" }, "chardet": { "hashes": [ @@ -61,11 +61,11 @@ }, "flask-sqlalchemy": { "hashes": [ - "sha256:0c9609b0d72871c540a7945ea559c8fdf5455192d2db67219509aed680a3d45a", - "sha256:8631bbea987bc3eb0f72b1f691d47bd37ceb795e73b59ab48586d76d75a7c605" + "sha256:0078d8663330dc05a74bc72b3b6ddc441b9a744e2f56fe60af1a5bfc81334327", + "sha256:6974785d913666587949f7c2946f7001e4fa2cb2d19f4e69ead02e4b8f50b33d" ], "index": "pypi", - "version": "==2.4.0" + "version": "==2.4.1" }, "gunicorn": { "hashes": [ @@ -184,9 +184,9 @@ }, "sqlalchemy": { "hashes": [ - "sha256:0459bf0ea6478f3e904de074d65769a11d74cdc34438ab3159250c96d089aef0" + "sha256:2f8ff566a4d3a92246d367f2e9cd6ed3edeef670dcd6dda6dfdc9efed88bcd80" ], - "version": "==1.3.7" + "version": "==1.3.8" }, "sqlalchemy-utils": { "hashes": [ @@ -197,17 +197,17 @@ }, "urllib3": { "hashes": [ - "sha256:b246607a25ac80bedac05c6f282e3cdaf3afb65420fd024ac94435cabe6e18d1", - "sha256:dbe59173209418ae49d485b87d1681aefa36252ee85884c31346debd19463232" + "sha256:3de946ffbed6e6746608990594d08faac602528ac7015ac28d33cee6a45b7398", + "sha256:9a107b99a5393caf59c7aa3c1249c16e6879447533d0887f4336dde834c7be86" ], - "version": "==1.25.3" + "version": "==1.25.6" }, "werkzeug": { "hashes": [ - "sha256:87ae4e5b5366da2347eb3116c0e6c681a0e939a33b2805e2c0cbd282664932c4", - "sha256:a13b74dd3c45f758d4ebdb224be8f1ab8ef58b3c0ffc1783a8c7d9f4f50227e6" + "sha256:7280924747b5733b246fe23972186c6b348f9ae29724135a6dfc1e53cea433e7", + "sha256:e5f4a1f98b52b18a93da705a7458e55afb26f32bff83ff5d19189f92462d65c4" ], - "version": "==0.15.5" + "version": "==0.16.0" } }, "develop": { @@ -272,10 +272,10 @@ }, "certifi": { "hashes": [ - "sha256:046832c04d4e752f37383b628bc601a7ea7211496b4638f6514d0e5b9acc4939", - "sha256:945e3ba63a0b9f577b1395204e13c3a231f9bc0223888be653286534e5873695" + "sha256:e4f3620cfea4f83eedc95b24abd9cd56f3c4b146dd0177e83a21b4eb49e21e50", + "sha256:fd7c7c74727ddcf00e9acd26bba8da604ffec95bf1c2144e67aff7a8b50e6cef" ], - "version": "==2019.6.16" + "version": "==2019.9.11" }, "cfgv": { "hashes": [ @@ -402,10 +402,10 @@ }, "identify": { "hashes": [ - "sha256:9aba2d08a82aa8e6f58810d4887ed3cf103a1befeb1eaf632d9c6fd2d6642542", - "sha256:b50ffad180b3a93b33a58b42597ef22493240d406ba07cc5058daf70f44b8d7c" + "sha256:4f1fe9a59df4e80fcb0213086fcf502bc1765a01ea4fe8be48da3b65afd2a017", + "sha256:d8919589bd2a5f99c66302fec0ef9027b12ae150b0b0213999ad3f695fc7296e" ], - "version": "==1.4.6" + "version": "==1.4.7" }, "idna": { "hashes": [ @@ -416,11 +416,11 @@ }, "importlib-metadata": { "hashes": [ - "sha256:23d3d873e008a513952355379d93cbcab874c58f4f034ff657c7a87422fa64e8", - "sha256:80d2de76188eabfbfcf27e6a37342c2827801e59c4cc14b0371c56fed43820e3" + "sha256:aa18d7378b00b40847790e7c27e11673d7fed219354109d0e7b9e5b25dc3ad26", + "sha256:d5f18a79777f3aa179c145737780282e27b508fc8fd688cb17c7a813e8bd39af" ], "markers": "python_version < '3.8'", - "version": "==0.19" + "version": "==0.23" }, "ipdb": { "hashes": [ @@ -431,10 +431,10 @@ }, "ipython": { "hashes": [ - "sha256:1d3a1692921e932751bc1a1f7bb96dc38671eeefdc66ed33ee4cbc57e92a410e", - "sha256:537cd0176ff6abd06ef3e23f2d0c4c2c8a4d9277b7451544c6cbf56d1c79a83d" + "sha256:c4ab005921641e40a68e405e286e7a1fcc464497e14d81b6914b4fd95e5dee9b", + "sha256:dd76831f065f17bddd7eaa5c781f5ea32de5ef217592cf019e34043b56895aa1" ], - "version": "==7.7.0" + "version": "==7.8.0" }, "ipython-genutils": { "hashes": [ @@ -519,10 +519,10 @@ }, "packaging": { "hashes": [ - "sha256:a7ac867b97fdc07ee80a8058fe4435ccd274ecc3b0ed61d852d7d53055528cf9", - "sha256:c491ca87294da7cc01902edbe30a5bc6c4c28172b5138ab4e4aa1b9d7bfaeafe" + "sha256:28b924174df7a2fa32c1953825ff29c61e2f5e082343165438812f00d3a7fc47", + "sha256:d9551545c6d761f3def1677baf08ab2a3ca17c56879e70fecba2fc4dde4ed108" ], - "version": "==19.1" + "version": "==19.2" }, "parso": { "hashes": [ @@ -533,10 +533,10 @@ }, "pbr": { "hashes": [ - "sha256:56e52299170b9492513c64be44736d27a512fa7e606f21942160b68ce510b4bc", - "sha256:9b321c204a88d8ab5082699469f52cc94c5da45c51f114113d01b3d993c24cdf" + "sha256:2c8e420cd4ed4cec4e7999ee47409e876af575d4c35a45840d59e8b5f3155ab8", + "sha256:b32c8ccaac7b1a20c0ce00ce317642e6cf231cf038f9875e0280e28af5bf7ac9" ], - "version": "==5.4.2" + "version": "==5.4.3" }, "pexpect": { "hashes": [ @@ -555,18 +555,18 @@ }, "pluggy": { "hashes": [ - "sha256:0825a152ac059776623854c1543d65a4ad408eb3d33ee114dff91e57ec6ae6fc", - "sha256:b9817417e95936bf75d85d3f8767f7df6cdde751fc40aed3bb3074cbcb77757c" + "sha256:0db4b7601aae1d35b4a033282da476845aa19185c1e6964b25cf324b5e4ec3e6", + "sha256:fa5fa1622fa6dd5c030e9cad086fa19ef6a0cf6d7a2d12318e10cb49d6d68f34" ], - "version": "==0.12.0" + "version": "==0.13.0" }, "pre-commit": { "hashes": [ - "sha256:21ce389ea3a480170804208baff8ceaac815ecf6b9bd6c6797de5584ad69cff8", - "sha256:3b0e901f442b966444833f1924e9bf9a7c10c79741b21520f68bc87639220f5e" + "sha256:1d3c0587bda7c4e537a46c27f2c84aa006acc18facf9970bf947df596ce91f3f", + "sha256:fa78ff96e8e9ac94c748388597693f18b041a181c94a4f039ad20f45287ba44a" ], "index": "pypi", - "version": "==1.18.2" + "version": "==1.18.3" }, "prompt-toolkit": { "hashes": [ @@ -620,11 +620,11 @@ }, "pytest": { "hashes": [ - "sha256:95b1f6db806e5b1b5b443efeb58984c24945508f93a866c1719e1a507a957d7c", - "sha256:c3d5020755f70c82eceda3feaf556af9a341334414a8eca521a18f463bcead88" + "sha256:813b99704b22c7d377bbd756ebe56c35252bb710937b46f207100e843440b3c2", + "sha256:cc6620b96bc667a0c8d4fa592a8c9c94178a1bd6cc799dbb057dfd9286d31a31" ], "index": "pypi", - "version": "==5.1.1" + "version": "==5.1.3" }, "pytest-cov": { "hashes": [ @@ -700,10 +700,10 @@ }, "stevedore": { "hashes": [ - "sha256:7be098ff53d87f23d798a7ce7ae5c31f094f3deb92ba18059b1aeb1ca9fec0a0", - "sha256:7d1ce610a87d26f53c087da61f06f9b7f7e552efad2a7f6d2322632b5f932ea2" + "sha256:01d9f4beecf0fbd070ddb18e5efb10567801ba7ef3ddab0074f54e3cd4e91730", + "sha256:e0739f9739a681c7a1fda76a102b65295e96a144ccdb552f2ae03c5f0abe8a14" ], - "version": "==1.30.1" + "version": "==1.31.0" }, "toml": { "hashes": [ @@ -714,24 +714,24 @@ }, "traitlets": { "hashes": [ - "sha256:9c4bd2d267b7153df9152698efb1050a5d84982d3384a37b2c1f7723ba3e7835", - "sha256:c6cb5e6f57c5a9bdaa40fa71ce7b4af30298fbab9ece9815b5d995ab6217c7d9" + "sha256:262089114405f22f4833be96b31e143ab906d7764a22c04c71fee0bbda4787ba", + "sha256:6ad5b30dacd5e2424c46cc94a0aeab990d98ae17d181acea2cc4272ac3409fca" ], - "version": "==4.3.2" + "version": "==4.3.3.dev0" }, "urllib3": { "hashes": [ - "sha256:b246607a25ac80bedac05c6f282e3cdaf3afb65420fd024ac94435cabe6e18d1", - "sha256:dbe59173209418ae49d485b87d1681aefa36252ee85884c31346debd19463232" + "sha256:3de946ffbed6e6746608990594d08faac602528ac7015ac28d33cee6a45b7398", + "sha256:9a107b99a5393caf59c7aa3c1249c16e6879447533d0887f4336dde834c7be86" ], - "version": "==1.25.3" + "version": "==1.25.6" }, "virtualenv": { "hashes": [ - "sha256:94a6898293d07f84a98add34c4df900f8ec64a570292279f6d91c781d37fd305", - "sha256:f6fc312c031f2d2344f885de114f1cb029dfcffd26aa6e57d2ee2296935c4e7d" + "sha256:680af46846662bb38c5504b78bad9ed9e4f3ba2d54f54ba42494fdf94337fe30", + "sha256:f78d81b62d3147396ac33fc9d77579ddc42cc2a98dd9ea38886f616b33bc7fb2" ], - "version": "==16.7.4" + "version": "==16.7.5" }, "wcwidth": { "hashes": [ @@ -742,10 +742,10 @@ }, "werkzeug": { "hashes": [ - "sha256:87ae4e5b5366da2347eb3116c0e6c681a0e939a33b2805e2c0cbd282664932c4", - "sha256:a13b74dd3c45f758d4ebdb224be8f1ab8ef58b3c0ffc1783a8c7d9f4f50227e6" + "sha256:7280924747b5733b246fe23972186c6b348f9ae29724135a6dfc1e53cea433e7", + "sha256:e5f4a1f98b52b18a93da705a7458e55afb26f32bff83ff5d19189f92462d65c4" ], - "version": "==0.15.5" + "version": "==0.16.0" }, "zipp": { "hashes": [ diff --git a/README.md b/README.md index c011fc4..ba4830c 100644 --- a/README.md +++ b/README.md @@ -151,6 +151,22 @@ Or using docker: $ make test-docker ``` +### Integration tests + +In order to execute the IT tests, first you need to start the Docker containers: + +``` +$ docker-compose up +``` + +After that, you are now able to run the integration tests: + +``` +$ make test-it +``` + +### Linting + Linting is a good way of keeping the code quality high. You can have everything you want (Python, Javascript and security checks) in a single task: ``` diff --git a/bin/docker-entrypoint.sh b/bin/docker-entrypoint.sh new file mode 100755 index 0000000..2b93e44 --- /dev/null +++ b/bin/docker-entrypoint.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +./bin/wait-for-it.sh -t 60 postgres:5432 +flask db upgrade --directory tomaco/migrations +npm run run & flask run --host=0.0.0.0 --port=8080 diff --git a/docker-compose.yaml b/docker-compose.yaml index 32d0652..96fd93d 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -11,12 +11,9 @@ services: - "./data/postgres:/var/lib/postgresql/data" ports: - 5432:5432 - tomaco: build: . container_name: tomaco - command: > - sh -c "npm run run & flask run --host=0.0.0.0 --port=8080" volumes: - ./:/app ports: @@ -28,6 +25,7 @@ services: GITHUB_CLIENT_ID: ${GITHUB_CLIENT_ID} GITHUB_CLIENT_SECRET: ${GITHUB_CLIENT_SECRET} DATABASE_URL: "postgresql://root@postgres/tomaco_dev" + entrypoint: bin/docker-entrypoint.sh stdin_open: true tty: true depends_on: diff --git a/pytest.ini b/pytest.ini index d52d0cc..0b42e6e 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,4 +1,6 @@ [pytest] -addopts = -s +addopts = -s -m 'not integration' filterwarnings = ignore::DeprecationWarning +markers = + integration: marks tests as integration tests diff --git a/tomaco/auth/tests/integration/__init__.py b/tomaco/auth/tests/integration/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tomaco/auth/tests/integration/test_login.py b/tomaco/auth/tests/integration/test_login.py new file mode 100644 index 0000000..fab4e42 --- /dev/null +++ b/tomaco/auth/tests/integration/test_login.py @@ -0,0 +1,18 @@ +import pytest + +from ...models import User + + +@pytest.mark.integration +class TestLogin: + def teardown_method(self, method): + User.query.delete() + User.query.session.commit() + + def test_should_access_login_page_when_not_logged_in(self, it_client): + result = it_client.get("http://0.0.0.0:8080/login") + assert result.status_code == 200 + + def test_should_create_user_after_login(self, it_client): + it_client.get("http://0.0.0.0:8080/login/start") + assert User.query.filter_by(username="gandalf").count() == 1 diff --git a/tomaco/conftest.py b/tomaco/conftest.py index c614cc2..52b9d21 100644 --- a/tomaco/conftest.py +++ b/tomaco/conftest.py @@ -1,3 +1,4 @@ +import requests import pytest from tomaco import create_app @@ -10,8 +11,13 @@ @pytest.fixture -def app(): - return create_app("tomaco.settings.Testing") +def app(request): + settings = ( + "tomaco.settings.IntegrationTests" + if request.config.option.markexpr == "integration" + else "tomaco.settings.Testing" + ) + return create_app(settings) @pytest.fixture @@ -45,3 +51,8 @@ def auth_client(client, user): @pytest.fixture def db_session_commit_mock(mocker, db): return mocker.patch.object(db.session, "commit") + + +@pytest.fixture +def it_client(app): + return requests diff --git a/tomaco/settings.py b/tomaco/settings.py index 6ce0c36..96b3ffb 100644 --- a/tomaco/settings.py +++ b/tomaco/settings.py @@ -41,9 +41,12 @@ class Development(Config): SQLALCHEMY_DATABASE_URI = os.environ.get( "DATABASE_URL", "postgresql://root@localhost/tomaco_dev" ) - TEMPLATE_AUTO_RELOAD = True class Testing(Config): TESTING = True SQLALCHEMY_DATABASE_URI = "sqlite://" + + +class IntegrationTests(Testing): + SQLALCHEMY_DATABASE_URI = "postgresql://root@0.0.0.0:5432/tomaco_dev"