django-esi 8.0.0b1__py3-none-any.whl → 8.0.0b2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of django-esi might be problematic. Click here for more details.

Files changed (46) hide show
  1. {django_esi-8.0.0b1.dist-info → django_esi-8.0.0b2.dist-info}/METADATA +3 -2
  2. {django_esi-8.0.0b1.dist-info → django_esi-8.0.0b2.dist-info}/RECORD +46 -45
  3. esi/__init__.py +1 -1
  4. esi/aiopenapi3/plugins.py +12 -2
  5. esi/clients.py +41 -1
  6. esi/decorators.py +26 -10
  7. esi/exceptions.py +7 -3
  8. esi/helpers.py +1 -0
  9. esi/locale/cs_CZ/LC_MESSAGES/django.mo +0 -0
  10. esi/locale/cs_CZ/LC_MESSAGES/django.po +2 -2
  11. esi/locale/de/LC_MESSAGES/django.mo +0 -0
  12. esi/locale/de/LC_MESSAGES/django.po +2 -2
  13. esi/locale/en/LC_MESSAGES/django.mo +0 -0
  14. esi/locale/en/LC_MESSAGES/django.po +2 -2
  15. esi/locale/es/LC_MESSAGES/django.mo +0 -0
  16. esi/locale/es/LC_MESSAGES/django.po +2 -2
  17. esi/locale/fr_FR/LC_MESSAGES/django.mo +0 -0
  18. esi/locale/fr_FR/LC_MESSAGES/django.po +2 -2
  19. esi/locale/it_IT/LC_MESSAGES/django.mo +0 -0
  20. esi/locale/it_IT/LC_MESSAGES/django.po +2 -2
  21. esi/locale/ja/LC_MESSAGES/django.mo +0 -0
  22. esi/locale/ja/LC_MESSAGES/django.po +2 -2
  23. esi/locale/ko_KR/LC_MESSAGES/django.mo +0 -0
  24. esi/locale/ko_KR/LC_MESSAGES/django.po +2 -2
  25. esi/locale/nl_NL/LC_MESSAGES/django.mo +0 -0
  26. esi/locale/nl_NL/LC_MESSAGES/django.po +2 -2
  27. esi/locale/pl_PL/LC_MESSAGES/django.mo +0 -0
  28. esi/locale/pl_PL/LC_MESSAGES/django.po +2 -2
  29. esi/locale/ru/LC_MESSAGES/django.mo +0 -0
  30. esi/locale/ru/LC_MESSAGES/django.po +2 -2
  31. esi/locale/sk/LC_MESSAGES/django.mo +0 -0
  32. esi/locale/sk/LC_MESSAGES/django.po +2 -2
  33. esi/locale/uk/LC_MESSAGES/django.mo +0 -0
  34. esi/locale/uk/LC_MESSAGES/django.po +2 -2
  35. esi/locale/zh_Hans/LC_MESSAGES/django.mo +0 -0
  36. esi/locale/zh_Hans/LC_MESSAGES/django.po +2 -2
  37. esi/managers.pyi +3 -0
  38. esi/openapi_clients.py +139 -31
  39. esi/rate_limiting.py +50 -21
  40. esi/signals.py +21 -0
  41. esi/tests/__init__.py +30 -8
  42. esi/tests/test_decorators.py +61 -1
  43. esi/tests/test_openapi.json +65 -2
  44. esi/tests/test_openapi.py +387 -47
  45. {django_esi-8.0.0b1.dist-info → django_esi-8.0.0b2.dist-info}/WHEEL +0 -0
  46. {django_esi-8.0.0b1.dist-info → django_esi-8.0.0b2.dist-info}/licenses/LICENSE +0 -0
@@ -122,7 +122,12 @@
122
122
  }
123
123
  },
124
124
  "type": "object"
125
+ },
126
+ "UniverseTypesGet": {
127
+ "items": { "format": "int64", "type": "integer" },
128
+ "type": "array"
125
129
  }
130
+
126
131
  },
