Coverage for cc_modules/tests/cc_report_tests.py: 30%
138 statements
« prev ^ index » next coverage.py v6.5.0, created at 2022-11-08 23:14 +0000
« prev ^ index » next coverage.py v6.5.0, created at 2022-11-08 23:14 +0000
1#!/usr/bin/env python
3"""
4camcops_server/cc_modules/tests/cc_report_tests.py
6===============================================================================
8 Copyright (C) 2012, University of Cambridge, Department of Psychiatry.
9 Created by Rudolf Cardinal (rnc1001@cam.ac.uk).
11 This file is part of CamCOPS.
13 CamCOPS is free software: you can redistribute it and/or modify
14 it under the terms of the GNU General Public License as published by
15 the Free Software Foundation, either version 3 of the License, or
16 (at your option) any later version.
18 CamCOPS is distributed in the hope that it will be useful,
19 but WITHOUT ANY WARRANTY; without even the implied warranty of
20 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 GNU General Public License for more details.
23 You should have received a copy of the GNU General Public License
24 along with CamCOPS. If not, see <https://www.gnu.org/licenses/>.
26===============================================================================
28"""
30import logging
31from typing import Generator, Optional, TYPE_CHECKING
33from cardinal_pythonlib.classes import classproperty
34from cardinal_pythonlib.logs import BraceStyleAdapter
35from cardinal_pythonlib.pyramid.responses import (
36 OdsResponse,
37 TsvResponse,
38 XlsxResponse,
39)
40from deform.form import Form
41import pendulum
42from pyramid.httpexceptions import HTTPBadRequest
43from pyramid.response import Response
44from sqlalchemy.orm.query import Query
45from sqlalchemy.sql.selectable import SelectBase
47from camcops_server.cc_modules.cc_report import (
48 AverageScoreReport,
49 get_all_report_classes,
50 PlainReportType,
51 Report,
52)
53from camcops_server.cc_modules.cc_unittest import (
54 BasicDatabaseTestCase,
55 DemoDatabaseTestCase,
56 DemoRequestTestCase,
57)
58from camcops_server.cc_modules.cc_validators import (
59 validate_alphanum_underscore,
60)
62if TYPE_CHECKING:
63 from camcops_server.cc_modules.cc_forms import ( # noqa: F401
64 ReportParamForm,
65 ReportParamSchema,
66 )
67 from camcops_server.cc_modules.cc_patient import Patient
68 from camcops_server.cc_modules.cc_patientidnum import PatientIdNum
69 from camcops_server.cc_modules.cc_request import ( # noqa: F401
70 CamcopsRequest,
71 )
73log = BraceStyleAdapter(logging.getLogger(__name__))
76# =============================================================================
77# Unit testing
78# =============================================================================
81class AllReportTests(DemoDatabaseTestCase):
82 """
83 Unit tests.
84 """
86 def test_reports(self) -> None:
87 self.announce("test_reports")
88 from camcops_server.cc_modules.cc_forms import ( # noqa: F811
89 ReportParamSchema,
90 )
92 req = self.req
93 for cls in get_all_report_classes(req):
94 log.info("Testing report: {}", cls)
96 report = cls()
98 self.assertIsInstance(report.report_id, str)
99 validate_alphanum_underscore(report.report_id)
100 self.assertIsInstance(report.title(req), str)
101 self.assertIsInstance(report.superuser_only, bool)
103 querydict = report.get_test_querydict()
104 # We can't use req.params.update(querydict); we get
105 # "NestedMultiDict objects are read-only". We can't replace
106 # req.params ("can't set attribute"). Making a fresh request is
107 # also a pain, as they are difficult to initialize properly.
108 # However, achievable with some hacking to make "params" writable;
109 # see CamcopsDummyRequest.
110 # Also: we must use self.req as this has the correct database
111 # session.
112 req = self.req
113 req.clear_get_params() # as we're re-using old requests
114 req.add_get_params(querydict)
116 try:
117 q = report.get_query(req)
118 assert (
119 q is None
120 or isinstance(q, SelectBase)
121 or isinstance(q, Query)
122 ), (
123 f"get_query() method of class {cls} returned {q} which is "
124 f"of type {type(q)}"
125 )
126 except HTTPBadRequest:
127 pass
129 try:
130 self.assertIsInstanceOrNone(
131 report.get_rows_colnames(req), PlainReportType
132 )
133 except HTTPBadRequest:
134 pass
136 cls = report.get_paramform_schema_class()
137 assert issubclass(cls, ReportParamSchema)
139 self.assertIsInstance(report.get_form(req), Form)
141 try:
142 self.assertIsInstance(report.get_response(req), Response)
143 except HTTPBadRequest:
144 pass
147class AverageScoreReportTestCase(BasicDatabaseTestCase):
148 def __init__(self, *args, **kwargs) -> None:
149 super().__init__(*args, **kwargs)
150 self.patient_id_sequence = self.get_patient_id()
151 self.task_id_sequence = self.get_task_id()
152 self.patient_idnum_id_sequence = self.get_patient_idnum_id()
154 def setUp(self) -> None:
155 super().setUp()
157 self.report = self.create_report()
159 def create_report(self) -> AverageScoreReport:
160 raise NotImplementedError(
161 "Report TestCase needs to implement create_report"
162 )
164 @staticmethod
165 def get_patient_id() -> Generator[int, None, None]:
166 i = 1
168 while True:
169 yield i
170 i += 1
172 @staticmethod
173 def get_task_id() -> Generator[int, None, None]:
174 i = 1
176 while True:
177 yield i
178 i += 1
180 @staticmethod
181 def get_patient_idnum_id() -> Generator[int, None, None]:
182 i = 1
184 while True:
185 yield i
186 i += 1
188 def create_patient(self, idnum_value: int = 333) -> "Patient":
189 from camcops_server.cc_modules.cc_patient import Patient
191 patient = Patient()
192 patient.id = next(self.patient_id_sequence)
193 self.apply_standard_db_fields(patient)
195 patient.forename = f"Forename {patient.id}"
196 patient.surname = f"Surname {patient.id}"
197 patient.dob = pendulum.parse("1950-01-01")
198 self.dbsession.add(patient)
200 self.create_patient_idnum(patient, idnum_value)
202 self.dbsession.commit()
204 return patient
206 def create_patient_idnum(
207 self, patient, idnum_value: int = 333
208 ) -> "PatientIdNum":
209 from camcops_server.cc_modules.cc_patient import PatientIdNum
211 patient_idnum = PatientIdNum()
212 patient_idnum.id = next(self.patient_idnum_id_sequence)
213 self.apply_standard_db_fields(patient_idnum)
214 patient_idnum.patient_id = patient.id
215 patient_idnum.which_idnum = self.nhs_iddef.which_idnum
216 patient_idnum.idnum_value = idnum_value
217 self.dbsession.add(patient_idnum)
219 return patient_idnum
222class TestReport(Report):
223 # noinspection PyMethodParameters
224 @classproperty
225 def report_id(cls) -> str:
226 return "test_report"
228 @classmethod
229 def title(cls, req: "CamcopsRequest") -> str:
230 return "Test report"
232 def get_rows_colnames(
233 self, req: "CamcopsRequest"
234 ) -> Optional[PlainReportType]:
235 rows = [
236 ["one", "two", "three"],
237 ["eleven", "twelve", "thirteen"],
238 ["twenty-one", "twenty-two", "twenty-three"],
239 ]
241 column_names = ["column 1", "column 2", "column 3"]
243 return PlainReportType(rows=rows, column_names=column_names)
246class ReportSpreadsheetTests(DemoRequestTestCase):
247 def test_render_xlsx(self) -> None:
248 report = TestReport()
250 response = report.render_xlsx(self.req)
251 self.assertIsInstance(response, XlsxResponse)
253 self.assertIn(
254 "filename=CamCOPS_test_report", response.content_disposition
255 )
257 self.assertIn(".xlsx", response.content_disposition)
259 def test_render_ods(self) -> None:
260 report = TestReport()
262 response = report.render_ods(self.req)
263 self.assertIsInstance(response, OdsResponse)
265 self.assertIn(
266 "filename=CamCOPS_test_report", response.content_disposition
267 )
269 self.assertIn(".ods", response.content_disposition)
271 def test_render_tsv(self) -> None:
272 report = TestReport()
274 response = report.render_tsv(self.req)
275 self.assertIsInstance(response, TsvResponse)
277 self.assertIn(
278 "filename=CamCOPS_test_report", response.content_disposition
279 )
281 self.assertIn(".tsv", response.content_disposition)
283 import csv
284 import io
286 reader = csv.reader(
287 io.StringIO(response.body.decode()), dialect="excel-tab"
288 )
290 headings = next(reader)
291 row_1 = next(reader)
293 self.assertEqual(headings, ["column 1", "column 2", "column 3"])
294 self.assertEqual(row_1, ["one", "two", "three"])