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.
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`.
8There are also some functions that are even higher level, and have been
9factored out from concrete test functions.
10"""
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)
45def assertAddItem(client, table, expect):
46 """Adds an item to a table.
48 The response texts will be analysed into messages and fields, the eid
49 of the new item will be read off.
51 Parameters
52 ----------
53 client: fixture
54 table: string
55 expect: boolean
57 Returns
58 -------
59 eid: str(ObjectId)
60 The id of the inserted item.
61 """
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
74def assertCaptions(client, expect):
75 """Check whether a response text shows a certain set of captions.
77 Parameters
78 ----------
79 client: fixture
80 expect: set of string
81 """
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
106def assertDelItem(client, table, eid, expect):
107 """Deletes an item from a table.
109 Parameters
110 ----------
111 client: fixture
112 table: string
113 eid: string(ObjectId)
114 expect: boolean
115 """
117 assertStatus(client, f"/api/{table}/delete/{eid}", expect)
120def assertEditor(client, table, eid, valueTables, expect, clear=False):
121 """Sets the `editors` of an item to **editor**, or clears the `editors` field.
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 """
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)
142def assertFieldValue(source, field, expect):
143 """Verify whether a field has a certain expected value.
145 If we pass expect `None` we want to assert that the field is not present at all.
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 """
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
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]
176def assertModifyField(client, table, eid, field, newValue, expect):
177 """Try to modify a field and check the outcome.
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.
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 """
196 if not expect:
197 info = getItem(client, table, eid)
198 fields = info["fields"]
199 oldValue = fields[field] if field in fields else None
201 if type(newValue) is tuple:
202 (newValue, newValueRep) = newValue
203 else:
204 newValueRep = newValue
206 (text, fields) = modifyField(client, table, eid, field, newValue)
208 if not expect:
209 assert field not in fields
211 info = getItem(client, table, eid)
212 fields = info["fields"]
214 if expect:
215 assertFieldValue(fields, field, newValueRep)
216 else:
217 if field in fields:
218 assertFieldValue(fields, field, oldValue)
221def assertReviewDecisions(clients, reviewId, kinds, decisions, expect):
222 """Check whether the reviewers can take certain decisions.
224 You specify which reviewers take which decisions, and they will
225 all be carried out in that order.
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.
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 """
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)
270def assertShiftDate(clientSys, table, eid, field, amount):
271 """Shifts the date in a field annd recomputes workflow.
273 See `helpers.shiftDate`.
274 """
275 shiftDate(table, eid, field, amount)
276 assertStatus(clientSys, "/workflow", True)
279def assertStage(client, table, eid, expect):
280 """Check whether a record has a certain workflow stage.
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
290 Returns
291 -------
292 dict
293 The text, fields, msgs and stage of the record
294 """
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
307def assertStartTask(client, task, eid, expect):
308 """Issues a start workflow command.
310 There are `startAssessment` and `startReview` tasks that create a record,
311 and there are task that set a field in an existing recordd.
313 Tasks take as arguments the eid of a record in a table.
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.
318 Parameters
319 ----------
320 client: fixture
321 eid: string(ObjectId)
322 The id that is the argumenent for the workflow task.
323 expect: boolean
325 Returns
326 -------
327 eid: str(ObjectId) | `None`
328 """
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)
339 return newEid if task in {START_ASSESSMENT, START_REVIEW} else None
342def assertStatus(client, url, expect):
343 """Get data and see whether that went right or wrong.
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 """
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
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
382def assignReviewers(clients, users, aId, field, user, keep, expect):
383 """Verify assigning reviewers to an assessment.
385 A reviewer will be assigned to an assessment and immediately be unassigned.
386 But the undo can be suppressed.
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 """
406 value = G(users, user)
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)
413 forall(clients, expect, assertIt)
416def illegalize(clients, url, **kwargs):
417 """Append illegal/long arguments to an url and trigger a 400 response.
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 """
428 kwargsx = {k: v + "a" * 200 for (k, v) in kwargs.items()}
429 base = url.format(**kwargs)
430 basex = url.format(**kwargsx)
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 ]
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)
452def isIllegal(clients, url):
453 """Make sure that the ur triggers a 400 response for all clients.
455 Parameters
456 ----------
457 clients: dict
458 Mapping from users to client fixtures
459 """
461 expectx = {user: 400 for user in USERS if user in clients}
462 serverprint(f"HACKED URL ! ({url})")
463 forall(clients, expectx, assertStatus, url)
466def inspectTitleAll(clients, table, eid, expect):
467 """Verify the title of an item, as seen by each user.
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 """
479 def assertIt(cl, exp):
480 assertFieldValue((cl, table, eid), TITLE, exp)
482 forall(clients, expect, assertIt)
485def modifyTitleAll(clients, table, eid, expect):
486 """Modify the title of an item, performed by each user.
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 """
498 def assertIt(cl, exp):
499 assertModifyField(cl, table, eid, TITLE, TITLE2, exp)
500 if exp:
501 assertModifyField(cl, table, eid, TITLE, TITLE1, exp)
503 forall(clients, expect, assertIt)
506def sidebar(clients, amounts):
507 """Verify the sidebar.
509 It will be verified whether each user sees the right entries,
510 and that following an entry leads to the expected results.
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 """
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)
547 expect = {user: G(expectedCaptions, user) for user in USERS}
548 forall(clients, expect, assertCaptions)
551def startAssessment(clients, eid, expect):
552 """Starts an assessment and deletes it immediately afterwards.
554 All users in `clients` for which there is an entry in `expect` do this.
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 """
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
574 forall(clients, expect, assertIt)