127
132
  "securitySchemes": {
128
133
  "OAuth2": {
@@ -217,6 +222,58 @@
217
222
  },
218
223
  "openapi": "3.1.0",
219
224
  "paths": {
225
+ "/universe/types": {
226
+ "get": {
227
+ "description": "Get a list of type ids\n\nThis route expires daily at 11:05",
228
+ "operationId": "GetUniverseTypes",
229
+ "parameters": [
230
+ {
231
+ "in": "query",
232
+ "name": "page",
233
+ "schema": {
234
+ "description": "Which page of results to return.",
235
+ "format": "int32",
236
+ "minimum": 1,
237
+ "type": "integer"
238
+ }
239
+ },
240
+ { "$ref": "#/components/parameters/AcceptLanguage" },
241
+ { "$ref": "#/components/parameters/IfNoneMatch" },
242
+ { "$ref": "#/components/parameters/CompatibilityDate" },
243
+ { "$ref": "#/components/parameters/Tenant" }
244
+ ],
245
+ "responses": {
246
+ "200": {
247
+ "content": {
248
+ "application/json": {
249
+ "schema": { "$ref": "#/components/schemas/UniverseTypesGet" }
250
+ }
251
+ },
252
+ "description": "OK",
253
+ "headers": {
254
+ "Cache-Control": { "$ref": "#/components/headers/CacheControl" },
255
+ "ETag": { "$ref": "#/components/headers/ETag" },
256
+ "Last-Modified": { "$ref": "#/components/headers/LastModified" },
257
+ "X-Pages": {
258
+ "description": "The total number of pages in the result set.",
259
+ "schema": { "format": "int64", "type": "integer" }
260
+ }
261
+ }
262
+ },
263
+ "default": {
264
+ "content": {
265
+ "application/json": {
266
+ "schema": { "$ref": "#/components/schemas/Error" }
267
+ }
268
+ },
269
+ "description": "Error"
270
+ }
271
+ },
272
+ "summary": "Get types",
273
+ "tags": ["Universe"],
274
+ "x-compatibility-date": "2020-01-01"
275
+ }
276
+ },
220
277
  "/status": {
221
278
  "get": {
222
279
  "description": "EVE Server status",
@@ -253,12 +310,18 @@
253
310
  "summary": "Retrieve the uptime and player counts",
254
311
  "tags": ["Status"],
255
312
  "x-cache-age": 30,
256
- "x-compatibility-date": "2020-01-01"
313
+ "x-compatibility-date": "2020-01-01",
314
+ "x-rate-limit": {
315
+ "group": "status",
316
+ "max-tokens": 600,
317
+ "window-size": "15m"
318
+ }
257
319
  }
258
320
  }
259
321
  },
260
322
  "servers": [{ "url": "https://esi.evetech.net" }],
261
323
  "tags": [
262
- { "name": "Status" }
324
+ { "name": "Status" },
325
+ { "name": "Universe" }
263
326
  ]
264
327
  }
esi/tests/test_openapi.py CHANGED
@@ -1,5 +1,5 @@
1
+ import json
1
2
  import os
2
- from unittest import mock
3
3
  from unittest.mock import MagicMock, patch
4
4
  from django.test import TestCase
5
5
  from datetime import date, timedelta
@@ -7,17 +7,21 @@ from datetime import date, timedelta
7
7
  from esi.openapi_clients import ESIClientProvider
8
8
  from django.core.cache import cache
9
9
  from django.utils import timezone
10
+ from esi.tests import NoSocketsTestCase
10
11
  from httpx import RequestError, HTTPStatusError
11
- from esi.exceptions import ESIErrorLimitException, HTTPNotModified
12
+ from esi.exceptions import ESIErrorLimitException, HTTPClientError, HTTPNotModified, ESIBucketLimitException, HTTPServerError
13
+ from esi.rate_limiting import ESIRateLimits
12
14
  from esi import app_settings
13
15
  from esi import __title__, __url__, __version__
14
16
  import httpx
15
17
 
18
+ from . import _generate_token
16
19
  from .. import openapi_clients as oc
17
20
 
18
21
  SPEC_PATH = os.path.join(
19
22
  os.path.dirname(os.path.abspath(__file__)), "test_openapi.json"
20
23
  )
24
+ MODULE_PATH_PLUGINS = 'esi.aiopenapi3.plugins'
21
25
 
22
26
 
23
27
  class TestClientFunctions(TestCase):
@@ -78,7 +82,7 @@ class BuildUserAgentTests(TestCase):
78
82
  ua = oc._build_user_agent(self.app_name, self.app_ver, self.app_url)
79
83
 
