| @@ -4,5 +4,6 @@ | |||
| <file url="file://$PROJECT_DIR$/calender/auth.py" dialect="GenericSQL" /> | |||
| <file url="file://$PROJECT_DIR$/calender/calender.py" dialect="GenericSQL" /> | |||
| <file url="file://$PROJECT_DIR$/calender/schema.sql" dialect="SQLite" /> | |||
| <file url="file://$PROJECT_DIR$/tests/data.sql" dialect="GenericSQL" /> | |||
| </component> | |||
| </project> | |||
| @@ -0,0 +1,4 @@ | |||
| include calender/schema.sql | |||
| graft calender/static | |||
| graft calender/templates | |||
| global-exclude *.pyc | |||
| @@ -0,0 +1,7 @@ | |||
| [tool:pytest] | |||
| testpaths = tests | |||
| [coverage:run] | |||
| branch = True | |||
| source = | |||
| calender | |||
| @@ -0,0 +1,12 @@ | |||
| from setuptools import find_packages, setup | |||
| setup( | |||
| name='calender', | |||
| version='3.0.1', | |||
| packages=find_packages(), | |||
| include_package_data=True, | |||
| zip_safe=False, | |||
| install_requires=[ | |||
| 'flask', | |||
| ], | |||
| ) | |||
| @@ -0,0 +1,57 @@ | |||
| import os | |||
| import tempfile | |||
| import pytest | |||
| from calender import create_app | |||
| from calender.db import get_db, init_db | |||
| with open(os.path.join(os.path.dirname(__file__), 'data.sql'), 'rb') as f: | |||
| _data_sql = f.read().decode('utf8') | |||
| @pytest.fixture | |||
| def app(): | |||
| db_fd, db_path = tempfile.mkstemp() | |||
| app = create_app({ | |||
| 'TESTING': True, | |||
| 'DATABASE': db_path, | |||
| }) | |||
| with app.app_context(): | |||
| init_db() | |||
| get_db().executescript(_data_sql) | |||
| yield app | |||
| os.close(db_fd) | |||
| os.unlink(db_path) | |||
| @pytest.fixture | |||
| def client(app): | |||
| return app.test_client() | |||
| @pytest.fixture | |||
| def runner(app): | |||
| return app.test_cli_runner() | |||
| class AuthActions(object): | |||
| def __init__(self, client): | |||
| self._client = client | |||
| def login(self, username='test', password='test'): | |||
| return self._client.post( | |||
| '/auth/login', | |||
| data={'username': username, 'password': password} | |||
| ) | |||
| def logout(self): | |||
| return self._client.get('/auth/logout') | |||
| @pytest.fixture | |||
| def auth(client): | |||
| return AuthActions(client) | |||
| @@ -0,0 +1,8 @@ | |||
| INSERT INTO user (username, password) | |||
| VALUES | |||
| ('test', 'pbkdf2:sha256:50000$TCI4GzcX$0de171a4f4dac32e3364c7ddc7c14f3e2fa61f2d17574483f7ffbb431b4acb2f'), | |||
| ('other', 'pbkdf2:sha256:50000$kJPKsz6N$d2d4784f1b030a9761f5ccaeeaca413f27f2ecb76d6168407af962ddce849f79'); | |||
| INSERT INTO post (title, body, author_id, created) | |||
| VALUES | |||
| ('test title', 'test' || x'0a' || 'body', 1, '2018-01-01 00:00:00'); | |||
| @@ -0,0 +1,58 @@ | |||
| import pytest | |||
| from flask import g, session | |||
| from calender.db import get_db | |||
| def test_register(client, app): | |||
| assert client.get('/auth/register').status_code == 200 | |||
| response = client.post( | |||
| '/auth/register', data={'username': 'a', 'password': 'a'} | |||
| ) | |||
| assert 'http://localhost/auth/login' == response.headers['Location'] | |||
| with app.app_context(): | |||
| assert get_db().execute( | |||
| "select * from user where username = 'a'", | |||
| ).fetchone() is not None | |||
| @pytest.mark.parametrize(('username', 'password', 'message'), ( | |||
| ('', '', b'Username is required.'), | |||
| ('a', '', b'Password is required.'), | |||
| ('test', 'test', b'already registered'), | |||
| )) | |||
| def test_register_validate_input(client, username, password, message): | |||
| response = client.post( | |||
| '/auth/register', | |||
| data={'username': username, 'password': password} | |||
| ) | |||
| assert message in response.data | |||
| def test_login(client, auth): | |||
| assert client.get('/auth/login').status_code == 200 | |||
| response = auth.login() | |||
| assert response.headers['Location'] == 'http://localhost/' | |||
| with client: | |||
| client.get('/') | |||
| assert session['user_id'] == 1 | |||
| assert g.user['username'] == 'test' | |||
| @pytest.mark.parametrize(('username', 'password', 'message'), ( | |||
| ('a', 'test', b'Incorrect username.'), | |||
| ('test', 'a', b'Incorrect password.'), | |||
| )) | |||
| def test_login_validate_input(auth, username, password, message): | |||
| response = auth.login(username, password) | |||
| assert message in response.data | |||
| def test_logout(client, auth): | |||
| auth.login() | |||
| with client: | |||
| auth.logout() | |||
| assert 'user_id' not in session | |||
| @@ -0,0 +1,94 @@ | |||
| import pytest | |||
| from calender.db import get_db | |||
| def test_index(client, auth): | |||
| response = client.get('/') | |||
| assert b"Log In" in response.data | |||
| assert b"Register" in response.data | |||
| auth.login() | |||
| response = client.get('/') | |||
| assert b'Log Out' in response.data | |||
| assert b'test title' in response.data | |||
| assert b'by test on 2018-01-01' in response.data | |||
| assert b'test\nbody' in response.data | |||
| assert b'href="/1/update"' in response.data | |||
| @pytest.mark.parametrize('path', ( | |||
| '/create', | |||
| '/1/update', | |||
| '/1/delete', | |||
| )) | |||
| def test_login_required(client, path): | |||
| response = client.post(path) | |||
| assert response.headers['Location'] == 'http://localhost/auth/login' | |||
| def test_author_required(app, client, auth): | |||
| # change the post author to another user | |||
| with app.app_context(): | |||
| db = get_db() | |||
| db.execute('UPDATE post SET author_id = 2 WHERE id = 1') | |||
| db.commit() | |||
| auth.login() | |||
| # current user can't modify other user's post | |||
| assert client.post('/1/update').status_code == 403 | |||
| assert client.post('/1/delete').status_code == 403 | |||
| # current user doesn't see edit link | |||
| assert b'href="/1/update"' not in client.get('/').data | |||
| @pytest.mark.parametrize('path', ( | |||
| '/2/update', | |||
| '/2/delete', | |||
| )) | |||
| def test_exists_required(client, auth, path): | |||
| auth.login() | |||
| assert client.post(path).status_code == 404 | |||
| def test_create(client, auth, app): | |||
| auth.login() | |||
| assert client.get('/create').status_code == 200 | |||
| client.post('/create', data={'title': 'created', 'body': ''}) | |||
| with app.app_context(): | |||
| db = get_db() | |||
| count = db.execute('SELECT COUNT(id) FROM post').fetchone()[0] | |||
| assert count == 2 | |||
| def test_update(client, auth, app): | |||
| auth.login() | |||
| assert client.get('/1/update').status_code == 200 | |||
| client.post('/1/update', data={'title': 'updated', 'body': ''}) | |||
| with app.app_context(): | |||
| db = get_db() | |||
| post = db.execute('SELECT * FROM post WHERE id = 1').fetchone() | |||
| assert post['title'] == 'updated' | |||
| @pytest.mark.parametrize('path', ( | |||
| '/create', | |||
| '/1/update', | |||
| )) | |||
| def test_create_update_validate(client, auth, path): | |||
| auth.login() | |||
| response = client.post(path, data={'title': '', 'body': ''}) | |||
| assert b'Title is required.' in response.data | |||
| def test_delete(client, auth, app): | |||
| auth.login() | |||
| response = client.post('/1/delete') | |||
| assert response.headers['Location'] == 'http://localhost/' | |||
| with app.app_context(): | |||
| db = get_db() | |||
| post = db.execute('SELECT * FROM post WHERE id = 1').fetchone() | |||
| assert post is None | |||
| @@ -0,0 +1,28 @@ | |||
| import sqlite3 | |||
| import pytest | |||
| from calender.db import get_db | |||
| def test_get_close_db(app): | |||
| with app.app_context(): | |||
| db = get_db() | |||
| assert db is get_db() | |||
| with pytest.raises(sqlite3.ProgrammingError) as e: | |||
| db.execute('SELECT 1') | |||
| assert 'closed' in str(e.value) | |||
| def test_init_db_command(runner, monkeypatch): | |||
| class Recorder(object): | |||
| called = False | |||
| def fake_init_db(): | |||
| Recorder.called = True | |||
| monkeypatch.setattr('calender.db.init_db', fake_init_db) | |||
| result = runner.invoke(args=['init-db']) | |||
| assert 'Initialized' in result.output | |||
| assert Recorder.called | |||
| @@ -0,0 +1,11 @@ | |||
| from calender import create_app | |||
| def test_config(): | |||
| assert not create_app().testing | |||
| assert create_app({'TESTING': True}).testing | |||
| def test_hello(client): | |||
| response = client.get('/hello') | |||
| assert response.data == b'Hello, World!' | |||