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"""Higher level assert functions test helpers. 

2 

3This module contains a bunch of `assertXXX` functions, 

4which all have a client as first argument and an expected outcome as last. 

5They perform higher level tasks for a single client and can be conveniently be 

6used in iterations using `helpers.forall`. 

7 

8There are also some functions that are even higher level, and have been 

9factored out from concrete test functions. 

10""" 

11 

12from control.utils import pick as G, serverprint, E 

13from conftest import USERS 

14from example import ( 

15 ASSESS, 

16 CAPTIONS, 

17 EDITOR, 

18 EDITORS, 

19 REVIEW, 

20 REVIEW_DECISION, 

21 START_ASSESSMENT, 

22 START_REVIEW, 

23 TITLE, 

24 TITLE1, 

25 TITLE2, 

26 UNDEF_VALUE, 

27 USER, 

28 USER_COUNTRY, 

29) 

30from helpers import ( 

31 accessUrl, 

32 findCaptions, 

33 findMsg, 

34 findEid, 

35 findMainN, 

36 findStages, 

37 forall, 

38 getEid, 

39 getItem, 

40 modifyField, 

41 shiftDate, 

42) 

43 

44 

45def assertAddItem(client, table, expect): 

46 """Adds an item to a table. 

47 

48 The response texts will be analysed into messages and fields, the eid 

49 of the new item will be read off. 

50 

51 Parameters 

52 ---------- 

53 client: fixture 

54 table: string 

55 expect: boolean 

56 

57 Returns 

58 ------- 

59 eid: str(ObjectId) 

60 The id of the inserted item. 

61 """ 

62 

63 response = client.get(f"/api/{table}/insert", follow_redirects=True) 

64 text = response.get_data(as_text=True) 

65 msgs = findMsg(text) 

66 eid = findEid(text) 

67 if expect: 

68 assert "item added" in msgs 

69 else: 

70 assert "item added" not in msgs 

71 return eid 

72 

73 

74def assertCaptions(client, expect): 

75 """Check whether a response text shows a certain set of captions. 

76 

77 Parameters 

78 ---------- 

79 client: fixture 

80 expect: set of string 

81 """ 

82 

83 url = "/" 

84 (text, status, msgs) = accessUrl(client, url) 

85 captionsFound = {caption: url for (caption, url) in findCaptions(text)} 

86 for caption in captionsFound: 

87 assert caption in expect 

88 for caption in expect: 

89 assert caption in captionsFound 

90 for (caption, url) in captionsFound.items(): 

91 (expNumber, expItem) = expect[caption] 

92 serverprint(f"CAPTION {caption}: {client.user} CLICKS {url}") 

93 (text, status, msgs) = accessUrl(client, url) 

94 if expNumber is None: 

95 expItem in text 

96 else: 

97 (n, item) = findMainN(text)[0] 

98 nX = f"=/={expNumber}" if n != str(expNumber) else E 

99 iX = f"=/={expItem}" if item != expItem else E 

100 if iX or nX: 100 ↛ 101line 100 didn't jump to line 101, because the condition on line 100 was never true

101 serverprint(f"CAPTION {caption}: {n}{nX} {item}{iX}") 

102 assert n == str(expNumber) 

103 assert item == expItem 

104 

105 

106def assertDelItem(client, table, eid, expect): 

107 """Deletes an item from a table. 

108 

109 Parameters 

110 ---------- 

111 client: fixture 

112 table: string 

113 eid: string(ObjectId) 

114 expect: boolean 

115 """ 

116 

117 assertStatus(client, f"/api/{table}/delete/{eid}", expect) 

118 

119 

120def assertEditor(client, table, eid, valueTables, expect, clear=False): 

121 """Sets the `editors` of an item to **editor**, or clears the `editors` field. 

122 

123 Parameters 

124 ---------- 

125 table: string 

126 eid: string(ObjectId) 

127 valueTables: the store for the value tables 

128 expect: boolean 

129 clear: boolean, optional `False` 

130 If True, clears the editors field. 

131 """ 

132 

133 if clear: 

134 value = ([], "") 

135 else: 

136 users = valueTables[USER] 

137 editorId = users[EDITOR] 

138 value = ([editorId], EDITOR) 

139 assertModifyField(client, table, eid, EDITORS, value, expect) 

140 

141 

142def assertFieldValue(source, field, expect): 

143 """Verify whether a field has a certain expected value. 

144 

145 If we pass expect `None` we want to assert that the field is not present at all. 

146 

147 Parameters 

148 ---------- 

149 source: dict | (client: fixture, table: string, eid: string) 

150 The dictionary of fields and values of a retrieved response. 

151 If it is a tuple, the dictionary will be retrieved by looking up 

152 the item specified by `table` and `eid`. 

153 field: string 

154 The name of the specific field. 

155 expect: 

156 The expected value for this field. 

157 """ 