80
84
  expected_app_name = "TestApp"
81
- expected_title = 'DjangoEsi'
85
+ expected_title = "DjangoEsi"
82
86
 
83
87
  self.assertEqual(
84
88
  (
@@ -100,7 +104,7 @@ class BuildUserAgentTests(TestCase):
100
104
  ua = oc._build_user_agent("test app", self.app_ver, self.app_url)
101
105
 
102
106
  expected_app_name = "TestApp"
103
- expected_title = 'DjangoEsi'
107
+ expected_title = "DjangoEsi"
104
108
 
105
109
  self.assertEqual(
106
110
  (
@@ -122,7 +126,7 @@ class BuildUserAgentTests(TestCase):
122
126
  ua = oc._build_user_agent("test-app", self.app_ver, self.app_url)
123
127
 
124
128
  expected_app_name = "TestApp"
125
- expected_title = 'DjangoEsi'
129
+ expected_title = "DjangoEsi"
126
130
 
127
131
  self.assertEqual(
128
132
  (
@@ -137,7 +141,7 @@ class BuildUserAgentTests(TestCase):
137
141
  ua = oc._build_user_agent(self.app_name, self.app_ver)
138
142
 
139
143
  expected_app_name = "TestApp"
140
- expected_title = 'DjangoEsi'
144
+ expected_title = "DjangoEsi"
141
145
 
142
146
  self.assertEqual(
143
147
  (
@@ -176,6 +180,7 @@ class BaseEsiOperationTests(TestCase):
176
180
  ]
177
181
  self.fake_op.tags = ["test"]
178
182
  self.fake_op.operationId = "fake_op"
183
+ self.fake_op.extensions = {}
179
184
  self.api = MagicMock(app_name="TestApp")
180
185
  self.op = oc.BaseEsiOperation(
181
186
  ("GET", "/fake_op", self.fake_op, {}),
@@ -246,6 +251,19 @@ class EsiOperationTests(TestCase):
246
251
  self.op_mock.parameters = []
247
252
  self.op_mock.tags = ["tag"]
248
253
  self.op_mock.operationId = "opid"
254
+ self.op_mock.extensions = {}
255
+
256
+ self.op_mock_rate = MagicMock()
257
+ self.op_mock_rate.parameters = []
258
+ self.op_mock_rate.tags = ["tag"]
259
+ self.op_mock_rate.operationId = "opidRated"
260
+ self.op_mock_rate.extensions = {
261
+ "rate-limit": {
262
+ "group": "test-group",
263
+ "max-tokens": 100,
264
+ "window-size": "5m"
265
+ }
266
+ }
249
267
 
250
268
  self.api_mock = MagicMock()
251
269
  self.api_mock.app_name = "TestApp"
@@ -260,6 +278,16 @@ class EsiOperationTests(TestCase):
260
278
  self.api_mock
261
279
  )
262
280
 
281
+ self.op_rate_rated = oc.EsiOperation(
282
+ (
283
+ "GET",
284
+ "/url",
285
+ self.op_mock_rate,
286
+ {}
287
+ ),
288
+ self.api_mock
289
+ )
290
+
263
291
  @patch.object(oc.EsiOperation, "_make_request")
264
292
  def test_result_and_results(self, mock_make_request):
265
293
  data = {"data": "stuff"}
@@ -268,8 +296,37 @@ class EsiOperationTests(TestCase):
268
296
  data_resp = self.op(foo="bar").result()
269
297
  self.assertEqual(data, data_resp)
270
298
 
299
+ def test_esi_bucket_public(self):
300
+ op = self.op_rate_rated(foo="bar")
301
+ self.assertEqual(op.bucket.window, 300)
302
+ self.assertEqual(op.bucket.slug, "test-group")
303
+ self.assertEqual(op.bucket.limit, 100)
271
304
 
272
- class TestOpenapiClientProvider(TestCase):
305
+ def test_esi_bucket_limit(self):
306
+ op = self.op_rate_rated(foo="bar")
307
+ ESIRateLimits.set_bucket(op.bucket, 0)
308
+
309
+ with self.assertRaises(ESIBucketLimitException):
310
+ op.result()
311
+
312
+ class TestOpenapiClientProvider(NoSocketsTestCase):
313
+ def setUp(self):
314
+ self.app_name = "TestsApp"
315
+ self.app_ver = "1.2.3"
316
+ self.app_url = "https://tests.pass"
317
+ self.esi = ESIClientProvider(
318
+ ua_appname=self.app_name,
319
+ ua_url=self.app_url,
320
+ ua_version=self.app_ver,
321
+ compatibility_date="2020-01-01",
322
+ tags=["Status"],
323
+ spec_file=SPEC_PATH
324
+ )
325
+ cache.clear()
326
+
327
+ def test_str(self):
328
+ self.assertIn(self.esi._ua_appname, str(self.esi))
329
+ self.assertIn(self.esi._ua_version, str(self.esi))
273
330
 
274
331
  def test_compatibilitydate_date_to_string(self):
275
332
  testdate_1 = date(2024, 1, 1)
@@ -280,19 +337,6 @@ class TestOpenapiClientProvider(TestCase):
280
337
 
281
338
  @patch.object(httpx.Client, "send")
282
339
  def test_ua(self, send: MagicMock):
283
- app_name = "TestsApp"
284
- app_ver = "1.2.3"
285
- app_url = "https://tests.pass"
286
- esi = ESIClientProvider(
287
- ua_appname=app_name,
288
- ua_url=app_url,
289
- ua_version=app_ver,
290
- compatibility_date="2020-01-01",
291
- tags=["Status"],
292
- spec_file=SPEC_PATH
293
- )
294
- cache.clear()
295
-
296
340
  send.return_value = httpx.Response(
297
341
  200,
298
342
  json={
@@ -303,7 +347,7 @@ class TestOpenapiClientProvider(TestCase):
303
347
  request=httpx.Request("GET", "test"),
304
348
  )
305
349
 
306
- status = esi.client.Status.GetStatus().result()
350
+ status = self.esi.client.Status.GetStatus().result()
307
351
  call_args, call_kwargs = send.call_args
308
352
 
309
353
  expected_app_name = "TestsApp"
@@ -312,19 +356,35 @@ class TestOpenapiClientProvider(TestCase):
312
356
  self.assertEqual(
313
357
  call_args[0].headers["user-agent"],
314
358
  (
315
- f"{expected_app_name}/{app_ver} "
316
- f"({app_settings.ESI_USER_CONTACT_EMAIL}{f'; +{app_url})'} "
359
+ f"{expected_app_name}/{self.app_ver} "
360
+ f"({app_settings.ESI_USER_CONTACT_EMAIL}{f'; +{self.app_url})'} "
317
361
  f"{expected_title}/{__version__} (+{__url__})"
318
362
  )
319
363
  )
320
364
  self.assertEqual(status.players, 1234)
321
365
 
322
- @patch.object(httpx.Client, "send")
323
- def test_etag_hit_cached(self, send: MagicMock):
366
+ @patch(MODULE_PATH_PLUGINS + '.settings.DEBUG', True)
367
+ def test_no_tag_no_op_debug(self):
324
368
  app_name = "TestsApp"
325
369
  app_ver = "1.2.3"
326
370
  app_url = "https://tests.pass"
327
- etag = "'123456789abcdef123456789abcdef'"
371
+
372
+ esi = ESIClientProvider(
373
+ ua_appname=app_name,
374
+ ua_url=app_url,
375
+ ua_version=app_ver,
376
+ compatibility_date="2020-01-01",
377
+ spec_file=SPEC_PATH
378
+ )
379
+ self.assertIsNotNone(esi.client.Status)
380
+ self.assertIsNotNone(esi.client.Status.GetStatus)
381
+
382
+ @patch(MODULE_PATH_PLUGINS + '.settings.DEBUG', True)
383
+ def test_tag_no_op_debug(self):
384
+ app_name = "TestsApp"
385
+ app_ver = "1.2.3"
386
+ app_url = "https://tests.pass"
387
+
328
388
  esi = ESIClientProvider(
329
389
  ua_appname=app_name,
330
390
  ua_url=app_url,
@@ -333,7 +393,66 @@ class TestOpenapiClientProvider(TestCase):
333
393
  tags=["Status"],
334
394
  spec_file=SPEC_PATH
335
395
  )
336
- cache.clear()
396
+ self.assertIsNotNone(esi.client.Status)
397
+ self.assertIsNotNone(esi.client.Status.GetStatus)
398
+
399
+ @patch(MODULE_PATH_PLUGINS + '.settings.DEBUG', True)
400
+ def test_no_tag_op_debug(self):
401
+ app_name = "TestsApp"
402
+ app_ver = "1.2.3"
403
+ app_url = "https://tests.pass"
404
+
405
+ esi = ESIClientProvider(
406
+ ua_appname=app_name,
407
+ ua_url=app_url,
408
+ ua_version=app_ver,
409
+ compatibility_date="2020-01-01",
410
+ tags=["Status"],
411
+ spec_file=SPEC_PATH
412
+ )
413
+ self.assertIsNotNone(esi.client.Status)
414
+ self.assertIsNotNone(esi.client.Status.GetStatus)
415
+
416
+
417
+ @patch(MODULE_PATH_PLUGINS + '.settings.DEBUG', False)
418
+ def test_no_tag_no_op_no_debug(self):
419
+ app_name = "TestsApp"
420
+ app_ver = "1.2.3"
421
+ app_url = "https://tests.pass"
422
+
423
+ with self.assertRaises(AttributeError):
424
+ esi = ESIClientProvider(
425
+ ua_appname=app_name,
426
+ ua_url=app_url,
427
+ ua_version=app_ver,
428
+ compatibility_date="2020-01-01",
429
+ spec_file=SPEC_PATH
430
+ )
431
+ esi.client
432
+
433
+ @patch.object(httpx.Client, "send")
434
+ def test_no_bucket(self, send: MagicMock):
435
+ self.esi = ESIClientProvider(
436
+ ua_appname=self.app_name,
437
+ ua_url=self.app_url,
438
+ ua_version=self.app_ver,
439
+ compatibility_date="2020-01-01",
440
+ tags=["Universe"],
441
+ spec_file=SPEC_PATH
442
+ )
443
+
444
+ send.return_value = httpx.Response(
445
+ 200,
446
+ json=[1,2,3,4],
447
+ request=httpx.Request("GET", "test"),
448
+ )
449
+
450
+ types = self.esi.client.Universe.GetUniverseTypes().result()
451
+ self.assertEqual(len(types), 4)
452
+
453
+ @patch.object(httpx.Client, "send")
454
+ def test_etag_hit_cached(self, send: MagicMock):
455
+ etag = "'123456789abcdef123456789abcdef'"
337
456
 
338
457
  expires = (
339
458
  timezone.now() + timedelta(minutes=5)
@@ -356,26 +475,15 @@ class TestOpenapiClientProvider(TestCase):
356
475
  ),
357
476
  )
358
477
 
359
- esi.client.Status.GetStatus().result()
478
+ self.esi.client.Status.GetStatus().result()
360
479
 
361
480
  with self.assertRaises(HTTPNotModified):
362
- esi.client.Status.GetStatus().result()
481
+ self.esi.client.Status.GetStatus().result()
363
482
 
364
483
  @patch.object(httpx.Client, "send")
365
- def test_etag_hit_external(self, send: MagicMock):
366
- app_name = "TestsApp"
367
- app_ver = "1.2.3"
368
- app_url = "https://tests.pass"
484
+ def test_etag_not_hit_cached(self, send: MagicMock):
369
485
  etag = "'123456789abcdef123456789abcdef'"
370
- esi = ESIClientProvider(
371
- ua_appname=app_name,
372
- ua_url=app_url,
373
- ua_version=app_ver,
374
- compatibility_date="2020-01-01",
375
- tags=["Status"],
376
- spec_file=SPEC_PATH
377
- )
378
- cache.clear()
486
+
379
487
  expires = (
380
488
  timezone.now() + timedelta(minutes=5)
381
489
  ).strftime('%a, %d %b %Y %H:%M:%S %Z')
@@ -396,12 +504,27 @@ class TestOpenapiClientProvider(TestCase):
396
504
  "test",
397
505
  ),
398
506
  )
399
- esi.client.Status.GetStatus().result()
400
507
 
401
- cache.delete(esi.client.Status.GetStatus()._cache_key())
508
+ self.esi.client.Status.GetStatus().result()
509
+
510
+ result = self.esi.client.Status.GetStatus().result(use_etag=False)
511
+ self.assertEqual(result.players, 1234)
512
+
513
+ @patch.object(httpx.Client, "send")
514
+ def test_force_refresh(self, send: MagicMock):
515
+ etag = "'123456789abcdef123456789abcdef'"
516
+
517
+ expires = (
518
+ timezone.now() + timedelta(minutes=5)
519
+ ).strftime('%a, %d %b %Y %H:%M:%S %Z')
402
520
 
403
521
  send.return_value = httpx.Response(
404
- 304,
522
+ 200,
523
+ json={
524
+ "players": 1234,
525
+ "server_version": "1234",
526
+ "start_time": "2029-09-19T11:02:08Z"
527
+ },
405
528
  headers={
406
529
  "etag": etag,
407
530
  "expires": expires
@@ -411,5 +534,222 @@ class TestOpenapiClientProvider(TestCase):
411
534
  "test",
412
535
  ),
413
536
  )
414
- with self.assertRaises(HTTPNotModified):
415
- esi.client.Status.GetStatus().result()
537
+
538
+ self.esi.client.Status.GetStatus().result()
539
+
540
+ result = self.esi.client.Status.GetStatus().result(force_refresh=True)
541
+ self.assertEqual(result.players, 1234)
542
+ self.assertEqual(send.call_count, 2)
543
+
544
+ @patch.object(httpx.Client, "send")
545
+ def test_404(self, send: MagicMock):
546
+ self.esi = ESIClientProvider(
547
+ ua_appname=self.app_name,
548
+ ua_url=self.app_url,
549
+ ua_version=self.app_ver,
550
+ compatibility_date="2020-01-01",
551
+ tags=["Universe"],
552
+ spec_file=SPEC_PATH
553
+ )
554
+
555
+ send.return_value = httpx.Response(
556
+ 404,
557
+ json={
558
+ "error": "error"
559
+ },
560
+ headers={
561
+ "X-RateLimit-Reset": "15",
562
+ "X-RateLimit-Remaining": "0"
563
+ },
564
+ request=httpx.Request(
565
+ "GET",
566
+ "/universe/types"
567
+ ),
568
+ )
569
+
570
+ with self.assertRaises(HTTPClientError):
571
+ self.esi.client.Universe.GetUniverseTypes().result()
572
+
573
+
574
+ @patch.object(httpx.Client, "send")
575
+ def test_420(self, send: MagicMock):
576
+ self.esi = ESIClientProvider(
577
+ ua_appname=self.app_name,
578
+ ua_url=self.app_url,
579
+ ua_version=self.app_ver,
580
+ compatibility_date="2020-01-01",
581
+ tags=["Universe"],
582
+ spec_file=SPEC_PATH
583
+ )
584
+
585
+ send.return_value = httpx.Response(
586
+ 420,
587
+ json={
588
+ "error": "error"
589
+ },
590
+ headers={
591
+ "X-RateLimit-Reset": "15",
592
+ "X-RateLimit-Remaining": "0"
593
+ },
594
+ request=httpx.Request(
595
+ "GET",
596
+ "/universe/types"
597
+ ),
598
+ )
599
+
600
+ with self.assertRaises(ESIErrorLimitException):
601
+ self.esi.client.Universe.GetUniverseTypes().result()
602
+
603
+ self.assertGreater(cache.get("esi_error_limit_reset"), 10)
604
+
605
+ @patch.object(httpx.Client, "send")
606
+ def test_420_past(self, send: MagicMock):
607
+ self.esi = ESIClientProvider(
608
+ ua_appname=self.app_name,
609
+ ua_url=self.app_url,
610
+ ua_version=self.app_ver,
611
+ compatibility_date="2020-01-01",
612
+ tags=["Universe"],
613
+ spec_file=SPEC_PATH
614
+ )
615
+
616
+ send.return_value = httpx.Response(
617
+ 420,
618
+ json={
619
+ "error": "error"
620
+ },
621
+ headers={
622
+ "X-RateLimit-Remaining": "0"
623
+ },
624
+ request=httpx.Request(
625
+ "GET",
626
+ "/universe/types"
627
+ ),
628
+ )
629
+
630
+ with self.assertRaises(ESIErrorLimitException):
631
+ self.esi.client.Universe.GetUniverseTypes().result()
632
+
633
+ self.assertIsNone(cache.get("esi_error_limit_reset"))
634
+
635
+ @patch.object(httpx.Client, "send")
636
+ def test_rate_bucket(self, send: MagicMock):
637
+ send.return_value = httpx.Response(
638
+ 200,
639
+ json={
640
+ "players": 1234,
641
+ "server_version": "1234",
642
+ "start_time": "2029-09-19T11:02:08Z"
643
+ },
644
+ headers={
645
+ "x-ratelimit-group": "status",
646
+ "x-ratelimit-used": "2",
647
+ "x-ratelimit-remaining": "598",
648
+ "x-ratelimit-limit": "600/15m",
649
+ },
650
+ request=httpx.Request(
651
+ "GET",
652
+ "/status"
653
+ ),
654
+ )
655
+ self.esi.client.Status.GetStatus().result()
656
+ self.assertEqual(
657
+ ESIRateLimits.get_bucket(self.esi.client.Status.GetStatus().bucket),
658
+ 598
659
+ )
660
+
661
+ @patch.object(httpx.Client, "send")
662
+ def test_server_error(self, send: MagicMock):
663
+ send.return_value = httpx.Response(
664
+ 520,
665
+ json={
666
+ "error": "error"
667
+ },
668
+ headers={
669
+ "x-ratelimit-group": "status",
670
+ "x-ratelimit-used": "5",
671
+ "x-ratelimit-remaining": "595",
672
+ "x-ratelimit-limit": "600/15m",
673
+ },
674
+ request=httpx.Request(
675
+ "GET",
676
+ "/status"
677
+ ),
678
+ )
679
+ with self.assertRaises(HTTPServerError):
680
+ self.esi.client.Status.GetStatus().result()
681
+
682
+ def test_minified_op_not_found(self):
683
+ with self.assertRaises(AttributeError):
684
+ self.esi.client.Universe.GetUniverseTypes()
685
+
686
+ def test_minified_op_not_found(self):
687
+ self.esi = ESIClientProvider(
688
+ ua_appname=self.app_name,
689
+ ua_url=self.app_url,
690
+ ua_version=self.app_ver,
691
+ compatibility_date="2020-01-01",
692
+ tags=["Universe"],
693
+ spec_file=SPEC_PATH
694
+ )
695
+
696
+ with self.assertRaises(AttributeError):
697
+ self.esi.client.Universe.GetUniverseAncestries()
698
+
699
+ def test_rate_bucket_found_in_spec(self):
700
+
701
+ op = self.esi.client.Status.GetStatus()
702
+
703
+ self.assertIsNotNone(op.bucket)
704
+ self.assertEqual(op.bucket.limit, 600)
705
+ self.assertEqual(op.bucket.slug, "status")
706
+ self.assertEqual(op.bucket.window, 900)
707
+
708
+ def test_rate_bucket_hit(self):
709
+ op = self.esi.client.Status.GetStatus()
710
+
711
+ ESIRateLimits.set_bucket(op.bucket, 0)
712
+
713
+ with self.assertRaises(ESIBucketLimitException):
714
+ op.result()
715
+
716
+ def test_global_limit_hit(self):
717
+ op = self.esi.client.Status.GetStatus()
718
+
719
+ cache.set("esi_error_limit_reset", 15, 15)
720
+
721
+ with self.assertRaises(ESIErrorLimitException):
722
+ op.result()
723
+
724
+ @patch.object(httpx.Client, "send")
725
+ def test_load_sync(self, send: MagicMock):
726
+ esi = ESIClientProvider(
727
+ ua_appname=self.app_name,
728
+ ua_url=self.app_url,
729
+ ua_version=self.app_ver,
730
+ compatibility_date="2020-01-01",
731
+ tags=["Status"]
732
+ )
733
+ cache.clear()
734
+ expires = (
735
+ timezone.now() + timedelta(minutes=5)
736
+ ).strftime('%a, %d %b %Y %H:%M:%S %Z')
737
+
738
+ spec = None
739
+ with open(SPEC_PATH) as f:
740
+ spec = json.load(f)
741
+
742
+ send.return_value = httpx.Response(
743
+ 200,
744
+ json=spec,
745
+ headers={
746
+ "expires": expires
747
+ },
748
+ request=httpx.Request(
749
+ "GET",
750
+ "test",
751
+ ),
752
+ )
753
+
754
+ esi.client
755
+ self.assertIsNotNone(esi.client.Status)