Coverage for cc_modules/tests/cc_report_tests.py: 34%

86 statements  

« prev     ^ index     » next       coverage.py v7.6.10, created at 2025-01-30 13:48 +0000

1""" 

2camcops_server/cc_modules/tests/cc_report_tests.py 

3 

4=============================================================================== 

5 

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

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

8 

9 This file is part of CamCOPS. 

10 

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

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

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

14 (at your option) any later version. 

15 

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

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

18 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

19 GNU General Public License for more details. 

20 

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

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

23 

24=============================================================================== 

25 

26""" 

27 

28import csv 

29import io 

30import logging 

31from typing import 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 

41from pyramid.httpexceptions import HTTPBadRequest 

42from pyramid.response import Response 

43from sqlalchemy.orm.query import Query 

44from sqlalchemy.sql.selectable import SelectBase 

45 

46from camcops_server.cc_modules.cc_report import ( 

47 get_all_report_classes, 

48 PlainReportType, 

49 Report, 

50) 

51from camcops_server.cc_modules.cc_unittest import ( 

52 DemoDatabaseTestCase, 

53 DemoRequestTestCase, 

54) 

55from camcops_server.cc_modules.cc_validators import ( 

56 validate_alphanum_underscore, 

57) 

58 

59if TYPE_CHECKING: 

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

61 ReportParamForm, 

62 ReportParamSchema, 

63 ) 

64 from camcops_server.cc_modules.cc_request import ( 

65 CamcopsRequest, 

66 ) 

67 

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

69 

70 

71# ============================================================================= 

72# Unit testing 

73# ============================================================================= 

74 

75 

76class AllReportTests(DemoDatabaseTestCase): 

77 """ 

78 Unit tests. 

79 """ 

80 

81 def test_reports(self) -> None: 

82 self.announce("test_reports") 

83 from camcops_server.cc_modules.cc_forms import ( # noqa: F811 

84 ReportParamSchema, 

85 ) 

86 

87 req = self.req 

88 for cls in get_all_report_classes(req): 

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

90 

91 report = cls() 

92 

93 self.assertIsInstance(report.report_id, str) 

94 validate_alphanum_underscore(report.report_id) 

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

96 self.assertIsInstance(report.superuser_only, bool) 

97 

98 querydict = report.get_test_querydict() 

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

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

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

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

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

104 # see CamcopsDummyRequest. 

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

106 # session. 

107 req = self.req 

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

109 req.add_get_params(querydict) 

110 

111 try: 

112 q = report.get_query(req) 

113 assert ( 

114 q is None 

115 or isinstance(q, SelectBase) 

116 or isinstance(q, Query) 

117 ), ( 

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

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

120 ) 

121 except HTTPBadRequest: 

122 pass 

123 

124 try: 

125 self.assertIsInstanceOrNone( 

126 report.get_rows_colnames(req), PlainReportType 

127 ) 

128 except HTTPBadRequest: 

129 pass 

130 

131 cls = report.get_paramform_schema_class() 

132 assert issubclass(cls, ReportParamSchema) 

133 

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

135 

136 try: 

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

138 except HTTPBadRequest: 

139 pass 

140 

141 

142class TestReport(Report): 

143 # noinspection PyMethodParameters 

144 @classproperty 

145 def report_id(cls) -> str: 

146 return "test_report" 

147 

148 @classmethod 

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

150 return "Test report" 

151 

152 def get_rows_colnames( 

153 self, req: "CamcopsRequest" 

154 ) -> Optional[PlainReportType]: 

155 rows = [ 

156 ["one", "two", "three"], 

157 ["eleven", "twelve", "thirteen"], 

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

159 ] 

160 

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

162 

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

164 

165 

166class ReportSpreadsheetTests(DemoRequestTestCase): 

167 def test_render_xlsx(self) -> None: 

168 report = TestReport() 

169 

170 response = report.render_xlsx(self.req) 

171 self.assertIsInstance(response, XlsxResponse) 

172 

173 self.assertIn( 

174 "filename=CamCOPS_test_report", response.content_disposition 

175 ) 

176 

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

178 

179 def test_render_ods(self) -> None: 

180 report = TestReport() 

181 

182 response = report.render_ods(self.req) 

183 self.assertIsInstance(response, OdsResponse) 

184 

185 self.assertIn( 

186 "filename=CamCOPS_test_report", response.content_disposition 

187 ) 

188 

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

190 

191 def test_render_tsv(self) -> None: 

192 report = TestReport() 

193 

194 response = report.render_tsv(self.req) 

195 self.assertIsInstance(response, TsvResponse) 

196 

197 self.assertIn( 

198 "filename=CamCOPS_test_report", response.content_disposition 

199 ) 

200 

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

202 

203 reader = csv.reader( 

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

205 ) 

206 

207 headings = next(reader) 

208 row_1 = next(reader) 

209 

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

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