158 

159 if type(source) is tuple: 

160 (client, table, eid) = source 

161 info = getItem(client, table, eid) 

162 fields = info["fields"] 

163 else: 

164 fields = source 

165 

166 if expect is None: 

167 assert field not in fields 

168 else: 

169 assert field in fields 

170 value = fields[field] 

171 if value != expect: 171 ↛ 172line 171 didn't jump to line 172, because the condition on line 171 was never true

172 serverprint(f"FIELDVALUE {field}={value} (=/={expect})") 

173 assert expect == fields[field] 

174 

175 

176def assertModifyField(client, table, eid, field, newValue, expect): 

177 """Try to modify a field and check the outcome. 

178 

179 !!! note "Read access" 

180 The test has to reckon with the fact that the client may not even have 

181 read access to the field. 

182 

183 Parameters 

184 ---------- 

185 client: fixture 

186 table: string 

187 eid: ObjectId | string 

188 field: string 

189 newValue: string | tuple 

190 If a tuple, the first component is the modification value, 

191 and the second component is the value we read back from the modified record 

192 expect: boolean 

193 Whether we expect the modification to succeed 

194 """ 

195 

196 if not expect: 

197 info = getItem(client, table, eid) 

198 fields = info["fields"] 

199 oldValue = fields[field] if field in fields else None 

200 

201 if type(newValue) is tuple: 

202 (newValue, newValueRep) = newValue 

203 else: 

204 newValueRep = newValue 

205 

206 (text, fields) = modifyField(client, table, eid, field, newValue) 

207 

208 if not expect: 

209 assert field not in fields 

210 

211 info = getItem(client, table, eid) 

212 fields = info["fields"] 

213 

214 if expect: 

215 assertFieldValue(fields, field, newValueRep) 

216 else: 

217 if field in fields: 

218 assertFieldValue(fields, field, oldValue) 

219 

220 

221def assertReviewDecisions(clients, reviewId, kinds, decisions, expect): 

222 """Check whether the reviewers can take certain decisions. 

223 

224 You specify which reviewers take which decisions, and they will 

225 all be carried out in that order. 

226 

227 You specifiy the expected outcomes in a dict or a boolean, telling 

228 whether the taking of the decision is expected to succeed or not. 

229 

230 

231 Parameters 

232 ---------- 

233 clients: dict 

234 Mapping from users to client fixtures. 

235 reviewId: dict 

236 The review ids for the expert and final review 

237 kinds: list of {expert, final} 

238 At most one of each, the order is important. 

239 decisions: list of {Reject, Revise, Accept, Revoke} 

240 At most one of each, the order is important. 

241 expect: bool | dict 

242 Expected outcomes. 

243 If it is a boolean, that is the expected outcome of all actions by all 

244 reviewers. 

245 Otherwise the dict is keyed by kind of reviewer. 

246 The values are booleans or dicts. 

247 A boolean indicates the expected outcome of all actions for that reviewer. 

248 A dict specifies per action of that reviewer what the outcome is. 

249 """ 

250 

251 for kind in kinds: 

252 rId = G(reviewId, kind) 

253 expectKind = ( 

254 True if expect is True else False if expect is False else G(expect, kind) 

255 ) 

256 for decision in decisions: 

257 decisionStr = G(G(REVIEW_DECISION, decision), kind) 

258 url = f"/api/task/{decisionStr}/{rId}" 

259 exp = ( 

260 True 

261 if expectKind is True 

262 else False 

263 if expectKind is False 

264 else G(expectKind, decision) 

265 ) 

266 serverprint(f"REVIEW DECISION {decision} by {kind} expects {exp}") 

267 assertStatus(G(clients, kind), url, exp) 

268 

269 

270def assertShiftDate(clientSys, table, eid, field, amount): 

271 """Shifts the date in a field annd recomputes workflow. 

272 

273 See `helpers.shiftDate`. 

274 """ 

275 shiftDate(table, eid, field, amount) 

276 assertStatus(clientSys, "/workflow", True) 

277 

278 

279def assertStage(client, table, eid, expect): 

280 """Check whether a record has a certain workflow stage. 

281 

282 Parameters 

283 ---------- 

284 client: fixture 

285 table: string 

286 eid: ObjectId | string 

287 expect: string | set of string 

288 If a set, we expect one of the values in the set 

289 

290 Returns 

291 ------- 

292 dict 

293 The text, fields, msgs and stage of the record 

294 """ 

295 

296 info = getItem(client, table, eid) 

297 text = info["text"] 

298 stageFound = findStages(text)[0] 

