Coverage for cc_modules/tests/cc_taskreports_tests.py: 99%

1"""

2 camcops_server/cc_modules/tests/cc_tasksreports_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**Test server reports on CamCOPS tasks.**

27

28"""

29

30 from typing import Optional

31

32 from camcops_server.cc_modules.cc_pyramid import ViewParam

33 from camcops_server.cc_modules.cc_taskindex import TaskIndexEntry

34 from camcops_server.cc_modules.cc_taskreports import TaskCountReport

35 from camcops_server.cc_modules.cc_testfactories import (

36 GroupFactory,

37 PatientFactory,

38 UserFactory,

39 UserGroupMembershipFactory,

40)

41 from camcops_server.cc_modules.cc_unittest import BasicDatabaseTestCase

42 from camcops_server.tasks.tests.factories import BmiFactory, Phq9Factory

43

44 from pendulum import DateTime as Pendulum, datetime

45

46

47 class TaskCountReportTestBase(BasicDatabaseTestCase):

48 # pytest will collect tests that are derived from unitest.TestCase

49 # regardless of what python_classes says in pytest.ini so we need to set

50 # this to stop tests being run in the baseclass and override in the derived

51 # class.

52 __test__ = False

53

54 def setUp(self) -> None:

55 super().setUp()

56

57 self.date_01_oct_2022 = datetime(2022, 10, 1, 12)

58 self.date_01_nov_2022 = datetime(2022, 11, 1, 12)

59 self.date_30_nov_2022 = datetime(2022, 11, 30, 12)

60 self.date_01_jan_2023 = datetime(2023, 1, 1, 12)

61

62 self.num_01_nov_2022_phq9_tasks = 4

63 self.num_30_nov_2022_phq9_tasks = 5

64 self.num_01_oct_2022_bmi_tasks = 2

65 self.num_01_nov_2022_bmi_tasks = 3

66 self.num_01_jan_2023_bmi_tasks = 1

67

68 # Freda and Jim are both members of Group A. Freda took over from Jim

69 # in Nov 2022

70 self.group_a = GroupFactory(name="Group A")

71 self.group_b = GroupFactory(name="Group B")

72

73 self.freda = UserFactory(username="freda")

74 self.jim = UserFactory(username="jim")

75

76 UserGroupMembershipFactory(

77 group_id=self.group_a.id,

78 user_id=self.freda.id,

79 may_run_reports=True,

80 )

81 UserGroupMembershipFactory(

82 group_id=self.group_a.id, user_id=self.jim.id, may_run_reports=True

83 )

84

85 self.num_jim_tasks = self.num_01_oct_2022_bmi_tasks = 2

86 self.num_freda_tasks = (

87 self.num_01_nov_2022_phq9_tasks

88 + self.num_30_nov_2022_phq9_tasks

89 + self.num_01_nov_2022_bmi_tasks

90 + self.num_01_jan_2023_bmi_tasks

91 )

92

93 self.num_group_a_tasks = self.num_jim_tasks + self.num_freda_tasks

94

95 all_tasks = []

96

97 for i in range(0, self.num_01_oct_2022_bmi_tasks):

98 patient = PatientFactory(

99 _when_added_exact=self.date_01_oct_2022,

100 )

101 all_tasks.append(

102 BmiFactory(

103 _group=self.group_a,

104 patient_id=patient.id,

105 when_created=self.date_01_oct_2022,

106 _when_added_exact=self.date_01_oct_2022,

107 _adding_user=self.jim,

108 )

109 )

110

111 for i in range(0, self.num_01_nov_2022_bmi_tasks):

112 patient = PatientFactory(

113 _when_added_exact=self.date_01_nov_2022,

114 )

115 all_tasks.append(

116 BmiFactory(

117 _group=self.group_a,

118 patient_id=patient.id,

119 when_created=self.date_01_nov_2022,

120 _when_added_exact=self.date_01_nov_2022,

121 _adding_user=self.freda,

122 )

123 )

124

125 for i in range(0, self.num_01_nov_2022_phq9_tasks):

126 patient = PatientFactory(

127 _when_added_exact=self.date_01_nov_2022,

128 )

