Bodies van verzoeken en een JSON-API · oefening
Bewijs dat PUT idempotent is
In hoofdstuk 2 las je twee beloften die de PUT is PUT twee keer sturen laat de server in dezelfde toestand achter als hem één keer sturen. POST is dat niet: een tweede identieke POST voegt een tweede boek toe. Een client vertrouwt op die beloften als een antwoord verloren gaat en hij moet beslissen of hij het verzoek opnieuw stuurt. Je server accepteert nu beide methoden, en nog niets laat zien dat hij een van beide beloften nakomt. Deze les bewijst ze allebei met twee eigen tests.
Vergelijk toestanden, geen antwoorden
Je zag in hoofdstuk 2 dat een herhaalde DELETE idempotent is, ook al verschillen de twee antwoorden, 204 en daarna 404: de belofte gaat over de toestand op de server, niet over wat er terugkomt. Een test van die belofte moet naar de toestand kijken.
Hier is daar geen server voor nodig, omdat handle de state als reading_server.sample_state(), stuur een PUT en bewaar hoe de state eruitziet; stuur dezelfde PUT nog eens en vergelijk:
state = reading_server.sample_state()
data = {"title": "Dune", "author": "Frank Herbert"}
reading_server.handle(make_request("PUT", "/books/2", data), state)
books_after_one = len(state["books"])
Voorbereiden, uitvoeren, controleren (arrange, act, assert), zoals in elke test die je hebt geschreven: hier is het uitvoeren het tweede verzoek, en de asserts vergelijken. Twee vergelijkingen zeggen wat idempotent voor deze server betekent. Het aantal boeken na twee PUTs is gelijk aan het aantal na één. En het boek dat de state["books"][2], is na beide hetzelfde.
Wat het niet zou bewijzen, zijn twee statussen. Een server waarvan de PUT elke keer stilletjes een nieuw boek opsloeg, zou nog steeds twee keer 200 OK antwoorden. Een test die alleen de antwoorden vergelijkt, slaagt ook voor die server, en bewijst dus niets over de belofte.
make_request(method, target, data) bovenaan tests/test_server.py bouwt de verzoek-data codeert hij die als content-type: application/json en een op bytes getelde content-length toe, zoals read_body ze zou hebben achtergelaten.
Twee POSTs, twee boeken
De POST-test controleert de tegenovergestelde belofte: twee identieke POST /books-verzoeken maken twee boeken aan. Een verse voorbeeld-state heeft twee boeken, dus na de twee verzoeken zouden er vier moeten zijn. De twee antwoorden zeggen in location waar elk boek terechtkwam, en die moeten verschillen: twee boeken, twee id’s.
first = reading_server.handle(make_request("POST", "/books", data), state)
first["headers"]["location"] is het pad van het eerste nieuwe boek. Een server waarvan de POST twee keer dezelfde id uitdeelde, zou het tweede boek over het eerste heen opslaan, beide keren 201 Created antwoorden en twee keer dezelfde location sturen. Boeken tellen, of de twee locaties vergelijken, vangt dat op.
Waarom de tests reading_server.handle aanroepen
Je tests worden zelf ook gecontroleerd. Check my work voert ze uit tegen andere versies van handle: één die zich correct gedraagt, en kapotte versies die de ene belofte nakomen maar de andere niet. Hij zet elke versie op zijn plaats door reading_server.handle te vervangen. Een test die reading_server.handle(...) aanroept, zoekt de from reading_server import handle zegt, of handle = reading_server.handle, houdt de oorspronkelijke functie vast in een eigen naam, en de vervanging bereikt die nooit. Daarom zegt de docstring van het bestand dat je de functie via de module moet aanroepen. Een eigen hulpfunctie in het testbestand die reading_server.handle(...) aanroept, is ook prima: die zoekt de functie ook elke keer op als hij draait.
De correcte versie die Check my work gebruikt, is zijn eigen versie, niet de jouwe, dus de foutmeldingen kunnen anders geformuleerd zijn dan de jouwe. Test statussen, headers en de state, niet de formulering van een fout.
Welke assert vangt een PUT die elke keer een nieuw boek toevoegt?
Jouw opdracht
Vervang in tests/test_server.py de twee placeholders die assert False, "not written yet" zeggen:
test_repeating_a_put_leaves_one_state: stuur dezelfdePUT /books/2twee keer naar één versereading_server.sample_state(), en controleer met asserts dat de tweede het aantal boeken en boek 2 precies zo achterliet als de eerste.test_repeating_a_post_creates_two_books: stuur dezelfdePOST /bookstwee keer naar één verse state, en controleer met asserts dat de state daarna vier boeken bevat en dat de tweelocation-verschillen.
Laat test_get_books_is_json staan, laat import reading_server staan, en stuur elk verzoek via reading_server.handle(...), binnen elke test of in een eigen hulpfunctie in hetzelfde bestand. Voer daarna de tests uit in het tabblad Shell:
python -m pytest -q
Het zou 3 passed moeten melden. Klik daarna op Check my work. Het project waarmee dit hoofdstuk eindigt, voegt nog één functie aan de leeslijst toe en bewijst alles.
Opdracht
Bewijs met je eigen tests dat PUT POST niet.
Vervang in tests/test_server.py de twee placeholders:
test_repeating_a_put_leaves_one_state: stuur dezelfdePUT /books/2met dezelfde body twee keer naar één versereading_server.sample_state(). Controleer met asserts dat na het tweede verzoek het aantal boeken en boek 2 precies zijn wat ze na het eerste waren.test_repeating_a_post_creates_two_books: stuur dezelfdePOST /bookstwee keer naar één verse state. Controleer met asserts dat de state daarna twee boeken meer bevat dan aan het begin, en dat delocation-van de twee antwoorden verschillen.
Bouw verzoeken met de meegeleverde make_request(method, target, data). Laat test_get_books_is_json en import reading_server staan, en stuur elk verzoek via reading_server.handle(...), binnen elke test of in een eigen hulpfunctie in hetzelfde bestand; voeg geen regel from reading_server import toe en bewaar handle niet in een naam bovenaan het bestand. Test statussen, headers en de state, niet de formulering van foutmeldingen.
In de python -m pytest -q de tests uit; als beide zijn geschreven, meldt het 3 passed.
Klik daarna op Check my work. Die voert je tests drie keer uit, elke keer met een andere handle die via de PUT bij elke aanroep de body onder een nieuwe id opslaat, en een versie waarvan de POST nooit doorschuift naar de volgende id, zodat een tweede POST de eerste overschrijft. Je tests moeten allemaal slagen tegen de eerste, en tegen elk van de andere moet er minstens één mislukken. Een run die langer dan 3 seconden duurt, wordt gestopt en gemeld. Niets uit je terminal wordt beoordeeld.