Skip to content
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

Unit tests #125

Draft
wants to merge 7 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions conda-lock.yml
Original file line number Diff line number Diff line change
Expand Up @@ -478,15 +478,15 @@ package:
category: main
optional: false
- name: filelock
version: 3.12.2
version: 3.12.3
manager: conda
platform: linux-64
dependencies:
python: '>=3.7'
url: https://conda.anaconda.org/conda-forge/noarch/filelock-3.12.2-pyhd8ed1ab_0.conda
url: https://conda.anaconda.org/conda-forge/noarch/filelock-3.12.3-pyhd8ed1ab_0.conda
hash:
md5: 53522ec72e6adae42bd373ef58357230
sha256: 1cbae9f05860f2e566e2977f14dfcd5494beb22c028b0a853ade4ec381d9de71
md5: 3104cf0ab9fb9de393051bf92b10dbe9
sha256: 47635be45aa6cbfd6af65c13b5f649aef84c484e1897aef625e4ad717663eebc
category: main
optional: false
- name: greenlet
Expand Down Expand Up @@ -1568,15 +1568,15 @@ package:
category: main
optional: false
- name: filelock
version: 3.12.2
version: 3.12.3
manager: conda
platform: osx-64
dependencies:
python: '>=3.7'
url: https://conda.anaconda.org/conda-forge/noarch/filelock-3.12.2-pyhd8ed1ab_0.conda
url: https://conda.anaconda.org/conda-forge/noarch/filelock-3.12.3-pyhd8ed1ab_0.conda
hash:
md5: 53522ec72e6adae42bd373ef58357230
sha256: 1cbae9f05860f2e566e2977f14dfcd5494beb22c028b0a853ade4ec381d9de71
md5: 3104cf0ab9fb9de393051bf92b10dbe9
sha256: 47635be45aa6cbfd6af65c13b5f649aef84c484e1897aef625e4ad717663eebc
category: main
optional: false
- name: greenlet
Expand Down
1 change: 1 addition & 0 deletions environment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,4 @@ dependencies:
- wtforms-sqlalchemy
- flask-dance
- bootstrap-flask ~=2.3.0
# - mock-alchemy~=0.2.6
4 changes: 4 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
[tool.pytest.ini_options]
testpaths = ['tests']
pythonpath = ['.']

[tool.black]
target-version = ['py310']
skip-string-normalization = true
Expand Down
7 changes: 7 additions & 0 deletions tasks/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,13 @@ def typecheck(ctx):
print('🎉🦆 Type checking passed.')


@task()
def unit(ctx):
"""Run unit tests."""
print_and_run(f'cd {PROJECT_DIR} && pytest')
print('🎉🦆 Unit checking passed.')


@task(
pre=[typecheck],
default=True,
Expand Down
38 changes: 38 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
from unittest import mock

import pytest
from flask_login import FlaskLoginClient
from mock_alchemy.mocking import UnifiedAlchemyMagicMock

from usaon_vta_survey import app as my_app
from usaon_vta_survey.models.tables import User


@pytest.fixture()
def app():
yield my_app


@pytest.fixture()
def client(app):
return app.test_client()


@pytest.fixture()
def session_with_user():
session = UnifiedAlchemyMagicMock(
data=[
(
[mock.call.query(User), mock.call.get(1)],
[User(id=1, email='[email protected]', name='foo bar', role_id='admin')],
)
]
)
return session


@pytest.fixture()
def client_logged_in(app, session_with_user):
app.test_client_class = FlaskLoginClient
user = session_with_user.query(User).get(1)
return app.test_client(user=user)
5 changes: 5 additions & 0 deletions tests/test_logged_in.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
def test_logged_in(client_logged_in):
actual = client_logged_in.get('/')
# breakpoint()
expected = ...
assert actual == expected
4 changes: 4 additions & 0 deletions tests/test_logged_out.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
def test_logged_out(client):
actual = client.get('/').location
expected = '/login?next=%2F'
assert actual == expected
7 changes: 7 additions & 0 deletions tests/test_version.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from usaon_vta_survey.constants.version import VERSION


def test_version_matches(client):
actual = client.get('/login', follow_redirects=True).text
expected = VERSION
assert expected in actual
6 changes: 6 additions & 0 deletions usaon_vta_survey/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

from flask import Flask
from flask_bootstrap import Bootstrap5
from flask_login import LoginManager
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy import MetaData
from sqlalchemy import inspect as sqla_inspect
Expand All @@ -25,6 +26,7 @@
)
)


