diff options
| author | Mitsuo Tokumori <[email protected]> | 2023-10-29 06:59:37 -0500 |
|---|---|---|
| committer | Mitsuo Tokumori <[email protected]> | 2023-10-29 06:59:37 -0500 |
| commit | 9baa55436bc5a98d118a17656bbf25e563522964 (patch) | |
| tree | daa01118bd11f5cf13d386d2c727a384563df1da | |
| parent | 71e7d6516608486f67afad5aad1f7b7f9a45886f (diff) | |
| download | ustayml-9baa55436bc5a98d118a17656bbf25e563522964.tar.gz ustayml-9baa55436bc5a98d118a17656bbf25e563522964.tar.bz2 ustayml-9baa55436bc5a98d118a17656bbf25e563522964.zip | |
Add unit tests
| -rw-r--r-- | flaskr/__init__.py | 6 | ||||
| -rw-r--r-- | pyproject.toml | 9 | ||||
| -rw-r--r-- | tests/conftest.py | 61 | ||||
| -rw-r--r-- | tests/data.sql | 9 | ||||
| -rw-r--r-- | tests/test_auth.py | 62 | ||||
| -rw-r--r-- | tests/test_blog.py | 93 | ||||
| -rw-r--r-- | tests/test_db.py | 33 | ||||
| -rw-r--r-- | tests/test_factory.py | 11 |
8 files changed, 280 insertions, 4 deletions
diff --git a/flaskr/__init__.py b/flaskr/__init__.py index f2a4584..25c2682 100644 --- a/flaskr/__init__.py +++ b/flaskr/__init__.py @@ -24,9 +24,9 @@ def create_app(test_config=None): pass # Routes - # @app.route('/hello') - # def index(): - # return 'Hello, World!' + @app.route('/hello') + def hello(): + return 'Hello, World!' # Register functions and blueprints from . import db diff --git a/pyproject.toml b/pyproject.toml index ab85115..5156197 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,4 +8,11 @@ dependencies = [ [build-system] requires = ["flit_core<4"] -build-backend = "flit_core.buildapi"
\ No newline at end of file +build-backend = "flit_core.buildapi" + +[tool.pytest.ini_options] +testpaths = ["tests"] + +[tool.coverage.run] +branch = true +source = ["flaskr"]
\ No newline at end of file diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..45079a9 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,61 @@ +"""contains setup functions called fixtures that each test will use""" + +import os +import tempfile + +import pytest +from flaskr import create_app +from flaskr.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') + + +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) + + +def client(app): + """can make requests to the app without running the server""" + return app.test_client() + + +def runner(app): + """can all the Click commands registered with the application""" + 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') + + +def auth(client): + return AuthActions(client)
\ No newline at end of file diff --git a/tests/data.sql b/tests/data.sql new file mode 100644 index 0000000..46269e4 --- /dev/null +++ b/tests/data.sql @@ -0,0 +1,9 @@ +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'); + diff --git a/tests/test_auth.py b/tests/test_auth.py new file mode 100644 index 0000000..401b61f --- /dev/null +++ b/tests/test_auth.py @@ -0,0 +1,62 @@ +import pytest +from flask import g, session +from flaskr.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 response.headers["Location"] == "/auth/login" + + with app.app_context(): + assert get_db().execute( + "SELECT * FROM user WHERE username = 'a'", + ).fetchone() is not None + + [email protected](('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"] == "/" + + with client: + """ + Using client in a with block allows accessing context variables such as + session after the response is returned. Normally, accessing session + outside of a request would raise an error. + """ + client.get('/') + assert session['user_id'] == 1 + assert g.user['username'] == 'test' + + [email protected](('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 diff --git a/tests/test_blog.py b/tests/test_blog.py new file mode 100644 index 0000000..75f67a5 --- /dev/null +++ b/tests/test_blog.py @@ -0,0 +1,93 @@ +import pytest +from flaskr.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 + + [email protected]('path', ( + '/create', + '/1/update', + '/1/delete', +)) +def test_login_required(client, path): + response = client.post(path) + assert response.headers["Location"] == "/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 + + [email protected]('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' + + [email protected]('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"] == "/" + + with app.app_context(): + db = get_db() + post = db.execute('SELECT * FROM post WHERE id = 1').fetchone() + assert post is None
\ No newline at end of file diff --git a/tests/test_db.py b/tests/test_db.py new file mode 100644 index 0000000..6ac4635 --- /dev/null +++ b/tests/test_db.py @@ -0,0 +1,33 @@ +import sqlite3 + +import pytest +from flaskr.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): + """ + This test uses Pytest’s monkeypatch fixture to replace the init_db + function with one that records that it’s been called. The runner fixture you + wrote above is used to call the init-db command by name. + """ + class Recorder(object): + called = False + + def fake_init_db(): + Recorder.called = True + + monkeypatch.setattr('flaskr.db.init_db', fake_init_db) + result = runner.invoke(args=['init-db']) + assert 'Initialized' in result.output + assert Recorder.called
\ No newline at end of file diff --git a/tests/test_factory.py b/tests/test_factory.py new file mode 100644 index 0000000..024c19a --- /dev/null +++ b/tests/test_factory.py @@ -0,0 +1,11 @@ +from flaskr 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!'
\ No newline at end of file |