129 all_tasks.append(

130 Phq9Factory(

131 _group=self.group_a,

132 patient_id=patient.id,

133 when_created=self.date_01_nov_2022,

134 _when_added_exact=self.date_01_nov_2022,

135 _adding_user=self.freda,

136 )

137 )

138

139 for i in range(0, self.num_30_nov_2022_phq9_tasks):

140 patient = PatientFactory(

141 _when_added_exact=self.date_30_nov_2022,

142 )

143 all_tasks.append(

144 Phq9Factory(

145 _group=self.group_a,

146 patient_id=patient.id,

147 when_created=self.date_30_nov_2022,

148 _when_added_exact=self.date_30_nov_2022,

149 _adding_user=self.freda,

150 )

151 )

152

153 for i in range(0, self.num_01_jan_2023_bmi_tasks):

154 patient = PatientFactory(

155 _when_added_exact=self.date_01_jan_2023,

156 )

157 all_tasks.append(

158 BmiFactory(

159 _group=self.group_a,

160 patient_id=patient.id,

161 when_created=self.date_01_jan_2023,

162 _when_added_exact=self.date_01_jan_2023,

163 _adding_user=self.freda,

164 )

165 )

166

167 # A task in another group, which won't be seen by group A

168 self.shabeen = UserFactory(username="shabeen")

169

170 UserGroupMembershipFactory(

171 group_id=self.group_b.id, user_id=self.shabeen.id

172 )

173

174 patient = PatientFactory(

175 _when_added_exact=self.date_01_jan_2023,

176 )

177

178 all_tasks.append(

179 BmiFactory(

180 _group=self.group_b,

181 patient_id=patient.id,

182 _when_added_exact=self.date_01_jan_2023,

183 _adding_user=self.shabeen,

184 )

185 )

186 self.num_group_b_tasks = 1

187

188 if self.via_index:

189 # There might be a better way of doing this automatically in

190 # TaskFactory though in the real world indexing is a manual

191 # process.

192 for task in all_tasks:

193 TaskIndexEntry.index_task(

194 task, self.dbsession, indexed_at_utc=Pendulum.utcnow()

195 )

196

197 self.report = TaskCountReport()

198

199 @property

200 def via_index(self) -> Optional[bool]:

201 raise NotImplementedError("via_index must return True or False")

202

203 def test_task_counts_by_year_and_month(self) -> None:

204 year_column = 0

205 month_column = 1

206 task_column = 2

207 num_tasks_added_column = 3

208

209 num_nov_2022_phq9_tasks = (

210 self.num_01_nov_2022_phq9_tasks + self.num_30_nov_2022_phq9_tasks

211 )

212

213 # Default is by year and month but better to be explicit

214 self.req.add_get_params(

215 {

216 ViewParam.BY_YEAR: "true",

217 ViewParam.BY_MONTH: "true",

218 ViewParam.VIA_INDEX: "true" if self.via_index else "false",

219 }

220 )

221

222 self.req._debugging_user = self.freda

223 result = self.report.get_rows_colnames(self.req)

224 self.assertEqual(

225 result.column_names,

226 [

227 "year",

228 "month",

229 "task",

230 "num_tasks_added",

231 ],

232 )

233

234 row = 0

235

236 self.assertEqual(result.rows[row][year_column], 2023)

237 self.assertEqual(result.rows[row][month_column], 1)

238 self.assertEqual(result.rows[row][task_column], "bmi")

239 self.assertEqual(

240 result.rows[row][num_tasks_added_column],

241 self.num_01_jan_2023_bmi_tasks,

242 )

243

244 row += 1

245

246 self.assertEqual(result.rows[row][year_column], 2022)

247 self.assertEqual(result.rows[row][month_column], 11)

248 self.assertEqual(result.rows[row][task_column], "bmi")

249 self.assertEqual(

250 result.rows[row][num_tasks_added_column],

251 self.num_01_nov_2022_bmi_tasks,

252 )

253

254 row += 1

255

256 self.assertEqual(result.rows[row][year_column], 2022)

257 self.assertEqual(result.rows[row][month_column], 11)

258 self.assertEqual(result.rows[row][task_column], "phq9")