app = Flask(__name__)
app.config['SECRET_KEY'] = os.environ.get('FLASK_SECRET_KEY', 'youcanneverguess')
app.config['LOGIN_DISABLED'] = envvar_is_true("USAON_VTA_LOGIN_DISABLED")
Expand All @@ -33,6 +35,10 @@
db.init_app(app)
bootstrap = Bootstrap5(app)

login_manager = LoginManager(app)
login_manager.login_view = 'login'
login_manager.login_message_category = 'info'

app.jinja_env.globals.update(sqla_inspect=sqla_inspect, __version__=__version__)

# NOTE: This is a circular import, but it's specified by the Flask docs:
Expand Down
7 changes: 6 additions & 1 deletion usaon_vta_survey/routes/login.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import os
import time

from flask import redirect, session, url_for
from flask import redirect, render_template, session, url_for
from flask_dance.contrib.google import google, make_google_blueprint
from flask_login import login_user

Expand All @@ -18,6 +18,11 @@

@app.route("/login")
def login():
return render_template('login.html')


@app.route("/login_start")
def login_start():
if not google.authorized:
return redirect(url_for("google.login"))
resp = google.get("/oauth2/v2/userinfo")
Expand Down
3 changes: 2 additions & 1 deletion usaon_vta_survey/routes/logout.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
from flask import redirect
from flask_login import logout_user
from flask_login import login_required, logout_user

from usaon_vta_survey import app


@app.route("/logout")
@login_required
def logout():
logout_user()
return redirect("/")
2 changes: 2 additions & 0 deletions usaon_vta_survey/routes/response/applications.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from flask import redirect, render_template, request, url_for
from flask_login import login_required

from usaon_vta_survey import app, db
from usaon_vta_survey.forms import FORMS_BY_MODEL
Expand All @@ -8,6 +9,7 @@


@app.route('/response/<string:survey_id>/applications', methods=['GET', 'POST'])
@login_required
def view_response_applications(survey_id: str):
"""View and add to applications associated with a response."""
Form = FORMS_BY_MODEL[ResponseApplication]
Expand Down
2 changes: 2 additions & 0 deletions usaon_vta_survey/routes/response/data_products.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from flask import redirect, render_template, request, url_for
from flask_login import login_required

from usaon_vta_survey import app, db
from usaon_vta_survey.forms import FORMS_BY_MODEL
Expand All @@ -7,6 +8,7 @@


@app.route('/response/<string:survey_id>/data_products', methods=['GET', 'POST'])
@login_required
def view_response_data_products(survey_id: str):
"""View and add to data products associated with a response."""
Form = FORMS_BY_MODEL[ResponseDataProduct]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from flask import Request, redirect, render_template, request, url_for
from flask_login import login_required
from flask_wtf import FlaskForm
from wtforms import FormField

Expand Down Expand Up @@ -134,6 +135,7 @@ def _request_args(request: Request) -> tuple[int | None, int | None]:
'/response/<string:survey_id>/application_societal_benefit_area_relationships',
methods=['GET', 'POST'],
)
@login_required
def view_response_application_societal_benefit_area_relationships(survey_id: str):
"""View and add application/SBA relationships to a response.

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from flask import Request, redirect, render_template, request, url_for
from flask_login import login_required
from flask_wtf import FlaskForm
from wtforms import FormField

Expand Down Expand Up @@ -125,6 +126,7 @@ def _request_args(request: Request) -> tuple[int | None, int | None]:
'/response/<string:survey_id>/data_product_application_relationships',
methods=['GET', 'POST'],
)
@login_required
def view_response_data_product_application_relationships(survey_id: str):
"""View and add application/dataproduct relationships to a response.

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from flask import Request, redirect, render_template, request, url_for
from flask_login import login_required
from flask_wtf import FlaskForm
from wtforms import FormField

