Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1#!/usr/bin/env python 

2 

3""" 

4camcops_server/cc_modules/tests/cc_report_tests.py 

5 

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

7 

8 Copyright (C) 2012-2020 Rudolf Cardinal (rudolf@pobox.com). 

9 

10 This file is part of CamCOPS. 

11 

12 CamCOPS is free software: you can redistribute it and/or modify 

13 it under the terms of the GNU General Public License as published by 

14 the Free Software Foundation, either version 3 of the License, or 

15 (at your option) any later version. 

16 

17 CamCOPS is distributed in the hope that it will be useful, 

18 but WITHOUT ANY WARRANTY; without even the implied warranty of 

19 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

20 GNU General Public License for more details. 

21 

22 You should have received a copy of the GNU General Public License 

23 along with CamCOPS. If not, see <https://www.gnu.org/licenses/>. 

24 

25=============================================================================== 

26 

27""" 

28 

29import logging 

30from typing import Generator, Optional, TYPE_CHECKING 

31 

32from cardinal_pythonlib.classes import classproperty 

33from cardinal_pythonlib.logs import BraceStyleAdapter 

34from cardinal_pythonlib.pyramid.responses import ( 

35 OdsResponse, TsvResponse, XlsxResponse, 

36) 

37from deform.form import Form 

38import pendulum 

39from pyramid.httpexceptions import HTTPBadRequest 

40from pyramid.response import Response 

41from sqlalchemy.orm.query import Query 

42from sqlalchemy.sql.selectable import SelectBase 

43 

44from camcops_server.cc_modules.cc_report import ( 

45 AverageScoreReport, 

46 get_all_report_classes, 

47 PlainReportType, 

48 Report, 

49) 

50from camcops_server.cc_modules.cc_unittest import ( 

51 DemoDatabaseTestCase, 

52 DemoRequestTestCase, 

53) 

54from camcops_server.cc_modules.cc_validators import ( 

55 validate_alphanum_underscore, 

56) 

57 

58if TYPE_CHECKING: 

59 from camcops_server.cc_modules.cc_forms import ( # noqa: F401 

60 ReportParamForm, 

61 ReportParamSchema, 

62 ) 

63 from camcops_server.cc_modules.cc_patient import Patient 

64 from camcops_server.cc_modules.cc_patientidnum import PatientIdNum 

65 from camcops_server.cc_modules.cc_request import CamcopsRequest # noqa: E501,F401 

66 

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

68 

69 

70# ============================================================================= 

71# Unit testing 

72# ============================================================================= 

73 

74class AllReportTests(DemoDatabaseTestCase): 

75 """ 

76 Unit tests. 

77 """ 

78 def test_reports(self) -> None: 

79 self.announce("test_reports") 

80 req = self.req 

81 for cls in get_all_report_classes(req): 

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

83 from camcops_server.cc_modules.cc_forms import ReportParamSchema # noqa 

84 report = cls() 

85 

86 self.assertIsInstance(report.report_id, str) 

87 validate_alphanum_underscore(report.report_id) 

88 self.assertIsInstance(report.title(req), str) 

89 self.assertIsInstance(report.superuser_only, bool) 

90 

91 querydict = report.get_test_querydict() 

92 # We can't use req.params.update(querydict); we get 

93 # "NestedMultiDict objects are read-only". We can't replace 

94 # req.params ("can't set attribute"). Making a fresh request is 

95 # also a pain, as they are difficult to initialize properly. 

96 # However, achievable with some hacking to make "params" writable; 

97 # see CamcopsDummyRequest. 

98 # Also: we must use self.req as this has the correct database 

99 # session. 

100 req = self.req 

101 req.clear_get_params() # as we're re-using old requests 

102 req.add_get_params(querydict) 

103 

104 try: 

105 q = report.get_query(req) 

106 assert (q is None or 

107 isinstance(q, SelectBase) or 

108 isinstance(q, Query)), ( 

109 f"get_query() method of class {cls} returned {q} which is " 

110 f"of type {type(q)}" 

111 ) 

112 except HTTPBadRequest: 

113 pass 

114 

115 try: 

116 self.assertIsInstanceOrNone( 

117 report.get_rows_colnames(req), PlainReportType) 

118 except HTTPBadRequest: 

119 pass 

120 

121 cls = report.get_paramform_schema_class() 

122 assert issubclass(cls, ReportParamSchema) 

123 

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

125 

126 try: 

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

128 except HTTPBadRequest: 

