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

1#!/usr/bin/env python 

2 

3""" 

4camcops_server/cc_modules/tests/cc_report_tests.py 

5 

6=============================================================================== 

7 

8 Copyright (C) 2012, University of Cambridge, Department of Psychiatry. 

9 Created by Rudolf Cardinal (rnc1001@cam.ac.uk). 

10 

11 This file is part of CamCOPS. 

12 

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. 

17 

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. 

22 

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/>. 

25 

26=============================================================================== 

27 

28""" 

29 

30import logging 

31from typing import Generator, Optional, TYPE_CHECKING 

32 

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 

46 

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) 

61 

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 ) 

72 

73log = BraceStyleAdapter(logging.getLogger(__name__)) 

74 

75 

76# ============================================================================= 

77# Unit testing 

78# ============================================================================= 

79 

80 

81class AllReportTests(DemoDatabaseTestCase): 

82 """ 

83 Unit tests. 

84 """ 

85 

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 ) 

91 

92 req = self.req 

93 for cls in get_all_report_classes(req): 

94 log.info("Testing report: {}", cls) 

95 

96 report = cls() 

97 

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) 

102 

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) 

115 

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 

128 

129 try: 

130 self.assertIsInstanceOrNone( 

131 report.get_rows_colnames(req), PlainReportType 

132 ) 

133 except HTTPBadRequest: 

134 pass 

135 

136 cls = report.get_paramform_schema_class() 

137 assert issubclass(cls, ReportParamSchema) 

138 

139 self.assertIsInstance(report.get_form(req), Form) 

140 

141 try: 

142 self.assertIsInstance(report.get_response(req), Response) 

143 except HTTPBadRequest: 

144 pass 

145 

146 

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() 

153 

154 def setUp(self) -> None: 

155 super().setUp() 

156 

157 self.report = self.create_report() 

158 

159 def create_report(self) -> AverageScoreReport: 

160 raise NotImplementedError( 

161 "Report TestCase needs to implement create_report" 

162 ) 

163 

164 @staticmethod 

165 def get_patient_id() -> Generator[int, None, None]: 

166 i = 1 

167 

168 while True: 

169 yield i 

170 i += 1 

171 

172 @staticmethod 

173 def get_task_id() -> Generator[int, None, None]: 

174 i = 1 

175 

176 while True: 

177 yield i 

178 i += 1 

179 

180 @staticmethod 

181 def get_patient_idnum_id() -> Generator[int, None, None]: 

182 i = 1 

183 

184 while True: 

185 yield i 

186 i += 1 

187 

188 def create_patient(self, idnum_value: int = 333) -> "Patient": 

189 from camcops_server.cc_modules.cc_patient import Patient 

190 

191 patient = Patient() 

192 patient.id = next(self.patient_id_sequence) 

193 self.apply_standard_db_fields(patient) 

194 

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) 

199 

200 self.create_patient_idnum(patient, idnum_value) 

201 

202 self.dbsession.commit() 

203 

204 return patient 

205 

206 def create_patient_idnum( 

207 self, patient, idnum_value: int = 333 

208 ) -> "PatientIdNum": 

209 from camcops_server.cc_modules.cc_patient import PatientIdNum 

210 

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) 

218 

219 return patient_idnum 

220 

221 

222class TestReport(Report): 

223 # noinspection PyMethodParameters 

224 @classproperty 

225 def report_id(cls) -> str: 

226 return "test_report" 

227 

228 @classmethod 

229 def title(cls, req: "CamcopsRequest") -> str: 

230 return "Test report" 

231 

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 ] 

240 

241 column_names = ["column 1", "column 2", "column 3"] 

242 

243 return PlainReportType(rows=rows, column_names=column_names) 

244 

245 

246class ReportSpreadsheetTests(DemoRequestTestCase): 

247 def test_render_xlsx(self) -> None: 

248 report = TestReport() 

249 

250 response = report.render_xlsx(self.req) 

251 self.assertIsInstance(response, XlsxResponse) 

252 

253 self.assertIn( 

254 "filename=CamCOPS_test_report", response.content_disposition 

255 ) 

256 

257 self.assertIn(".xlsx", response.content_disposition) 

258 

259 def test_render_ods(self) -> None: 

260 report = TestReport() 

261 

262 response = report.render_ods(self.req) 

263 self.assertIsInstance(response, OdsResponse) 

264 

265 self.assertIn( 

266 "filename=CamCOPS_test_report", response.content_disposition 

267 ) 

268 

269 self.assertIn(".ods", response.content_disposition) 

270 

271 def test_render_tsv(self) -> None: 

272 report = TestReport() 

273 

274 response = report.render_tsv(self.req) 

275 self.assertIsInstance(response, TsvResponse) 

276 

277 self.assertIn( 

278 "filename=CamCOPS_test_report", response.content_disposition 

279 ) 

280 

281 self.assertIn(".tsv", response.content_disposition) 

282 

283 import csv 

284 import io 

285 

286 reader = csv.reader( 

287 io.StringIO(response.body.decode()), dialect="excel-tab" 

288 ) 

289 

290 headings = next(reader) 

291 row_1 = next(reader) 

292 

293 self.assertEqual(headings, ["column 1", "column 2", "column 3"]) 

294 self.assertEqual(row_1, ["one", "two", "three"])