0%

Bodies van verzoeken en een JSON-API · oefening

Bewijs dat PUT idempotent is

In hoofdstuk 2 las je twee beloften die de doen. PUT is : dezelfde 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 krijgt. Maak een verse state met 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 aanduidt, 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- voor je. Met data codeert hij die als en voegt hij 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 in de op op het moment dat hij draait, dus hij treft de versie die er op dat moment staat. Een bestand dat bovenaan 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:

  1. test_repeating_a_put_leaves_one_state: stuur dezelfde PUT /books/2 twee keer naar één verse reading_server.sample_state(), en controleer met asserts dat de tweede het aantal boeken en boek 2 precies zo achterliet als de eerste.

  2. test_repeating_a_post_creates_two_books: stuur dezelfde POST /books twee keer naar één verse state, en controleer met asserts dat de state daarna vier boeken bevat en dat de twee location- 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 is en POST niet.

Vervang in tests/test_server.py de twee placeholders:

  1. test_repeating_a_put_leaves_one_state: stuur dezelfde PUT /books/2 met dezelfde body twee keer naar één verse reading_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.

  2. test_repeating_a_post_creates_two_books: stuur dezelfde POST /books twee keer naar één verse state. Controleer met asserts dat de state daarna twee boeken meer bevat dan aan het begin, en dat de location- 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 voert 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 op zijn plaats is gezet: een eigen correcte versie, een versie waarvan 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.