#!/usr/bin/env python
"""
camcops_server/cc_modules/tests/cc_report_tests.py
===============================================================================
Copyright (C) 2012, University of Cambridge, Department of Psychiatry.
Created by Rudolf Cardinal (rnc1001@cam.ac.uk).
This file is part of CamCOPS.
CamCOPS is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
CamCOPS is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with CamCOPS. If not, see <https://www.gnu.org/licenses/>.
===============================================================================
"""
import logging
from typing import Generator, Optional, TYPE_CHECKING
from cardinal_pythonlib.classes import classproperty
from cardinal_pythonlib.logs import BraceStyleAdapter
from cardinal_pythonlib.pyramid.responses import (
OdsResponse,
TsvResponse,
XlsxResponse,
)
from deform.form import Form
import pendulum
from pyramid.httpexceptions import HTTPBadRequest
from pyramid.response import Response
from sqlalchemy.orm.query import Query
from sqlalchemy.sql.selectable import SelectBase
from camcops_server.cc_modules.cc_report import (
AverageScoreReport,
get_all_report_classes,
PlainReportType,
Report,
)
from camcops_server.cc_modules.cc_unittest import (
BasicDatabaseTestCase,
DemoDatabaseTestCase,
DemoRequestTestCase,
)
from camcops_server.cc_modules.cc_validators import (
validate_alphanum_underscore,
)
if TYPE_CHECKING:
from camcops_server.cc_modules.cc_forms import ( # noqa: F401
ReportParamForm,
ReportParamSchema,
)
from camcops_server.cc_modules.cc_patient import Patient
from camcops_server.cc_modules.cc_patientidnum import PatientIdNum
from camcops_server.cc_modules.cc_request import ( # noqa: F401
CamcopsRequest,
)
log = BraceStyleAdapter(logging.getLogger(__name__))
# =============================================================================
# Unit testing
# =============================================================================
[docs]class AllReportTests(DemoDatabaseTestCase):
"""
Unit tests.
"""
def test_reports(self) -> None:
self.announce("test_reports")
from camcops_server.cc_modules.cc_forms import ( # noqa: F811
ReportParamSchema,
)
req = self.req
for cls in get_all_report_classes(req):
log.info("Testing report: {}", cls)
report = cls()
self.assertIsInstance(report.report_id, str)
validate_alphanum_underscore(report.report_id)
self.assertIsInstance(report.title(req), str)
self.assertIsInstance(report.superuser_only, bool)
querydict = report.get_test_querydict()
# We can't use req.params.update(querydict); we get
# "NestedMultiDict objects are read-only". We can't replace
# req.params ("can't set attribute"). Making a fresh request is
# also a pain, as they are difficult to initialize properly.
# However, achievable with some hacking to make "params" writable;
# see CamcopsDummyRequest.
# Also: we must use self.req as this has the correct database
# session.
req = self.req
req.clear_get_params() # as we're re-using old requests
req.add_get_params(querydict)
try:
q = report.get_query(req)
assert (
q is None
or isinstance(q, SelectBase)
or isinstance(q, Query)
), (
f"get_query() method of class {cls} returned {q} which is "
f"of type {type(q)}"
)
except HTTPBadRequest:
pass
try:
self.assertIsInstanceOrNone(
report.get_rows_colnames(req), PlainReportType
)
except HTTPBadRequest:
pass
cls = report.get_paramform_schema_class()
assert issubclass(cls, ReportParamSchema)
self.assertIsInstance(report.get_form(req), Form)
try:
self.assertIsInstance(report.get_response(req), Response)
except HTTPBadRequest:
pass
[docs]class AverageScoreReportTestCase(BasicDatabaseTestCase):
[docs] def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
self.patient_id_sequence = self.get_patient_id()
self.task_id_sequence = self.get_task_id()
self.patient_idnum_id_sequence = self.get_patient_idnum_id()
[docs] def setUp(self) -> None:
super().setUp()
self.report = self.create_report()
def create_report(self) -> AverageScoreReport:
raise NotImplementedError(
"Report TestCase needs to implement create_report"
)
@staticmethod
def get_patient_id() -> Generator[int, None, None]:
i = 1
while True:
yield i
i += 1
@staticmethod
def get_task_id() -> Generator[int, None, None]:
i = 1
while True:
yield i
i += 1
@staticmethod
def get_patient_idnum_id() -> Generator[int, None, None]:
i = 1
while True:
yield i
i += 1
def create_patient(self, idnum_value: int = 333) -> "Patient":
from camcops_server.cc_modules.cc_patient import Patient
patient = Patient()
patient.id = next(self.patient_id_sequence)
self.apply_standard_db_fields(patient)
patient.forename = f"Forename {patient.id}"
patient.surname = f"Surname {patient.id}"
patient.dob = pendulum.parse("1950-01-01")
self.dbsession.add(patient)
self.create_patient_idnum(patient, idnum_value)
self.dbsession.commit()
return patient
def create_patient_idnum(
self, patient, idnum_value: int = 333
) -> "PatientIdNum":
from camcops_server.cc_modules.cc_patient import PatientIdNum
patient_idnum = PatientIdNum()
patient_idnum.id = next(self.patient_idnum_id_sequence)
self.apply_standard_db_fields(patient_idnum)
patient_idnum.patient_id = patient.id
patient_idnum.which_idnum = self.nhs_iddef.which_idnum
patient_idnum.idnum_value = idnum_value
self.dbsession.add(patient_idnum)
return patient_idnum
[docs]class TestReport(Report):
# noinspection PyMethodParameters
@classproperty
def report_id(cls) -> str:
return "test_report"
@classmethod
def title(cls, req: "CamcopsRequest") -> str:
return "Test report"
[docs] def get_rows_colnames(
self, req: "CamcopsRequest"
) -> Optional[PlainReportType]:
rows = [
["one", "two", "three"],
["eleven", "twelve", "thirteen"],
["twenty-one", "twenty-two", "twenty-three"],
]
column_names = ["column 1", "column 2", "column 3"]
return PlainReportType(rows=rows, column_names=column_names)
[docs]class ReportSpreadsheetTests(DemoRequestTestCase):
def test_render_xlsx(self) -> None:
report = TestReport()
response = report.render_xlsx(self.req)
self.assertIsInstance(response, XlsxResponse)
self.assertIn(
"filename=CamCOPS_test_report", response.content_disposition
)
self.assertIn(".xlsx", response.content_disposition)
def test_render_ods(self) -> None:
report = TestReport()
response = report.render_ods(self.req)
self.assertIsInstance(response, OdsResponse)
self.assertIn(
"filename=CamCOPS_test_report", response.content_disposition
)
self.assertIn(".ods", response.content_disposition)
def test_render_tsv(self) -> None:
report = TestReport()
response = report.render_tsv(self.req)
self.assertIsInstance(response, TsvResponse)
self.assertIn(
"filename=CamCOPS_test_report", response.content_disposition
)
self.assertIn(".tsv", response.content_disposition)
import csv
import io
reader = csv.reader(
io.StringIO(response.body.decode()), dialect="excel-tab"
)
headings = next(reader)
row_1 = next(reader)
self.assertEqual(headings, ["column 1", "column 2", "column 3"])
self.assertEqual(row_1, ["one", "two", "three"])