From 2e5e10e97866da0c6dc0f091c63833309504aeb3 Mon Sep 17 00:00:00 2001 From: Eric Power Date: Sat, 24 Feb 2024 20:26:51 -0800 Subject: [PATCH 1/3] DimensionalQuestions can now be read using sqlite3 driver --- app/data_service/sqlite3/driver.py | 26 ++++++- app/models/__init__.py | 3 +- app/models/dimensional_question.py | 13 ++++ data/setup.sql | 10 +++ tests/test_assets/test_db_data.sql | 50 +++++++++++++ .../unit/data_service/sqlite3/test_driver.py | 75 ++++++++++++++++++- 6 files changed, 173 insertions(+), 4 deletions(-) create mode 100644 app/models/dimensional_question.py diff --git a/app/data_service/sqlite3/driver.py b/app/data_service/sqlite3/driver.py index 4083a78..0a7de27 100644 --- a/app/data_service/sqlite3/driver.py +++ b/app/data_service/sqlite3/driver.py @@ -5,7 +5,7 @@ from uuid import UUID from .model_factory import fetch_query_results_as_model -from app.models import Survey, TextQuestion +from app.models import Survey, TextQuestion, DimensionalQuestion class Sqlite3Driver: @@ -81,3 +81,27 @@ def insert_text_question(self, text_question: TextQuestion) -> None: text_question.question, ), ) + + def get_dimensional_question( + self, question_uid: UUID + ) -> DimensionalQuestion | None: + query = "SELECT * FROM dimensional_question WHERE uid = ? LIMIT 1;" + + with self._get_cursor() as cursor: + cursor.execute(query, (str(question_uid),)) + result = fetch_query_results_as_model(cursor, DimensionalQuestion) + + if len(result) > 0: + return result[0] + return None + + def get_dimensional_questions_from_survey( + self, survey_uid: UUID + ) -> list[DimensionalQuestion]: + query = "SELECT * FROM dimensional_question WHERE survey_uid = ?;" + + with self._get_cursor() as cursor: + cursor.execute(query, (str(survey_uid),)) + results = fetch_query_results_as_model(cursor, DimensionalQuestion) + + return results diff --git a/app/models/__init__.py b/app/models/__init__.py index 15ef524..06e9bd9 100644 --- a/app/models/__init__.py +++ b/app/models/__init__.py @@ -1,4 +1,5 @@ -__all__ = ["Survey", "TextQuestion"] +__all__ = ["Survey", "TextQuestion", "DimensionalQuestion"] from .survey import Survey from .text_question import TextQuestion +from .dimensional_question import DimensionalQuestion diff --git a/app/models/dimensional_question.py b/app/models/dimensional_question.py new file mode 100644 index 0000000..b24733c --- /dev/null +++ b/app/models/dimensional_question.py @@ -0,0 +1,13 @@ +from uuid import UUID, uuid4 +from pydantic import Field + +from .base_data_model import BaseDataModel + + +class DimensionalQuestion(BaseDataModel): + uid: UUID = Field(default_factory=uuid4) + survey_uid: UUID + question: str + dimension_one: str + dimension_two: str + dimension_three: str diff --git a/data/setup.sql b/data/setup.sql index ded0dd9..6d579a9 100644 --- a/data/setup.sql +++ b/data/setup.sql @@ -15,4 +15,14 @@ CREATE TABLE IF NOT EXISTS text_question ( , FOREIGN KEY(survey_uid) REFERENCES survey(uid) ); +CREATE TABLE IF NOT EXISTS dimensional_question ( + uid TEXT PRIMARY KEY + , survey_uid TEXT + , question TEXT + , dimension_one TEXT + , dimension_two TEXT + , dimension_three TEXT + , FOREIGN KEY(survey_uid) REFERENCES survey(uid) +); + COMMIT; diff --git a/tests/test_assets/test_db_data.sql b/tests/test_assets/test_db_data.sql index 11a8445..52744ec 100644 --- a/tests/test_assets/test_db_data.sql +++ b/tests/test_assets/test_db_data.sql @@ -18,6 +18,16 @@ INSERT INTO survey ( "00000000-b37a-32b3-19d9-72ec921021e3" , true , "Open Test Survey - 2Qs" +), +( + "00000000-0762-4fd1-b927-65ddb494e04f" + , true + , "Open Test Survey - 1DQ" +), +( + "00000000-e253-4c39-b32b-eeb4f8e8711d" + , true + , "Open Test Survey - 2DQ" ); INSERT INTO text_question ( @@ -45,4 +55,44 @@ INSERT INTO text_question ( , "What story?" ); +INSERT INTO dimensional_question ( + uid + , survey_uid + , question + , dimension_one + , dimension_two + , dimension_three +) VALUES ( + "11111111-3e01-4b2c-b396-1b20facf99c2" + , "00000000-9c88-4b81-9de4-bac7444fbb0a" + , "How would you describe your manager on the following dimensions?" + , "Empathetic" + , "Motivational" + , "Knowledgable" +), +( + "11111111-2b47-4d02-8c48-0fa65f0da016" + , "00000000-0762-4fd1-b927-65ddb494e04f" + , "What came across in your last interaction with the CEO?" + , "Purpose" + , "Audacity" + , "Clarity of Direction" +), +( + "11111111-041a-490d-a60c-82babc856120" + , "00000000-e253-4c39-b32b-eeb4f8e8711d" + , "How would you describe your manager on the following dimensions?" + , "Empathetic" + , "Motivational" + , "Knowledgable" +), +( + "11111111-b36f-4e80-aba4-9707a10d6acf" + , "00000000-e253-4c39-b32b-eeb4f8e8711d" + , "What came across in your last interaction with the CEO?" + , "Purpose" + , "Audacity" + , "Clarity of Direction" +); + COMMIT; diff --git a/tests/unit/data_service/sqlite3/test_driver.py b/tests/unit/data_service/sqlite3/test_driver.py index 3750a76..37d553f 100644 --- a/tests/unit/data_service/sqlite3/test_driver.py +++ b/tests/unit/data_service/sqlite3/test_driver.py @@ -2,7 +2,7 @@ import pytest from uuid import UUID -from app.models import Survey, TextQuestion +from app.models import Survey, TextQuestion, DimensionalQuestion from app.data_service.sqlite3 import Sqlite3Driver @@ -23,7 +23,7 @@ class TestDriverSurveyMethods: def test_sqlite3_driver_can_get_list_open_surveys(self, populated_db_driver): surveys = populated_db_driver.get_open_surveys() - assert len(surveys) == 2 + assert len(surveys) == 4 for survey in surveys: assert isinstance(survey, Survey) @@ -172,3 +172,74 @@ def test_insert_text_question_fails_do_to_survey_foreign_key( with pytest.raises(sqlite3.IntegrityError): populated_db_driver.insert_text_question(text_question) + + +class TestDriverDimensionalQuestionMethods: + @pytest.mark.parametrize( + "dimensional_question_uid", + ( + UUID("11111111-3e01-4b2c-b396-1b20facf99c2"), + UUID("11111111-2b47-4d02-8c48-0fa65f0da016"), + UUID("11111111-041a-490d-a60c-82babc856120"), + UUID("11111111-b36f-4e80-aba4-9707a10d6acf"), + ), + ) + def test_driver_get_dimensional_question( + self, populated_db_driver: Sqlite3Driver, dimensional_question_uid: UUID + ): + question = populated_db_driver.get_dimensional_question( + question_uid=dimensional_question_uid + ) + + assert isinstance(question, DimensionalQuestion) + assert question.uid == dimensional_question_uid + + def test_get_dimensional_question_returns_none_if_no_question_with_uid_exists( + self, populated_db_driver: Sqlite3Driver + ): + question = populated_db_driver.get_dimensional_question( + question_uid=UUID("209d67a3-d354-4cd8-afc4-7e6479582086") + ) + + assert question is None + + @pytest.mark.parametrize( + "survey_uid, expected_dq_uids", + ( + ( + UUID("00000000-9c88-4b81-9de4-bac7444fbb0a"), + {UUID("11111111-3e01-4b2c-b396-1b20facf99c2")}, + ), + ( + UUID("00000000-0762-4fd1-b927-65ddb494e04f"), + {UUID("11111111-2b47-4d02-8c48-0fa65f0da016")}, + ), + ( + UUID("00000000-e253-4c39-b32b-eeb4f8e8711d"), + { + UUID("11111111-041a-490d-a60c-82babc856120"), + UUID("11111111-b36f-4e80-aba4-9707a10d6acf"), + }, + ), + ( + UUID("99999999-a087-4fb6-a123-24ff30263530"), + {}, + ), + ), + ) + def test_driver_can_list_dimensional_questions_related_to_a_survey_uid( + self, + populated_db_driver: Sqlite3Driver, + survey_uid: UUID, + expected_dq_uids: set[UUID], + ): + dimensional_questions = ( + populated_db_driver.get_dimensional_questions_from_survey( + survey_uid=survey_uid + ) + ) + + assert len(dimensional_questions) == len(expected_dq_uids) + for tq in dimensional_questions: + assert isinstance(tq, DimensionalQuestion) + assert tq.uid in expected_dq_uids From 3555df4504fc8cb22814d249b68581b4d28acce0 Mon Sep 17 00:00:00 2001 From: Eric Power Date: Sun, 25 Feb 2024 13:29:31 -0800 Subject: [PATCH 2/3] DataService has methods to read DimensionalQuestions --- app/data_service/data_service.py | 12 +++- tests/unit/data_service/test_data_service.py | 76 ++++++++++++++++++-- 2 files changed, 80 insertions(+), 8 deletions(-) diff --git a/app/data_service/data_service.py b/app/data_service/data_service.py index 22cab12..52fac37 100644 --- a/app/data_service/data_service.py +++ b/app/data_service/data_service.py @@ -1,7 +1,7 @@ from uuid import UUID from .sqlite3 import Sqlite3Driver -from app.models import Survey, TextQuestion +from app.models import Survey, TextQuestion, DimensionalQuestion class DataService: @@ -31,3 +31,13 @@ def get_text_questions_from_survey(self, survey_uid: UUID) -> list[TextQuestion] def insert_text_question(self, text_question: TextQuestion) -> None: self._driver.insert_text_question(text_question=text_question) + + def get_dimensional_question( + self, question_uid: UUID + ) -> DimensionalQuestion | None: + return self._driver.get_dimensional_question(question_uid=question_uid) + + def get_dimensional_questions_from_survey( + self, survey_uid: UUID + ) -> list[DimensionalQuestion]: + return self._driver.get_dimensional_questions_from_survey(survey_uid=survey_uid) diff --git a/tests/unit/data_service/test_data_service.py b/tests/unit/data_service/test_data_service.py index 2601c81..607d4dd 100644 --- a/tests/unit/data_service/test_data_service.py +++ b/tests/unit/data_service/test_data_service.py @@ -4,38 +4,60 @@ from app.data_service import DataService from app.data_service.sqlite3 import Sqlite3Driver -from app.models import Survey, TextQuestion +from app.models import Survey, TextQuestion, DimensionalQuestion @pytest.fixture def surveys(new_open_survey) -> list[Survey]: - new_open_survey.uid = UUID("74bce4cf-0875-471b-a7c4-f25c7ef42864") return [ new_open_survey, ] @pytest.fixture -def questions() -> list[TextQuestion]: +def text_questions(new_open_survey) -> list[TextQuestion]: return [ TextQuestion( question="What's up?", - survey_uid=UUID("74bce4cf-0875-471b-a7c4-f25c7ef42864"), + survey_uid=new_open_survey.uid, ) ] @pytest.fixture -def data_service(surveys, questions) -> DataService: +def dimensional_questions(new_open_survey) -> list[DimensionalQuestion]: + return [ + DimensionalQuestion( + question="How would you rank?", + dimension_one="Dim 1", + dimension_two="Dim 2", + dimension_three="Dim 3", + survey_uid=new_open_survey.uid, + ) + ] + + +@pytest.fixture +def data_service( + surveys: list[Survey], + text_questions: list[TextQuestion], + dimensional_questions: list[DimensionalQuestion], +) -> DataService: mock_driver = Mock(spec=Sqlite3Driver) mock_driver.get_open_surveys.return_value = surveys mock_driver.insert_survey.return_value = None - mock_driver.get_text_questions_from_survey.return_value = questions - mock_driver.get_text_question.return_value = questions[0] + mock_driver.get_text_questions_from_survey.return_value = text_questions + mock_driver.get_text_question.return_value = text_questions[0] mock_driver.insert_text_question.return_value = None + mock_driver.get_dimensional_questions_from_survey.return_value = ( + dimensional_questions + ) + mock_driver.get_dimensional_question.return_value = dimensional_questions[0] + # mock_driver.insert_dimensional_question.return_value = None + return DataService(driver=mock_driver) @@ -133,3 +155,43 @@ def test_insert_text_question( data_service._driver.insert_text_question.assert_called_once_with( text_question=new_text_question ) + + +class TestDataServiceDimensionalQuestionMethods: + def test_get_questions_from_survey( + self, + data_service: DataService, + ): + survey_uid = UUID("ee50dd84-86a0-4a9d-a632-ec6670e2cd89") + questions = data_service.get_dimensional_questions_from_survey( + survey_uid=survey_uid + ) + + assert len(questions) == 1 + assert isinstance(questions[0], DimensionalQuestion) + method = data_service._driver.get_dimensional_questions_from_survey + method.assert_called_once_with(survey_uid=survey_uid) + + def test_get_question( + self, + data_service: DataService, + ): + question_uid = UUID("ee50dd84-86a0-4a9d-a632-ec6670e2cd89") + question = data_service.get_dimensional_question(question_uid=question_uid) + + assert isinstance(question, DimensionalQuestion) + assert question == data_service._driver.get_dimensional_question.return_value + + +# def test_insert_dimensional_question( +# self, +# data_service: DataService, +# existing_open_survey: Survey, +# new_dimensional_question: DimensionalQuestion, +# ): +# new_dimensional_question.survey_uid = existing_open_survey.uid +# +# data_service.insert_dimensional_question(new_dimensional_question) +# data_service._driver.insert_dimensional_question.assert_called_once_with( +# dimensional_question=new_dimensional_question +# ) From 1562f2b56cffc6998d729cd35dc1a21ed343d596 Mon Sep 17 00:00:00 2001 From: Eric Power Date: Sun, 25 Feb 2024 15:20:13 -0800 Subject: [PATCH 3/3] DimensionalQuestions now appear on /surveys/ pages. Answering / proper displaying of question not implemented. --- app/routes.py | 15 +++++++--- app/templates/survey.html | 19 ++++++++++-- data/initial_data.sql | 30 +++++++++++++++---- tests/integration/test_application.py | 21 ++++++++++++- tests/test_assets/test_db_data.sql | 28 ++++++++++++++++- .../unit/data_service/sqlite3/test_driver.py | 2 +- tests/unit/test_routes.py | 6 +++- 7 files changed, 105 insertions(+), 16 deletions(-) diff --git a/app/routes.py b/app/routes.py index 302160d..8bf7c1c 100644 --- a/app/routes.py +++ b/app/routes.py @@ -15,8 +15,6 @@ app = Flask(__name__) jinja_partials.register_extensions(app) -# TODO: test that every endpoint logs the request. - @app.before_request def create_data_service() -> None: @@ -49,12 +47,21 @@ def get_survey(uid: str) -> str: data_service: DataService = app.data_service # type: ignore[attr-defined] survey_uid = UUID(uid) survey = data_service.get_survey_if_open(survey_uid=survey_uid) - questions = data_service.get_text_questions_from_survey(survey_uid=survey_uid) if survey is None: abort(404, "Could not find a survey with that UUID.") - return render_template("survey.html", survey=survey, questions=questions) + text_questions = data_service.get_text_questions_from_survey(survey_uid=survey_uid) + dimensional_questions = data_service.get_dimensional_questions_from_survey( + survey_uid=survey_uid + ) + + return render_template( + "survey.html", + survey=survey, + text_questions=text_questions, + dimensional_questions=dimensional_questions, + ) @app.route("/surveys/new") diff --git a/app/templates/survey.html b/app/templates/survey.html index 4e0573b..0400a05 100644 --- a/app/templates/survey.html +++ b/app/templates/survey.html @@ -3,12 +3,25 @@ {% block content %}

{{survey.name}}

+

Note: Answering Questions is not yet implemented & this page is for testing purposes only.

+ +
+{% for question in text_questions %} +

{{question.question}}

+{% endfor %}
-{% for question in questions %} -

{{question.question}}

+ +
+{% for question in dimensional_questions %} +

{{question.question}}

+
    +
  • question.dimension_one
  • +
  • question.dimension_two
  • +
  • question.dimension_three
  • +
{% endfor %}
-Answering Questions is not yet implemented. + {% endblock %} diff --git a/data/initial_data.sql b/data/initial_data.sql index 220a10a..7529d29 100644 --- a/data/initial_data.sql +++ b/data/initial_data.sql @@ -15,9 +15,29 @@ INSERT INTO text_question ( ) VALUES ( '9c9facb5-f360-4155-852a-8e2ac04607ea' , '4b5bfb06-2060-4abf-b5fd-3bae5dcf72b9' - , 'What is your name?' -), ( - 'ee947616-3d16-4095-bc8f-603be72022d3' - , '4b5bfb06-2060-4abf-b5fd-3bae5dcf72b9' - , 'Are you sure?' + , 'What story would you tell your best friend about this company?' +); + +INSERT INTO dimensional_question ( + uid + , survey_uid + , question + , dimension_one + , dimension_two + , dimension_three +) VALUES ( + "4ccfc2b3-c18a-4c86-b918-1f30efdfeea2" + , "4b5bfb06-2060-4abf-b5fd-3bae5dcf72b9" + , "How strongly does this story demonstrate the following values?" + , "Empathy to Colleagues" + , "Service to Others" + , "Individual Growth" +), +( + "a6938e91-e7cc-4048-9b77-8cbba8d735cd" + , "4b5bfb06-2060-4abf-b5fd-3bae5dcf72b9" + , "How strongly does this story demonstrate the following values?" + , "Accountability" + , "Efficiency" + , "Individual Growth" ); diff --git a/tests/integration/test_application.py b/tests/integration/test_application.py index cb34017..c18bd6f 100644 --- a/tests/integration/test_application.py +++ b/tests/integration/test_application.py @@ -5,6 +5,7 @@ from app.data_service import DataService, Sqlite3Driver from app.models import Survey +from tests.unit.test_routes_helpers import assert_response_is_valid_html @pytest.fixture @@ -19,6 +20,24 @@ def patched_init(self, driver): monkeypatch.undo() +class TestReadEndpoints: + test_cases = ( + "/", + "/admin", + "/surveys/new", + "/surveys/00000000-1f7c-43e9-ab61-3c34fd59a333", + ) + + @pytest.mark.parametrize("slug", test_cases) + def test_endpoint_returns_html( + self, app_client, setup_data_service, slug: str + ) -> None: + response = app_client.get(slug) + + assert response.status_code == 200 + assert_response_is_valid_html(response) + + class TestMutationEndpoints: test_cases = [ ( @@ -38,7 +57,7 @@ class TestMutationEndpoints: "name": "test mutable endpoints 2", "is_open": "on", "question-0": "What's my name again?", - "question-1": "What's you name again?", + "question-1": "What's your name again?", "question-2": "Why are we here again?", }, "/surveys", diff --git a/tests/test_assets/test_db_data.sql b/tests/test_assets/test_db_data.sql index 52744ec..7eb5b3b 100644 --- a/tests/test_assets/test_db_data.sql +++ b/tests/test_assets/test_db_data.sql @@ -28,6 +28,11 @@ INSERT INTO survey ( "00000000-e253-4c39-b32b-eeb4f8e8711d" , true , "Open Test Survey - 2DQ" +), +( + "00000000-1f7c-43e9-ab61-3c34fd59a333" + , true + , "Open Test Survey - Mixed Questions" ); INSERT INTO text_question ( @@ -53,6 +58,11 @@ INSERT INTO text_question ( "11111111-b37a-44a1-19d9-72ec921021e3" , "00000000-b37a-32b3-19d9-72ec921021e3" , "What story?" +), +( + "11111111-8713-4275-9f43-4d127671f0ff" + , "00000000-1f7c-43e9-ab61-3c34fd59a333" + , "What story would you tell your grandchild about working in the military?" ); INSERT INTO dimensional_question ( @@ -93,6 +103,22 @@ INSERT INTO dimensional_question ( , "Purpose" , "Audacity" , "Clarity of Direction" -); +), +( + "11111111-0fcc-484b-bab4-c33309cebd3c" + , "00000000-1f7c-43e9-ab61-3c34fd59a333" + , "How strongly does this story demonstrate the following values?" + , "Empathy to Colleagues" + , "Service to Others" + , "Individual Growth" +), +( + "11111111-b54a-45ee-86af-05f625f3d239" + , "00000000-1f7c-43e9-ab61-3c34fd59a333" + , "How strongly does this story demonstrate the following values?" + , "Accountability" + , "Efficiency" + , "Individual Growth" +);; COMMIT; diff --git a/tests/unit/data_service/sqlite3/test_driver.py b/tests/unit/data_service/sqlite3/test_driver.py index 37d553f..add74a9 100644 --- a/tests/unit/data_service/sqlite3/test_driver.py +++ b/tests/unit/data_service/sqlite3/test_driver.py @@ -23,7 +23,7 @@ class TestDriverSurveyMethods: def test_sqlite3_driver_can_get_list_open_surveys(self, populated_db_driver): surveys = populated_db_driver.get_open_surveys() - assert len(surveys) == 4 + assert len(surveys) == 5 for survey in surveys: assert isinstance(survey, Survey) diff --git a/tests/unit/test_routes.py b/tests/unit/test_routes.py index 57d8112..27a60a4 100644 --- a/tests/unit/test_routes.py +++ b/tests/unit/test_routes.py @@ -72,7 +72,11 @@ def test_get_request_returns_404( ( ( "/surveys/00000000-a087-4fb6-a123-24ff30263530", - ("get_survey_if_open", "get_text_questions_from_survey"), + ( + "get_survey_if_open", + "get_text_questions_from_survey", + "get_dimensional_questions_from_survey", + ), ), ), )