259 self.assertEqual(

260 result.rows[row][num_tasks_added_column],

261 num_nov_2022_phq9_tasks,

262 )

263

264 row += 1

265

266 self.assertEqual(result.rows[row][year_column], 2022)

267 self.assertEqual(result.rows[row][month_column], 10)

268 self.assertEqual(result.rows[row][task_column], "bmi")

269 self.assertEqual(

270 result.rows[row][num_tasks_added_column],

271 self.num_01_oct_2022_bmi_tasks,

272 )

273

274 row += 1

275

276 self.assertEqual(len(result.rows), row)

277

278 def test_task_counts_by_year(self) -> None:

279 num_2022_bmi_tasks = (

280 self.num_01_oct_2022_bmi_tasks + self.num_01_nov_2022_bmi_tasks

281 )

282 num_2022_phq9_tasks = (

283 self.num_01_nov_2022_phq9_tasks + self.num_30_nov_2022_phq9_tasks

284 )

285 num_2023_bmi_tasks = self.num_01_jan_2023_bmi_tasks

286

287 year_column = 0

288 task_column = 1

289 num_tasks_added_column = 2

290

291 self.req.add_get_params(

292 {

293 ViewParam.BY_YEAR: "true",

294 ViewParam.BY_MONTH: "false",

295 ViewParam.VIA_INDEX: "true" if self.via_index else "false",

296 }

297 )

298

299 self.req._debugging_user = self.freda

300 result = self.report.get_rows_colnames(self.req)

301 self.assertEqual(

302 result.column_names,

303 [

304 "year",

305 "task",

306 "num_tasks_added",

307 ],

308 )

309

310 row = 0

311

312 self.assertEqual(result.rows[row][year_column], 2023)

313 self.assertEqual(result.rows[row][task_column], "bmi")

314 self.assertEqual(

315 result.rows[row][num_tasks_added_column], num_2023_bmi_tasks

316 )

317

318 row += 1

319

320 self.assertEqual(result.rows[row][year_column], 2022)

321 self.assertEqual(result.rows[row][task_column], "bmi")

322 self.assertEqual(

323 result.rows[row][num_tasks_added_column], num_2022_bmi_tasks

324 )

325

326 row += 1

327

328 self.assertEqual(result.rows[row][year_column], 2022)

329 self.assertEqual(result.rows[row][task_column], "phq9")

330 self.assertEqual(

331 result.rows[row][num_tasks_added_column], num_2022_phq9_tasks

332 )

333

334 row += 1

335

336 self.assertEqual(len(result.rows), row)

337

338 def test_task_counts_by_task(self) -> None:

339 num_bmi_tasks = (

340 self.num_01_oct_2022_bmi_tasks

341 + self.num_01_nov_2022_bmi_tasks

342 + self.num_01_jan_2023_bmi_tasks

343 )

344 num_phq9_tasks = (

345 self.num_01_nov_2022_phq9_tasks + self.num_30_nov_2022_phq9_tasks

346 )

347

348 task_column = 0

349 num_tasks_added_column = 1

350

351 self.req.add_get_params(

352 {

353 ViewParam.BY_YEAR: "false",

354 ViewParam.BY_MONTH: "false",

355 ViewParam.BY_TASK: "true",

356 ViewParam.VIA_INDEX: "true" if self.via_index else "false",

357 }

358 )

359

360 self.req._debugging_user = self.freda

361

362 result = self.report.get_rows_colnames(self.req)

363 self.assertEqual(

364 result.column_names,

365 [

366 "task",

367 "num_tasks_added",

368 ],

369 )

370

371 row = 0

372

373 self.assertEqual(result.rows[row][task_column], "bmi")

374 self.assertEqual(

375 result.rows[row][num_tasks_added_column], num_bmi_tasks

376 )

377

378 row += 1

379

380 self.assertEqual(result.rows[row][task_column], "phq9")

381 self.assertEqual(

382 result.rows[row][num_tasks_added_column], num_phq9_tasks

383 )

384

385 row += 1

386

387 self.assertEqual(len(result.rows), row)

388

389 def test_task_counts_by_user(self) -> None:

