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

1"""

2 camcops_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

28 import csv

29 import io

30 import logging

31 from typing import Optional, TYPE_CHECKING

32

33 from cardinal_pythonlib.classes import classproperty

34 from cardinal_pythonlib.logs import BraceStyleAdapter

35 from cardinal_pythonlib.pyramid.responses import (

36 OdsResponse,

37 TsvResponse,

38 XlsxResponse,

39)

40 from deform.form import Form

41 from pyramid.httpexceptions import HTTPBadRequest

42 from pyramid.response import Response

43 from sqlalchemy.orm.query import Query

44 from sqlalchemy.sql.selectable import SelectBase

45

46 from camcops_server.cc_modules.cc_report import (

47 get_all_report_classes,

48 PlainReportType,

49 Report,

50)

51 from camcops_server.cc_modules.cc_unittest import (

52 DemoDatabaseTestCase,

53 DemoRequestTestCase,

54)

55 from camcops_server.cc_modules.cc_validators import (

56 validate_alphanum_underscore,

57)

58

59 if 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

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

69

70

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

72# Unit testing

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

74

75

76 class 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

142 class 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

166 class 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"])