Expand Down Expand Up @@ -129,6 +130,7 @@ def _request_args(request: Request) -> tuple[int | None, int | None]:
'/response/<string:survey_id>/observing_system_data_product_relationships',
methods=['GET', 'POST'],
)
@login_required
def view_response_observing_system_data_product_relationships(survey_id: str):
"""View and add observing system/dataproduct relationships to a response.

Expand Down
2 changes: 2 additions & 0 deletions usaon_vta_survey/routes/response/sbas.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from flask import redirect, render_template, request, url_for
from flask_login import login_required

from usaon_vta_survey import app, db
from usaon_vta_survey.forms import FORMS_BY_MODEL
Expand All @@ -13,6 +14,7 @@
@app.route(
'/response/<string:survey_id>/societal_benefit_areas', methods=['GET', 'POST']
)
@login_required
def view_response_sbas(survey_id: str):
"""View and add to observing systems associated with a response."""
sbas = SocietalBenefitArea.query.all()
Expand Down
3 changes: 3 additions & 0 deletions usaon_vta_survey/routes/survey.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
from flask import redirect, render_template, request, url_for
from flask_login import login_required

from usaon_vta_survey import app, db
from usaon_vta_survey.forms import FORMS_BY_MODEL
from usaon_vta_survey.models.tables import Survey


@app.route('/survey/new', methods=['GET', 'POST'])
@login_required
def new_survey():
Form = FORMS_BY_MODEL[Survey]
survey = Survey()
Expand All @@ -26,6 +28,7 @@ def new_survey():


@app.route('/survey/<string:survey_id>')
@login_required
def view_survey(survey_id: str):
# Fetch survey by id
survey = db.get_or_404(Survey, survey_id)
Expand Down
3 changes: 3 additions & 0 deletions usaon_vta_survey/routes/surveys.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
from flask import render_template
from flask_login import login_required

from usaon_vta_survey import app
from usaon_vta_survey.models.tables import Survey


@app.route('/surveys')
@login_required
def view_surveys():
# NOTE: if we're logged out we don't want to talk to the DB at all.
surveys = Survey.query.order_by(Survey.created_timestamp).all()
return render_template(
'surveys.html',
Expand Down
7 changes: 3 additions & 4 deletions usaon_vta_survey/routes/user.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
from flask import flash, render_template, request
from flask_login import LoginManager, current_user
from flask_login import current_user, login_required

from usaon_vta_survey import app, db
from usaon_vta_survey import app, db, login_manager
from usaon_vta_survey.forms import FORMS_BY_MODEL
from usaon_vta_survey.models.tables import User

login_manager = LoginManager(app)


@login_manager.user_loader
def load_user(user_id: str) -> User:
Expand All @@ -28,6 +26,7 @@ def _validate_role_change(user: User, form) -> None:


@app.route('/user/<user_id>', methods=['POST', 'GET'])
@login_required
def user(user_id: str):
Form = FORMS_BY_MODEL[User]
user = db.get_or_404(User, user_id)
Expand Down
2 changes: 2 additions & 0 deletions usaon_vta_survey/routes/users.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
from flask import render_template
from flask_login import login_required

from usaon_vta_survey import app
from usaon_vta_survey.models.tables import User


@app.route('/users')
@login_required
def view_users():
users = User.query.order_by(User.name).all()
return render_template(
Expand Down
6 changes: 1 addition & 5 deletions usaon_vta_survey/templates/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,7 @@ <h1>US AON VTA Survey System</h1>
</header>


{% if not current_user.is_authenticated %}
<h3>Please login to use this application.</h3>
{% else %}
{% block content %}{% endblock %}
{% endif %}
{% block content %}{% endblock %}

</section>
<footer>
Expand Down
7 changes: 7 additions & 0 deletions usaon_vta_survey/templates/login.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{% extends 'base.html' %}

{% block content %}

<h1>{% block title %}Please log in.{% endblock %}</h1>

{% endblock %}
2 changes: 1 addition & 1 deletion usaon_vta_survey/templates/macros/nav_buttons.j2
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,6 @@
{{ render_nav_item("logout", "Log out")}}
{% endif %}
{% else %}
{{ render_nav_item("login", "Log in")}}
{{ render_nav_item("login_start", "Log in")}}
{% endif %}
{%- endmacro -%}