390 user_column = 0

391 num_tasks_added_column = 1

392

393 self.req.add_get_params(

394 {

395 ViewParam.BY_YEAR: "false",

396 ViewParam.BY_MONTH: "false",

397 ViewParam.BY_TASK: "false",

398 ViewParam.BY_USER: "true",

399 ViewParam.VIA_INDEX: "true" if self.via_index else "false",

400 }

401 )

402

403 self.req._debugging_user = self.freda

404 result = self.report.get_rows_colnames(self.req)

405 self.assertEqual(

406 result.column_names,

407 [

408 "adding_user_name",

409 "num_tasks_added",

410 ],

411 )

412

413 row = 0

414

415 self.assertEqual(result.rows[row][user_column], "freda")

416 self.assertEqual(

417 result.rows[row][num_tasks_added_column], self.num_freda_tasks

418 )

419

420 row += 1

421

422 self.assertEqual(result.rows[row][user_column], "jim")

423 self.assertEqual(

424 result.rows[row][num_tasks_added_column], self.num_jim_tasks

425 )

426

427 row += 1

428

429 self.assertEqual(len(result.rows), row)

430

431 def test_total_task_count_for_superuser(self) -> None:

432 num_tasks_added_column = 0

433

434 self.req.add_get_params(

435 {

436 ViewParam.BY_YEAR: "false",

437 ViewParam.BY_MONTH: "false",

438 ViewParam.BY_TASK: "false",

439 ViewParam.BY_USER: "false",

440 ViewParam.VIA_INDEX: "true" if self.via_index else "false",

441 }

442 )

443

444 result = self.report.get_rows_colnames(self.req)

445 self.assertEqual(

446 result.column_names,

447 [

448 "num_tasks_added",

449 ],

450 )

451

452 row = 0

453

454 total_tasks = self.num_group_a_tasks + self.num_group_b_tasks

455

456 self.assertEqual(result.rows[row][num_tasks_added_column], total_tasks)

457

458 row += 1

459

460 self.assertEqual(len(result.rows), row)

461

462 def test_task_counts_by_day_of_month(self) -> None:

463 # Not a very realistic scenario. Would normally

464 # be combined with month and year but these are

465 # covered in other tests.

466 num_day_01_tasks = (

467 self.num_01_oct_2022_bmi_tasks

468 + self.num_01_nov_2022_bmi_tasks

469 + self.num_01_nov_2022_phq9_tasks

470 + self.num_01_jan_2023_bmi_tasks

471 )

472 num_day_30_tasks = self.num_30_nov_2022_phq9_tasks

473

474 day_of_month_column = 0

475 num_tasks_added_column = 1

476

477 self.req.add_get_params(

478 {

479 ViewParam.BY_YEAR: "false",

480 ViewParam.BY_MONTH: "false",

481 ViewParam.BY_DAY_OF_MONTH: "true",

482 ViewParam.BY_TASK: "false",

483 ViewParam.VIA_INDEX: "true" if self.via_index else "false",

484 }

485 )

486

487 self.req._debugging_user = self.freda

488 result = self.report.get_rows_colnames(self.req)

489 self.assertEqual(

490 result.column_names,

491 [

492 "day_of_month",

493 "num_tasks_added",

494 ],

495 )

496

497 row = 0

498

499 self.assertEqual(result.rows[row][day_of_month_column], 30)

500 self.assertEqual(

501 result.rows[row][num_tasks_added_column], num_day_30_tasks

502 )

503

504 row += 1

505

506 self.assertEqual(result.rows[row][day_of_month_column], 1)

507 self.assertEqual(

508 result.rows[row][num_tasks_added_column], num_day_01_tasks

509 )

510

511 row += 1

512

513 self.assertEqual(len(result.rows), row)

514

515

516 class TaskCountReportNoIndexTests(TaskCountReportTestBase):

517 __test__ = True

518

519 @property

520 def via_index(self) -> Optional[bool]:

521 return False

522

523

524 class TaskCountReportWithIndexTests(TaskCountReportTestBase):

525 __test__ = True

526

527 @property

528 def via_index(self) -> Optional[bool]:

529 return True