299 info["stage"] = stageFound 

300 if type(expect) is set: 300 ↛ 301line 300 didn't jump to line 301, because the condition on line 300 was never true

301 assert stageFound in expect 

302 else: 

303 assert stageFound == expect 

304 return info 

305 

306 

307def assertStartTask(client, task, eid, expect): 

308 """Issues a start workflow command. 

309 

310 There are `startAssessment` and `startReview` tasks that create a record, 

311 and there are task that set a field in an existing recordd. 

312 

313 Tasks take as arguments the eid of a record in a table. 

314 

315 The response texts will be analysed into messages and fields. 

316 For start tasks, the new eid will be read off and returned, otherwise None is returned. 

317 

318 Parameters 

319 ---------- 

320 client: fixture 

321 eid: string(ObjectId) 

322 The id that is the argumenent for the workflow task. 

323 expect: boolean 

324 

325 Returns 

326 ------- 

327 eid: str(ObjectId) | `None` 

328 """ 

329 

330 table = ( 

331 ASSESS if task == START_ASSESSMENT else REVIEW if task == START_REVIEW else None 

332 ) 

333 assert table is not None 

334 assertStatus(client, f"/api/task/{task}/{eid}", expect) 

335 newEid = None 

336 if expect: 

337 newEid = getEid(client, table) 

338 

339 return newEid if task in {START_ASSESSMENT, START_REVIEW} else None 

340 

341 

342def assertStatus(client, url, expect): 

343 """Get data and see whether that went right or wrong. 

344 

345 Parameters 

346 ---------- 

347 client: function 

348 url: string(url) 

349 The url to retrieve from the server 

350 expect: boolean | int | set of int 

351 If boolean: Whether it is expected to be successful 

352 If int: status code should be exactly this 

353 If set of int: status code should be contained in this 

354 """ 

355 

356 try: 

357 response = client.get(url) 

358 code = response.status_code 

359 except Exception as e: 

360 serverprint(f"APPLICATION ERROR: {e}") 

361 code = 4000 

362 raise 

363 

364 if type(expect) is set: 

365 good = code in expect 

366 if not good: 366 ↛ 367line 366 didn't jump to line 367, because the condition on line 366 was never true

367 serverprint(f"STATUS {url} => {code} (not in {expect})") 

368 assert good 

369 elif type(expect) is int: 

370 good = code == expect 

371 if not good: 371 ↛ 372line 371 didn't jump to line 372, because the condition on line 371 was never true

372 serverprint(f"STATUS {url} => {code} (=/= {expect})") 

373 assert good 

374 else: 

375 codes = {200, 302} if expect else {400, 303} 

376 good = code in codes 

377 if not good: 377 ↛ 378line 377 didn't jump to line 378, because the condition on line 377 was never true

378 serverprint(f"STATUS {url} => {code} (not in {codes})") 

379 assert good 

380 

381 

382def assignReviewers(clients, users, aId, field, user, keep, expect): 

383 """Verify assigning reviewers to an assessment. 

384 

385 A reviewer will be assigned to an assessment and immediately be unassigned. 

386 But the undo can be suppressed. 

387 

388 Parameters 

389 ---------- 

390 clients: dict 

391 Mapping from users to client fixtures 

392 users: dict 

393 Mapping of users to ids 

394 aId: string(ObjectId) 

395 Assessment id 

396 field: string 

397 Reviewer field (`reviewerE` or `reviewerF`) 

398 user: string 

399 The reviewer user 

400 keep: boolean 

401 If True, the assignment will not be undone 

402 expect: dict 

403 For each user a boolean saying whether that user can assign the reviewer 

404 """ 

405 

406 value = G(users, user) 

407 

408 def assertIt(cl, exp): 

409 assertModifyField(cl, ASSESS, aId, field, (value, user), exp) 

410 if exp and not keep: 

411 assertModifyField(cl, ASSESS, aId, field, (None, UNDEF_VALUE), True) 

412 

413 forall(clients, expect, assertIt) 

414 

415 

416def illegalize(clients, url, **kwargs): 

417 """Append illegal/long arguments to an url and trigger a 400 response. 

418 

419 Parameters 

420 ---------- 

421 clients: dict 

422 Mapping from users to client fixtures 

423 kwargs: dict 

424 Additional parameters to illegalize. 

425 The url will be expanded by formatting it with the `kwargs` values. 

426 """ 

427 

428 kwargsx = {k: v + "a" * 200 for (k, v) in kwargs.items()} 

429 base = url.format(**kwargs) 

430 basex = url.format(**kwargsx) 

431 

432 uxs = [ 

433 base, 

434 basex, 

435 f"{base}?action=xxx", 

436 f"{base}?xxx=xxx", 

437 f"{base}?action=" + "a" * 200, 

438 f"{base}?" + "a" * 2000, 

439 ] 