129 pass 

130 

131 

132class AverageScoreReportTestCase(DemoDatabaseTestCase): 

133 def __init__(self, *args, **kwargs) -> None: 

134 super().__init__(*args, **kwargs) 

135 self.patient_id_sequence = self.get_patient_id() 

136 self.task_id_sequence = self.get_task_id() 

137 self.patient_idnum_id_sequence = self.get_patient_idnum_id() 

138 

139 def setUp(self) -> None: 

140 super().setUp() 

141 

142 self.report = self.create_report() 

143 

144 def create_tasks(self): 

145 pass 

146 

147 def create_report(self) -> AverageScoreReport: 

148 raise NotImplementedError( 

149 "Report TestCase needs to implement create_report" 

150 ) 

151 

152 @staticmethod 

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

154 i = 1 

155 

156 while True: 

157 yield i 

158 i += 1 

159 

160 @staticmethod 

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

162 i = 1 

163 

164 while True: 

165 yield i 

166 i += 1 

167 

168 @staticmethod 

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

170 i = 1 

171 

172 while True: 

173 yield i 

174 i += 1 

175 

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

177 from camcops_server.cc_modules.cc_patient import Patient 

178 patient = Patient() 

179 patient.id = next(self.patient_id_sequence) 

180 self._apply_standard_db_fields(patient) 

181 

182 patient.forename = f"Forename {patient.id}" 

183 patient.surname = f"Surname {patient.id}" 

184 patient.dob = pendulum.parse("1950-01-01") 

185 self.dbsession.add(patient) 

186 

187 self.create_patient_idnum(patient, idnum_value) 

188 

189 self.dbsession.commit() 

190 

191 return patient 

192 

193 def create_patient_idnum(self, patient, 

194 idnum_value: int = 333) -> "PatientIdNum": 

195 from camcops_server.cc_modules.cc_patient import PatientIdNum 

196 patient_idnum = PatientIdNum() 

197 patient_idnum.id = next(self.patient_idnum_id_sequence) 

198 self._apply_standard_db_fields(patient_idnum) 

199 patient_idnum.patient_id = patient.id 

200 patient_idnum.which_idnum = self.nhs_iddef.which_idnum 

201 patient_idnum.idnum_value = idnum_value 

202 self.dbsession.add(patient_idnum) 

203 

204 return patient_idnum 

205 

206 

207class TestReport(Report): 

208 # noinspection PyMethodParameters 

209 @classproperty 

210 def report_id(cls) -> str: 

211 return "test_report" 

212 

213 @classmethod 

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

215 return "Test report" 

216 

217 def get_rows_colnames(self, req: "CamcopsRequest") -> Optional[ 

218 PlainReportType]: 

219 rows = [ 

220 ["one", "two", "three"], 

221 ["eleven", "twelve", "thirteen"], 

222 ["twenty-one", "twenty-two", "twenty-three"], 

223 ] 

224 

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

226 

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

228 

229 

230class ReportSpreadsheetTests(DemoRequestTestCase): 

231 def test_render_xlsx(self) -> None: 

232 report = TestReport() 

233 

234 response = report.render_xlsx(self.req) 

235 self.assertIsInstance(response, XlsxResponse) 

236 

237 self.assertIn( 

238 "filename=CamCOPS_test_report", 

239 response.content_disposition 

240 ) 

241 

242 self.assertIn( 

243 ".xlsx", response.content_disposition 

244 ) 

245 

246 def test_render_ods(self) -> None: 

247 report = TestReport() 

248 

249 response = report.render_ods(self.req) 

250 self.assertIsInstance(response, OdsResponse) 

251 

252 self.assertIn( 

253 "filename=CamCOPS_test_report", 

254 response.content_disposition 

255 ) 

256 

257 self.assertIn( 

258 ".ods", response.content_disposition 

259 ) 

260 

261 def test_render_tsv(self) -> None: 

262 report = TestReport() 

263 

264 response = report.render_tsv(self.req) 

265 self.assertIsInstance(response, TsvResponse) 

266 

267 self.assertIn( 

268 "filename=CamCOPS_test_report", 

269 response.content_disposition 

270 ) 

271 

272 self.assertIn( 

273 ".tsv", response.content_disposition 

274 ) 

275 

276 import csv 

277 import io 

278 reader = csv.reader(io.StringIO(response.body.decode()), 

279 dialect="excel-tab") 

280 

281 headings = next(reader) 

282 row_1 = next(reader) 

283 

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

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