From af97c4219d347a210f873b000e0f3b11c70663a6 Mon Sep 17 00:00:00 2001 From: Andy Kuny Date: Fri, 9 Feb 2024 09:21:35 -0500 Subject: [PATCH 1/6] Add view models and presentation logic --- nad_ch/application/use_cases.py | 33 ++++-- nad_ch/application/view_models.py | 100 ++++++++++++++++++ nad_ch/controllers/web/routes.py | 14 ++- .../templates/{layout => _layouts}/base.html | 4 +- .../templates/{layout => _layouts}/head.html | 0 .../{layout => _layouts}/sidebar.html | 0 nad_ch/controllers/web/templates/index.html | 2 +- nad_ch/controllers/web/templates/reports.html | 23 ---- .../web/templates/reports/index.html | 27 +++++ .../web/templates/reports/show.html | 10 ++ nad_ch/domain/entities.py | 8 +- tests/application/test_use_cases.py | 61 +++++++---- tests/application/test_view_models.py | 67 ++++++++++++ tests/fakes_and_mocks.py | 3 + 14 files changed, 294 insertions(+), 58 deletions(-) create mode 100644 nad_ch/application/view_models.py rename nad_ch/controllers/web/templates/{layout => _layouts}/base.html (79%) rename nad_ch/controllers/web/templates/{layout => _layouts}/head.html (100%) rename nad_ch/controllers/web/templates/{layout => _layouts}/sidebar.html (100%) delete mode 100644 nad_ch/controllers/web/templates/reports.html create mode 100644 nad_ch/controllers/web/templates/reports/index.html create mode 100644 nad_ch/controllers/web/templates/reports/show.html create mode 100644 tests/application/test_view_models.py diff --git a/nad_ch/application/use_cases.py b/nad_ch/application/use_cases.py index 565788d..43c8ce3 100644 --- a/nad_ch/application/use_cases.py +++ b/nad_ch/application/use_cases.py @@ -2,10 +2,17 @@ from typing import List from nad_ch.application.dtos import DownloadResult from nad_ch.application.interfaces import ApplicationContext +from nad_ch.application.view_models import ( + get_view_model, + DataProviderViewModel, + DataSubmissionViewModel, +) from nad_ch.domain.entities import DataProvider, DataSubmission -def add_data_provider(ctx: ApplicationContext, provider_name: str) -> None: +def add_data_provider( + ctx: ApplicationContext, provider_name: str +) -> DataProviderViewModel: if not provider_name: ctx.logger.error("Provider name required") return @@ -19,19 +26,21 @@ def add_data_provider(ctx: ApplicationContext, provider_name: str) -> None: ctx.providers.add(provider) ctx.logger.info("Provider added") + return get_view_model(provider) -def list_data_providers(ctx: ApplicationContext) -> List[DataProvider]: + +def list_data_providers(ctx: ApplicationContext) -> List[DataProviderViewModel]: providers = ctx.providers.get_all() ctx.logger.info("Data Provider Names:") for p in providers: ctx.logger.info(p.name) - return providers + return get_view_model(providers) def ingest_data_submission( ctx: ApplicationContext, file_path: str, provider_name: str -) -> None: +) -> DataSubmissionViewModel: if not file_path: ctx.logger.error("File path required") return @@ -53,12 +62,24 @@ def ingest_data_submission( submission = DataSubmission(filename, provider) ctx.submissions.add(submission) ctx.logger.info(f"Submission added: {submission.filename}") + + return get_view_model(submission) except Exception as e: ctx.storage.delete(filename) ctx.logger.error(f"Failed to process submission: {e}") -def list_data_submissions_by_provider(ctx: ApplicationContext, provider_name: str): +def get_data_submission( + ctx: ApplicationContext, submission_id: int +) -> DataSubmissionViewModel: + submission = ctx.submissions.get_by_id(submission_id) + + return get_view_model(submission) + + +def list_data_submissions_by_provider( + ctx: ApplicationContext, provider_name: str +) -> List[DataSubmissionViewModel]: provider = ctx.providers.get_by_name(provider_name) if not provider: ctx.logger.error("Provider with that name does not exist") @@ -69,7 +90,7 @@ def list_data_submissions_by_provider(ctx: ApplicationContext, provider_name: st for s in submissions: ctx.logger.info(f"{s.provider.name}: {s.filename}") - return submissions + return get_view_model(submissions) def validate_data_submission(ctx: ApplicationContext, filename: str): diff --git a/nad_ch/application/view_models.py b/nad_ch/application/view_models.py new file mode 100644 index 0000000..79a1c6d --- /dev/null +++ b/nad_ch/application/view_models.py @@ -0,0 +1,100 @@ +from dataclasses import dataclass +from datetime import datetime +import json +from typing import Union, List, Tuple +from nad_ch.domain.entities import Entity, DataProvider, DataSubmission + + +def get_view_model(entity: Union[Entity, List[Entity]]) -> Union[Entity, List[Entity]]: + """ + Provide a single factory function that an application use case can call in order to + get a static view model object that it can return to its caller. + """ + entity_to_vm_function_map = { + DataProvider: create_data_provider_vm, + DataSubmission: create_data_submission_vm, + } + + # Check if the input is a list of entities + if isinstance(entity, list): + # Process each entity in the list using a list comprehension + return [get_view_model(single_entity) for single_entity in entity] + + # Process a single entity + entity_type = type(entity) + if entity_type in entity_to_vm_function_map: + mapping_function = entity_to_vm_function_map[entity_type] + return mapping_function(entity) # Call the mapping function for the entity + else: + raise ValueError(f"No mapping function defined for entity type: {entity_type}") + + +@dataclass +class DataProviderViewModel: + id: int + date_created: str + name: str + + +def create_data_provider_vm(provider: DataProvider) -> DataProviderViewModel: + return DataProviderViewModel( + id=provider.id, + date_created=present_date(provider.created_at), + name=provider.name, + ) + + +@dataclass +class DataSubmissionViewModel: + id: int + date_created: str + filename: str + provider_name: str + report: str + + +def create_data_submission_vm(submission: DataSubmission) -> DataSubmissionViewModel: + # TODO make this be an empty array so the frontend doesn't have to check for None + report_json = None + if submission.report is not None: + enriched_report = enrich_report(submission.report) + report_json = json.dumps(enriched_report) + + return DataSubmissionViewModel( + id=submission.id, + date_created=present_date(submission.created_at), + filename=submission.filename, + provider_name=submission.provider.name, + report=report_json, + ) + + +def enrich_report(report: dict) -> dict: + for feature in report.get("features", []): + percent_populated, percent_empty = calculate_percentages( + feature.get("populated_count"), feature.get("null_count") + ) + + feature["populated_percentage"] = present_percentage(percent_populated) + feature["null_percentage"] = present_percentage(percent_empty) + + return report + + +def present_date(date: datetime) -> str: + return date.strftime("%B %d, %Y") + + +def calculate_percentages(populated_count: int, null_count: int) -> Tuple[float, float]: + total_fields = populated_count + null_count + populated_percentage = (populated_count / total_fields) * 100 + null_percentage = (null_count / total_fields) * 100 + return populated_percentage, null_percentage + + +def present_percentage(percentage: float) -> str: + rounded_percentage = round(percentage, 2) + formatted_string = ( + f"{rounded_percentage:05.2f}%" if rounded_percentage != 0 else "00.00%" + ) + return formatted_string diff --git a/nad_ch/controllers/web/routes.py b/nad_ch/controllers/web/routes.py index 1d5d5aa..3265882 100644 --- a/nad_ch/controllers/web/routes.py +++ b/nad_ch/controllers/web/routes.py @@ -1,4 +1,8 @@ from flask import Blueprint, current_app, render_template, g +from nad_ch.application.use_cases import ( + list_data_submissions_by_provider, + get_data_submission, +) home_bp = Blueprint("home", __name__) @@ -16,4 +20,12 @@ def home(): @home_bp.route("/reports") def reports(): - return render_template("reports.html") + # For demo purposes, hard-code the provider name + view_model = list_data_submissions_by_provider(g.ctx, "NJ") + return render_template("reports/index.html", submissions=view_model) + + +@home_bp.route("/reports/") +def view_report(submission_id): + view_model = get_data_submission(g.ctx, submission_id) + return render_template("reports/show.html", submission=view_model) diff --git a/nad_ch/controllers/web/templates/layout/base.html b/nad_ch/controllers/web/templates/_layouts/base.html similarity index 79% rename from nad_ch/controllers/web/templates/layout/base.html rename to nad_ch/controllers/web/templates/_layouts/base.html index 3e2a79e..e64c693 100644 --- a/nad_ch/controllers/web/templates/layout/base.html +++ b/nad_ch/controllers/web/templates/_layouts/base.html @@ -1,13 +1,13 @@ - {% include "layout/head.html" with context %} + {% include "_layouts/head.html" with context %} {% block title %}{% endblock %} - NAD Collaboration Hub
-
{% include "layout/sidebar.html" %}
+
{% include "_layouts/sidebar.html" %}
diff --git a/nad_ch/controllers/web/templates/layout/head.html b/nad_ch/controllers/web/templates/_layouts/head.html similarity index 100% rename from nad_ch/controllers/web/templates/layout/head.html rename to nad_ch/controllers/web/templates/_layouts/head.html diff --git a/nad_ch/controllers/web/templates/layout/sidebar.html b/nad_ch/controllers/web/templates/_layouts/sidebar.html similarity index 100% rename from nad_ch/controllers/web/templates/layout/sidebar.html rename to nad_ch/controllers/web/templates/_layouts/sidebar.html diff --git a/nad_ch/controllers/web/templates/index.html b/nad_ch/controllers/web/templates/index.html index d61afe3..cb2ac5f 100644 --- a/nad_ch/controllers/web/templates/index.html +++ b/nad_ch/controllers/web/templates/index.html @@ -1,4 +1,4 @@ -{% extends "layout/base.html" %} {% block title %}Home Page{% endblock %} {% +{% extends "_layouts/base.html" %} {% block title %}Home Page{% endblock %} {% block content %}

Welcome to the NAD Collaboration Hub

{% endblock %} diff --git a/nad_ch/controllers/web/templates/reports.html b/nad_ch/controllers/web/templates/reports.html deleted file mode 100644 index 76d04ca..0000000 --- a/nad_ch/controllers/web/templates/reports.html +++ /dev/null @@ -1,23 +0,0 @@ -{% extends "layout/base.html" %} {% block title %}Home Page{% endblock %} -{%block content %} -

Reports

- - - - - - - - - - - {# {% for sub in submissions %} #} - - - - - - {# {% endfor %} #} - -
NameDate CreatedView
My ReportJanuary 31, 2024View
-{% endblock %} diff --git a/nad_ch/controllers/web/templates/reports/index.html b/nad_ch/controllers/web/templates/reports/index.html new file mode 100644 index 0000000..58c14dd --- /dev/null +++ b/nad_ch/controllers/web/templates/reports/index.html @@ -0,0 +1,27 @@ +{% extends "_layouts/base.html" %} {% block title %}Home Page{% endblock %} +{%block content %} +

Reports

+ + + + + + + + + + {% for sub in submissions %} + + + + + + {% endfor %} + +
NameDate CreatedView
{{ sub.filename }}{{ sub.date_created }} + View +
+{% endblock %} diff --git a/nad_ch/controllers/web/templates/reports/show.html b/nad_ch/controllers/web/templates/reports/show.html new file mode 100644 index 0000000..c87b903 --- /dev/null +++ b/nad_ch/controllers/web/templates/reports/show.html @@ -0,0 +1,10 @@ +{% extends "_layouts/base.html" %} {% block title %}Report{% endblock %} {%block +content %} +

Report

+

Fancy dynamic table coming soon!

+ +{% endblock %} diff --git a/nad_ch/domain/entities.py b/nad_ch/domain/entities.py index 94cccbb..6b768b0 100644 --- a/nad_ch/domain/entities.py +++ b/nad_ch/domain/entities.py @@ -6,13 +6,13 @@ class Entity: def __init__(self, id: int = None): self.id = id - self.created_at = None - self.updated_at = None + self.created_at: datetime = None + self.updated_at: datetime = None - def set_created_at(self, created_at): + def set_created_at(self, created_at: datetime): self.created_at = created_at - def set_updated_at(self, updated_at): + def set_updated_at(self, updated_at: datetime): self.updated_at = updated_at diff --git a/tests/application/test_use_cases.py b/tests/application/test_use_cases.py index ab87313..47c2234 100644 --- a/tests/application/test_use_cases.py +++ b/tests/application/test_use_cases.py @@ -1,15 +1,19 @@ import pytest import re from nad_ch.application.dtos import DataSubmissionReport, DataSubmissionReportOverview -from nad_ch.config import create_app_context -from nad_ch.domain.entities import DataProvider, DataSubmission -from nad_ch.domain.repositories import DataSubmissionRepository from nad_ch.application.use_cases import ( add_data_provider, list_data_providers, ingest_data_submission, + list_data_submissions_by_provider, validate_data_submission, ) +from nad_ch.application.view_models import ( + DataProviderViewModel, + DataSubmissionViewModel, +) +from nad_ch.config import create_app_context +from nad_ch.domain.repositories import DataSubmissionRepository @pytest.fixture(scope="function") @@ -18,13 +22,27 @@ def app_context(): yield context -def test_add_data_provider(app_context): +@pytest.fixture +def validate_date_format(): + def is_valid_date_format(date_str: str) -> bool: + """ + Verify that a given string matches the following format: + 'January 1, 2024' + """ + pattern = r"^\w+\s+\d{2},\s+\d{4}$" + match = re.match(pattern, date_str) + return bool(match) + + return is_valid_date_format + + +def test_add_data_provider(app_context, validate_date_format): name = "State X" - add_data_provider(app_context, name) - provider = app_context.providers.get_by_name(name) - assert provider.name == name - assert isinstance(provider, DataProvider) is True + result = add_data_provider(app_context, name) + + assert isinstance(result, DataProviderViewModel) + assert validate_date_format(result.date_created) def test_add_data_provider_logs_error_if_no_provider_name_given(mocker): @@ -45,10 +63,10 @@ def test_list_a_single_data_provider(app_context): name = "State X" add_data_provider(app_context, name) - providers = list_data_providers(app_context) + result = list_data_providers(app_context) - assert len(providers) == 1 - assert providers[0].name == name + assert len(result) == 1 + assert isinstance(result[0], DataProviderViewModel) def test_list_multiple_data_providers(app_context): @@ -58,10 +76,10 @@ def test_list_multiple_data_providers(app_context): second_name = "State Y" add_data_provider(app_context, second_name) - providers = list_data_providers(app_context) - assert len(providers) == 2 - assert providers[0].name == first_name - assert providers[1].name == second_name + result = list_data_providers(app_context) + assert len(result) == 2 + assert isinstance(result[0], DataProviderViewModel) + assert isinstance(result[1], DataProviderViewModel) def test_ingest_data_submission(app_context): @@ -69,10 +87,10 @@ def test_ingest_data_submission(app_context): add_data_provider(app_context, provider_name) filename = "my_cool_file.zip" - ingest_data_submission(app_context, filename, provider_name) - submission = app_context.submissions.get_by_id(1) - assert isinstance(submission, DataSubmission) is True + result = ingest_data_submission(app_context, filename, provider_name) + + assert isinstance(result, DataSubmissionViewModel) def test_list_data_submissions_by_provider(app_context): @@ -82,9 +100,10 @@ def test_list_data_submissions_by_provider(app_context): filename = "my_cool_file.zip" ingest_data_submission(app_context, filename, provider_name) - provider = app_context.providers.get_by_name(provider_name) - submissions = app_context.submissions.get_by_provider(provider) - assert len(submissions) == 1 + result = list_data_submissions_by_provider(app_context, provider_name) + + assert len(result) == 1 + assert isinstance(result[0], DataSubmissionViewModel) def test_validate_data_submission(app_context, caplog): diff --git a/tests/application/test_view_models.py b/tests/application/test_view_models.py new file mode 100644 index 0000000..11692b9 --- /dev/null +++ b/tests/application/test_view_models.py @@ -0,0 +1,67 @@ +from datetime import datetime +from nad_ch.application.view_models import ( + get_view_model, + DataProviderViewModel, + DataSubmissionViewModel, +) +from nad_ch.domain.entities import DataProvider, DataSubmission + + +def test_get_a_single_data_provider_view_model(): + provider = DataProvider("State X") + date = datetime(2024, 2, 29, 20, 48, 58, 205608) + provider.set_created_at(date) + + result = get_view_model(provider) + + assert isinstance(result, DataProviderViewModel) + assert result.date_created == "February 29, 2024" + + +def test_get_a_list_of_data_provider_view_models(): + provider_a = DataProvider("State A") + date_a = datetime(2024, 2, 29, 20, 48, 58, 205608) + provider_a.set_created_at(date_a) + + provider_b = DataProvider("State B") + date_b = datetime(2024, 5, 1, 20, 48, 58, 205608) + provider_b.set_created_at(date_b) + + result = get_view_model([provider_a, provider_b]) + + assert len(result) == 2 + assert result[0].date_created == "February 29, 2024" + assert result[1].date_created == "May 01, 2024" + + +def test_get_a_single_data_submisson_view_model(): + provider = DataProvider("State X") + submission = DataSubmission("some_file_name", provider) + date = datetime(2024, 2, 29, 20, 48, 58, 205608) + submission.set_created_at(date) + + result = get_view_model(submission) + + assert isinstance(result, DataSubmissionViewModel) + assert result.date_created == "February 29, 2024" + assert result.provider_name == "State X" + + +def test_get_a_list_of_data_submisson_view_models(): + provider_a = DataProvider("State A") + submission_a = DataSubmission("some_file_name", provider_a) + date_a = datetime(2024, 2, 29, 20, 48, 58, 205608) + submission_a.set_created_at(date_a) + + provider_b = DataProvider("State B") + submission_b = DataSubmission("some_other_file_name", provider_b) + date_b = datetime(2024, 5, 1, 20, 48, 58, 205608) + submission_b.set_created_at(date_b) + + result = get_view_model([submission_a, submission_b]) + + assert len(result) == 2 + assert result[0].date_created == "February 29, 2024" + assert result[0].provider_name == "State A" + assert result[1].date_created == "May 01, 2024" + assert result[1].provider_name == "State B" diff --git a/tests/fakes_and_mocks.py b/tests/fakes_and_mocks.py index d71c103..b29aa9b 100644 --- a/tests/fakes_and_mocks.py +++ b/tests/fakes_and_mocks.py @@ -1,3 +1,4 @@ +from datetime import datetime from typing import Optional from nad_ch.application.dtos import DownloadResult from nad_ch.domain.entities import DataProvider, DataSubmission @@ -11,6 +12,7 @@ def __init__(self) -> None: def add(self, provider: DataProvider) -> DataProvider: provider.id = self._next_id + provider.set_created_at(datetime.now()) self._providers.add(provider) self._next_id += 1 return provider @@ -29,6 +31,7 @@ def __init__(self) -> None: def add(self, submission: DataSubmission) -> DataSubmission: submission.id = self._next_id + submission.set_created_at(datetime.now()) self._submissions.add(submission) self._next_id += 1 return submission From bcf588e63730f30d66537bba9ec7de69bfbac8e5 Mon Sep 17 00:00:00 2001 From: Andy Kuny Date: Fri, 9 Feb 2024 13:51:26 -0500 Subject: [PATCH 2/6] Remove placeholder --- nad_ch/controllers/web/templates/reports/show.html | 6 ------ 1 file changed, 6 deletions(-) diff --git a/nad_ch/controllers/web/templates/reports/show.html b/nad_ch/controllers/web/templates/reports/show.html index c87b903..b7519aa 100644 --- a/nad_ch/controllers/web/templates/reports/show.html +++ b/nad_ch/controllers/web/templates/reports/show.html @@ -1,10 +1,4 @@ {% extends "_layouts/base.html" %} {% block title %}Report{% endblock %} {%block content %}

Report

-

Fancy dynamic table coming soon!

- {% endblock %} From f4ac01347db5fb9d4c548a2cadf49abb7fa0133b Mon Sep 17 00:00:00 2001 From: Andy Kuny Date: Mon, 12 Feb 2024 08:59:57 -0500 Subject: [PATCH 3/6] Update rounding logic for completion percentages --- nad_ch/application/view_models.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/nad_ch/application/view_models.py b/nad_ch/application/view_models.py index 79a1c6d..54ab113 100644 --- a/nad_ch/application/view_models.py +++ b/nad_ch/application/view_models.py @@ -1,6 +1,7 @@ from dataclasses import dataclass from datetime import datetime import json +import numpy as np from typing import Union, List, Tuple from nad_ch.domain.entities import Entity, DataProvider, DataSubmission @@ -93,7 +94,7 @@ def calculate_percentages(populated_count: int, null_count: int) -> Tuple[float, def present_percentage(percentage: float) -> str: - rounded_percentage = round(percentage, 2) + rounded_percentage = np.around(percentage, 2) formatted_string = ( f"{rounded_percentage:05.2f}%" if rounded_percentage != 0 else "00.00%" ) From 388d02c4ef3f784cc8babd70a7d9e816df79c57d Mon Sep 17 00:00:00 2001 From: Andy Kuny Date: Mon, 12 Feb 2024 09:03:28 -0500 Subject: [PATCH 4/6] Shift fixture to helper function --- tests/application/test_use_cases.py | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/tests/application/test_use_cases.py b/tests/application/test_use_cases.py index 47c2234..0b7731f 100644 --- a/tests/application/test_use_cases.py +++ b/tests/application/test_use_cases.py @@ -22,27 +22,23 @@ def app_context(): yield context -@pytest.fixture -def validate_date_format(): - def is_valid_date_format(date_str: str) -> bool: - """ - Verify that a given string matches the following format: - 'January 1, 2024' - """ - pattern = r"^\w+\s+\d{2},\s+\d{4}$" - match = re.match(pattern, date_str) - return bool(match) +def is_valid_date_format(date_str: str) -> bool: + """ + Verify that a given string matches the following format: + 'January 1, 2024' + """ + pattern = r"^\w+\s+\d{2},\s+\d{4}$" + match = re.match(pattern, date_str) + return bool(match) - return is_valid_date_format - -def test_add_data_provider(app_context, validate_date_format): +def test_add_data_provider(app_context): name = "State X" result = add_data_provider(app_context, name) assert isinstance(result, DataProviderViewModel) - assert validate_date_format(result.date_created) + assert is_valid_date_format(result.date_created) def test_add_data_provider_logs_error_if_no_provider_name_given(mocker): From 1c6a5518d319b2e427e6e65ebd344a94b49c5148 Mon Sep 17 00:00:00 2001 From: Andy Kuny Date: Mon, 12 Feb 2024 09:04:39 -0500 Subject: [PATCH 5/6] Add assertions to use case tests --- tests/application/test_use_cases.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/application/test_use_cases.py b/tests/application/test_use_cases.py index 0b7731f..2bc7f4d 100644 --- a/tests/application/test_use_cases.py +++ b/tests/application/test_use_cases.py @@ -63,6 +63,7 @@ def test_list_a_single_data_provider(app_context): assert len(result) == 1 assert isinstance(result[0], DataProviderViewModel) + assert result[0].name == name def test_list_multiple_data_providers(app_context): @@ -75,7 +76,9 @@ def test_list_multiple_data_providers(app_context): result = list_data_providers(app_context) assert len(result) == 2 assert isinstance(result[0], DataProviderViewModel) + assert result[0].name == first_name assert isinstance(result[1], DataProviderViewModel) + assert result[1].name == second_name def test_ingest_data_submission(app_context): From e691a57739418b6a0a55542ae4a5c653cf3cac17 Mon Sep 17 00:00:00 2001 From: Andy Kuny Date: Mon, 12 Feb 2024 09:23:37 -0500 Subject: [PATCH 6/6] Add basic tests for flask routes --- nad_ch/application/use_cases.py | 3 ++ .../web/templates/reports/index.html | 5 ++- .../web/templates/reports/show.html | 7 +++- tests/controllers/__init__.py | 0 tests/controllers/test_web.py | 34 +++++++++++++++++++ 5 files changed, 47 insertions(+), 2 deletions(-) create mode 100644 tests/controllers/__init__.py create mode 100644 tests/controllers/test_web.py diff --git a/nad_ch/application/use_cases.py b/nad_ch/application/use_cases.py index 43c8ce3..5c95062 100644 --- a/nad_ch/application/use_cases.py +++ b/nad_ch/application/use_cases.py @@ -74,6 +74,9 @@ def get_data_submission( ) -> DataSubmissionViewModel: submission = ctx.submissions.get_by_id(submission_id) + if submission is None: + return None + return get_view_model(submission) diff --git a/nad_ch/controllers/web/templates/reports/index.html b/nad_ch/controllers/web/templates/reports/index.html index 58c14dd..3926527 100644 --- a/nad_ch/controllers/web/templates/reports/index.html +++ b/nad_ch/controllers/web/templates/reports/index.html @@ -1,6 +1,7 @@ {% extends "_layouts/base.html" %} {% block title %}Home Page{% endblock %} {%block content %}

Reports

+{% if submissions %} @@ -24,4 +25,6 @@

Reports

{% endfor %}
-{% endblock %} +{% else %} +

You haven't uploaded any data submissions yet.

+{% endif %} {% endblock %} diff --git a/nad_ch/controllers/web/templates/reports/show.html b/nad_ch/controllers/web/templates/reports/show.html index b7519aa..53805ab 100644 --- a/nad_ch/controllers/web/templates/reports/show.html +++ b/nad_ch/controllers/web/templates/reports/show.html @@ -1,4 +1,9 @@ {% extends "_layouts/base.html" %} {% block title %}Report{% endblock %} {%block content %}

Report

-{% endblock %} +{% if submission %} +

{{ submission.filename }}

+

{{ submission.date_created}}

+{% else %} +

No such submission exists.

+{% endif %} {% endblock %} diff --git a/tests/controllers/__init__.py b/tests/controllers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/controllers/test_web.py b/tests/controllers/test_web.py new file mode 100644 index 0000000..90dd72c --- /dev/null +++ b/tests/controllers/test_web.py @@ -0,0 +1,34 @@ +import pytest +from nad_ch.config import create_app_context +from nad_ch.controllers.web.flask import create_flask_application + + +@pytest.fixture +def app(): + context = create_app_context() + app = create_flask_application(context) + yield app + + +@pytest.fixture +def client(app): + return app.test_client() + + +def test_home_route(client): + response = client.get("/") + assert response.status_code == 200 + assert "Welcome" in response.data.decode("utf-8") + + +def test_reports_route(client): + response = client.get("/reports") + assert response.status_code == 200 + assert "Reports" in response.data.decode("utf-8") + + +def test_view_report_route(client): + submission_id = "some_valid_id" + response = client.get(f"/reports/{submission_id}") + assert response.status_code == 200 + assert "Report" in response.data.decode("utf-8")