440 

441 passable = {200, 301, 302, 303} 

442 for (i, ux) in enumerate(uxs): 

443 expectx = { 

444 user: 400 if i > 1 or i == 1 and len(kwargsx) else passable 

445 for user in USERS 

446 if user in clients 

447 } 

448 serverprint(f"LEGAL URL ? ({ux})") 

449 forall(clients, expectx, assertStatus, ux) 

450 

451 

452def isIllegal(clients, url): 

453 """Make sure that the ur triggers a 400 response for all clients. 

454 

455 Parameters 

456 ---------- 

457 clients: dict 

458 Mapping from users to client fixtures 

459 """ 

460 

461 expectx = {user: 400 for user in USERS if user in clients} 

462 serverprint(f"HACKED URL ! ({url})") 

463 forall(clients, expectx, assertStatus, url) 

464 

465 

466def inspectTitleAll(clients, table, eid, expect): 

467 """Verify the title of an item, as seen by each user. 

468 

469 Parameters 

470 ---------- 

471 clients: dict 

472 Mapping from users to client fixtures 

473 table: the table of the item 

474 eid: the id of the item 

475 expect: dict 

476 The expected values, keyed per user 

477 """ 

478 

479 def assertIt(cl, exp): 

480 assertFieldValue((cl, table, eid), TITLE, exp) 

481 

482 forall(clients, expect, assertIt) 

483 

484 

485def modifyTitleAll(clients, table, eid, expect): 

486 """Modify the title of an item, performed by each user. 

487 

488 Parameters 

489 ---------- 

490 clients: dict 

491 Mapping from users to client fixtures 

492 table: the table of the item 

493 eid: the id of the item 

494 expect: dict 

495 The expected values, keyed per user 

496 """ 

497 

498 def assertIt(cl, exp): 

499 assertModifyField(cl, table, eid, TITLE, TITLE2, exp) 

500 if exp: 

501 assertModifyField(cl, table, eid, TITLE, TITLE1, exp) 

502 

503 forall(clients, expect, assertIt) 

504 

505 

506def sidebar(clients, amounts): 

507 """Verify the sidebar. 

508 

509 It will be verified whether each user sees the right entries, 

510 and that following an entry leads to the expected results. 

511 

512 Parameters 

513 ---------- 

514 clients: dict 

515 Mapping from users to client fixtures 

516 amounts: dict 

517 Keyed by entry, it is a list of instructions to change the expected amount. 

518 Each instruction is a pair `(set of users, amount)`, leading 

519 to setting the indicated amount for the indicated users. 

520 The set of users can be left out, then all users are implied. 

521 """ 

522 

523 expectedCaptions = {} 

524 for (caption, expectedUsers, expectedN, expectedItemSg, expectedItemPl) in CAPTIONS: 

525 for user in expectedUsers: 

526 thisCaption = ( 

527 caption.format(country=USER_COUNTRY[user]) 

528 if "{country}" in caption 

529 else caption 

530 ) 

531 n = expectedN 

532 for instruction in G(amounts, thisCaption, default=[]): 

533 if type(instruction) is tuple or type(instruction) is list: 

534 (theseUsers, thisAmount) = instruction 

535 else: 

536 (theseUsers, thisAmount) = (USERS, instruction) 

537 if user in theseUsers: 

538 n = thisAmount 

539 if n is None: 

540 expectedItem = expectedItemSg or thisCaption 

541 else: 

542 pl = expectedItemPl or thisCaption 

543 sg = expectedItemSg or thisCaption[0:-1] 

544 expectedItem = sg if n == 1 else pl 

545 expectedCaptions.setdefault(user, {})[thisCaption] = (n, expectedItem) 

546 

547 expect = {user: G(expectedCaptions, user) for user in USERS} 

548 forall(clients, expect, assertCaptions) 

549 

550 

551def startAssessment(clients, eid, expect): 

552 """Starts an assessment and deletes it immediately afterwards. 

553 

554 All users in `clients` for which there is an entry in `expect` do this. 

555 

556 Parameters 

557 ---------- 

558 clients: dict 

559 Mapping from users to client fixtures 

560 eid: string(ObjectId) 

561 The id of the contribution for an assessment is started 

562 expect: dict 

563 Per user whether the starting of an assessment succeeds or not 

564 """ 

565 

566 def assertIt(cl, exp): 

567 aId = assertStartTask(cl, START_ASSESSMENT, eid, exp) 

568 if exp: 

569 assert aId is not None 

570 assertDelItem(cl, ASSESS, aId, True) 

571 else: 

572 assert aId is None 

573 

574 forall(clients, expect, assertIt)