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 

363 if type(expect) is set: 

364 good = code in expect 

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

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

367 assert good 

368 elif type(expect) is int: 

369 good = code == expect 

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

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

372 assert good 

373 else: 

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

375 good = code in codes 

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

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

378 assert good 

379 

380 

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

382 """Verify assigning reviewers to an assessment. 

383 

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

385 But the undo can be suppressed. 

386 

387 Parameters 

388 ---------- 

389 clients: dict 

390 Mapping from users to client fixtures 

391 users: dict 

392 Mapping of users to ids 

393 aId: string(ObjectId) 

394 Assessment id 

395 field: string 

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

397 user: string 

398 The reviewer user 

399 keep: boolean 

400 If True, the assignment will not be undone 

401 expect: dict 

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

403 """ 

404 

405 value = G(users, user) 

406 

407 def assertIt(cl, exp): 

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

409 if exp and not keep: 

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

411 

412 forall(clients, expect, assertIt) 

413 

414 

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

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

417 

418 Parameters 

419 ---------- 

420 clients: dict 

421 Mapping from users to client fixtures 

422 kwargs: dict 

423 Additional parameters to illegalize. 

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

425 """ 

426 

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

428 base = url.format(**kwargs) 

429 basex = url.format(**kwargsx) 

430 

431 uxs = [ 

432 base, 

433 basex, 

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

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

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

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

438 ] 

439 

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

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

442 expectx = { 

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

444 for user in USERS 

445 if user in clients 

446 } 

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

448 forall(clients, expectx, assertStatus, ux) 

449 

450 

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

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

453 

454 Parameters 

455 ---------- 

456 clients: dict 

457 Mapping from users to client fixtures 

458 table: the table of the item 

459 eid: the id of the item 

460 expect: dict 

461 The expected values, keyed per user 

462 """ 

463 

464 def assertIt(cl, exp): 

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

466 

467 forall(clients, expect, assertIt) 

468 

469 

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

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

472 

473 Parameters 

474 ---------- 

475 clients: dict 

476 Mapping from users to client fixtures 

477 table: the table of the item 

478 eid: the id of the item 

479 expect: dict 

480 The expected values, keyed per user 

481 """ 

482 

483 def assertIt(cl, exp): 

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

485 if exp: 

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

487 

488 forall(clients, expect, assertIt) 

489 

490 

491def sidebar(clients, amounts): 

492 """Verify the sidebar. 

493 

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

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

496 

497 Parameters 

498 ---------- 

499 clients: dict 

500 Mapping from users to client fixtures 

501 amounts: dict 

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

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

504 to setting the indicated amount for the indicated users. 

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

506 """ 

507 

508 expectedCaptions = {} 

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

510 for user in expectedUsers: 

511 thisCaption = ( 

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

513 if "{country}" in caption 

514 else caption 

515 ) 

516 n = expectedN 

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

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

519 (theseUsers, thisAmount) = instruction 

520 else: 

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

522 if user in theseUsers: 

523 n = thisAmount 

524 if n is None: 

525 expectedItem = expectedItemSg or thisCaption 

526 else: 

527 pl = expectedItemPl or thisCaption 

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

529 expectedItem = sg if n == 1 else pl 

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

531 

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

533 forall(clients, expect, assertCaptions) 

534 

535 

536def startAssessment(clients, eid, expect): 

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

538 

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

540 

541 Parameters 

542 ---------- 

543 clients: dict 

544 Mapping from users to client fixtures 

545 eid: string(ObjectId) 

546 The id of the contribution for an assessment is started 

547 expect: dict 

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

549 """ 

550 

551 def assertIt(cl, exp): 

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

553 if exp: 

554 assert aId is not None 

555 assertDelItem(cl, ASSESS, aId, True) 

556 else: 

557 assert aId is None 

558 

559 forall(clients, expect, assertIt)