File size: 3,129 Bytes
a7ffb22
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
"""Few database round trips (fewer-database-calls 1.1): with every store call made to wait, a screen and an answer must still be quick,
because independent reads run at the same time and repeated reads happen once."""
import time
import uuid

import pytest
from fastapi.testclient import TestClient

from mathly2.bank import load_bank
from mathly2.config import Settings
from mathly2.main import create_app
from mathly2.store import InMemoryStore

DELAY = 0.1
KID = "kid@school.test"
H = {"Authorization": f"Bearer dev:{KID}"}


class SlowStore(InMemoryStore):
    """Every public call takes DELAY seconds, like a remote database."""
    slow = False

    def __getattribute__(self, name):
        attr = super().__getattribute__(name)
        if callable(attr) and not name.startswith("_") and name != "calls" and super().__getattribute__("slow"):
            def waiting(*a, **k):
                time.sleep(DELAY)
                return attr(*a, **k)
            return waiting
        return attr


@pytest.fixture(scope="module")
def world():
    bank = load_bank()
    store = SlowStore()
    lessons = [l["id"] for l in bank.lessons][:6]
    klass = store.create_class("setup", "A", lessons=lessons, teacher_email="t@school.test")
    store.add_member(klass["id"], KID)
    from datetime import timedelta
    from mathly2 import clock
    store.create_test(klass["id"], clock.school_day() + timedelta(days=9), lessons[:2], "t@school.test", created_at=clock.now() - timedelta(days=20))
    client = TestClient(create_app(Settings(), bank=bank, store=store))
    skill = next(s["id"] for s in bank.skills if s["unit"] in lessons and bank.question_keys(s["id"]))
    client.get("/api/me", headers=H)                               # warm the roles cache, as a signed-in student's later requests are
    client.get(f"/api/skills/{skill}", headers=H)                  # the question is chosen and remembered on the first open
    store.slow = True
    yield client, bank, skill, store
    store.slow = False


def timed(client, method, path, **kw):
    start = time.perf_counter()
    response = client.request(method, path, headers=H, **kw)
    assert response.status_code == 200, response.text
    return time.perf_counter() - start


@pytest.mark.parametrize("path", ["/api/home", "/api/path", "/api/grades", "/api/collection"])
def test_a_screen_waits_for_the_database_at_most_three_times_in_a_row(world, path):
    client, _, _, _ = world
    assert timed(client, "GET", path) < 0.55


def test_opening_a_skill_waits_at_most_three_times_in_a_row(world):
    client, _, skill, _ = world
    assert timed(client, "GET", f"/api/skills/{skill}") < 0.55


def test_an_answer_waits_at_most_four_times_in_a_row(world):
    client, bank, skill, store = world
    store.slow = False
    key = client.get(f"/api/skills/{skill}", headers=H).json()["question"]["key"]
    store.slow = True
    assert timed(client, "POST", f"/api/skills/{skill}/answer", json={"question_key": key, "choice": 0}) < 0.75


def test_me_waits_at_most_two_or_three_times(world):
    client, _, _, _ = world
    assert timed(client, "GET", "/api/me") < 0.55