async def test_stream_is_cached(self, url, async_client): async with async_client.stream("GET", url + "stream") as resp_1: content_1 = await resp_1.aread() async with async_client.stream("GET", url + "stream") as resp_2: content_2 = await resp_2.aread() assert not cache_hit(resp_1) assert cache_hit(resp_2) assert content_1 == content_2
async def test_redirect_response_is_cached(self, url): await self.async_client.get(url + "permanent_redirect", allow_redirects=False) resp = await self.async_client.get(url + "permanent_redirect", allow_redirects=False) assert cache_hit(resp)
async def test_multiple_choices_is_cacheable(self, url): await self.async_client.get(url + "multiple_choices_redirect", allow_redirects=False) resp = await self.async_client.get(url + "multiple_choices_redirect", allow_redirects=False) assert cache_hit(resp)
async def test_etags_get_example(self, async_client, url): """RFC 2616 14.26 The If-None-Match request-header field is used with a method to make it conditional. A client that has one or more entities previously obtained from the resource can verify that none of those entities is current by including a list of their associated entity tags in the If-None-Match header field. The purpose of this feature is to allow efficient updates of cached information with a minimum amount of transaction overhead If any of the entity tags match the entity tag of the entity that would have been returned in the response to a similar GET request (without the If-None-Match header) on that resource, [...] then the server MUST NOT perform the requested method, [...]. Instead, if the request method was GET or HEAD, the server SHOULD respond with a 304 (Not Modified) response, including the cache-related header fields (particularly ETag) of one of the entities that matched. (Paraphrased) A server may provide an ETag header on a response. On subsequent queries, the client may reference the value of this Etag header in an If-None-Match header; on receiving such a header, the server can check whether the entity at that URL has changed from the clients last version, and if not, it can return a 304 to indicate the client can use it's current representation. """ r1 = await async_client.get(url + "etag") # make sure we cached it assert await async_client._transport.cache.aget(url + "etag") # make the same request r2 = await async_client.get(url + "etag") assert cache_hit(r2) assert raw_resp(r2) == raw_resp(r1) # tell the server to change the etags of the response await async_client.get(url + "update_etag") r3 = await async_client.get(url + "etag") assert not cache_hit(r3) assert raw_resp(r3) != raw_resp(r1) r4 = await async_client.get(url + "etag") assert cache_hit(r4) assert raw_resp(r4) == raw_resp(r3)
async def test_vary_example(self, async_client, cache, url): """RFC 2616 13.6 When the cache receives a subsequent request whose Request-URI specifies one or more cache entries including a Vary header field, the cache MUST NOT use such a cache entry to construct a response to the new request unless all of the selecting request-headers present in the new request match the corresponding stored request-headers in the original request. Or, in simpler terms, when you make a request and the server returns defines a Vary header, unless all the headers listed in the Vary header are the same, it won't use the cached value. """ vary_url = urljoin(url, "/vary_accept") r = await async_client.get(vary_url, headers={"foo": "a"}) cached_response, _vary_data = await cache.aget(vary_url) # make the same request resp = await async_client.get(vary_url, headers={"foo": "b"}) assert cache_hit(resp) # make a similar request, changing the accept header resp = await async_client.get(vary_url, headers={ "Accept": "text/plain, text/html", "foo": "c" }) with pytest.raises(AssertionError): self.assert_cached_equal(cached_response, resp) assert not cache_hit(resp) # Just confirming two things here: # # 1) The server used the vary header # 2) We have more than one header we vary on # # The reason for this is that when we don't specify the header # in the request, it is considered the same in terms of # whether or not to use the cached value. assert "vary" in r.headers assert len(r.headers["vary"].replace(" ", "").split(",")) == 2
async def test_bust_cache_on_redirect(self, url): await self.async_client.get(url + "permanent_redirect", allow_redirects=False) resp = await self.async_client.get( url + "permanent_redirect", headers={"cache-control": "no-cache"}, allow_redirects=False, ) assert not cache_hit(resp)
async def test_cache_for_one_day(self, url): the_url = url + "optional_cacheable_request" r = await self.async_client.get(the_url) assert "expires" in r.headers assert "warning" in r.headers pprint(dict(r.headers)) r = await self.async_client.get(the_url) pprint(dict(r.headers)) assert cache_hit(r)
async def test_expires_after_one_day(self, url): the_url = url + "no_cache" resp = httpx.get(the_url) assert resp.headers["cache-control"] == "no-cache" r = await self.async_client.get(the_url) assert "expires" in r.headers assert "warning" in r.headers assert r.headers["cache-control"] == "public" r = await self.async_client.get(the_url) assert cache_hit(r)
async def test_cache_chunked_response(self, url, async_client): """ Verify that an otherwise cacheable response is cached when the response is chunked. """ url = url + "stream" r = await async_client.get(url) print(async_client._transport.cache.data) assert r.headers.get("transfer-encoding") == "chunked" r = await async_client.get(url, headers={"Cache-Control": "max-age=3600"}) assert cache_hit(r)
async def test_expired_etags_if_none_match_response( self, async_client, url): """Make sure an expired response that contains an ETag uses the If-None-Match header. """ # Cache an old etag response with freeze_time("2012-01-14"): await async_client.get(url + "etag") assert await async_client._transport.cache.aget(url + "etag") r2 = await async_client.get(url + "etag") assert cache_hit(r2) real_request = get_last_request(async_client) assert "if-none-match" in real_request.headers assert r2.status_code == 200
async def test_delete_invalidates_cache(self, url, async_client): await async_client.get(url) await async_client.delete(url) r3 = await async_client.get(url) assert not cache_hit(r3)
async def test_patch_invalidates_cache(self, url, async_client): await async_client.get(url) await async_client.patch(url, data={"foo": "bar"}) r3 = await async_client.get(url) assert not cache_hit(r3)
async def test_get_with_no_cache_does_not_cache(self, url, async_client): await async_client.get(url) r2 = await async_client.get(url, headers={"Cache-Control": "no-cache"}) assert not cache_hit(r2)
async def test_get_caches(self, url, async_client): await async_client.get(url) r2 = await async_client.get(url) assert cache_hit(r2)
async def test_stream_is_not_cached_when_content_is_not_read( self, url, async_client): async with async_client.stream("GET", url + "stream"): pass async with async_client.stream("GET", url + "stream") as resp: assert not cache_hit(resp)