django-esi 8.0.0a4__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 (48) hide show
  1. {django_esi-8.0.0a4.dist-info → django_esi-8.0.0b2.dist-info}/METADATA +5 -3
  2. {django_esi-8.0.0a4.dist-info → django_esi-8.0.0b2.dist-info}/RECORD +48 -47
  3. esi/__init__.py +1 -1
  4. esi/aiopenapi3/plugins.py +99 -3
  5. esi/clients.py +56 -7
  6. esi/decorators.py +26 -10
  7. esi/exceptions.py +7 -3
  8. esi/helpers.py +38 -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 +188 -44
  39. esi/rate_limiting.py +50 -21
  40. esi/signals.py +21 -0
  41. esi/stubs.pyi +9 -9
  42. esi/tests/__init__.py +33 -11
  43. esi/tests/test_clients.py +77 -19
  44. esi/tests/test_decorators.py +61 -1
  45. esi/tests/test_openapi.json +65 -2
  46. esi/tests/test_openapi.py +512 -18
  47. {django_esi-8.0.0a4.dist-info → django_esi-8.0.0b2.dist-info}/WHEEL +0 -0
  48. {django_esi-8.0.0a4.dist-info → django_esi-8.0.0b2.dist-info}/licenses/LICENSE +0 -0
esi/tests/test_openapi.py CHANGED
@@ -1,22 +1,27 @@
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
6
6
 
7
7
  from esi.openapi_clients import ESIClientProvider
8
+ from django.core.cache import cache
8
9
  from django.utils import timezone
10
+ from esi.tests import NoSocketsTestCase
9
11
  from httpx import RequestError, HTTPStatusError
10
- from esi.exceptions import ESIErrorLimitException
12
+ from esi.exceptions import ESIErrorLimitException, HTTPClientError, HTTPNotModified, ESIBucketLimitException, HTTPServerError
13
+ from esi.rate_limiting import ESIRateLimits
11
14
  from esi import app_settings
12
15
  from esi import __title__, __url__, __version__
13
16
  import httpx
14
17
 
18
+ from . import _generate_token
15
19
  from .. import openapi_clients as oc
16
20
 
17
21
  SPEC_PATH = os.path.join(
18
22
  os.path.dirname(os.path.abspath(__file__)), "test_openapi.json"
19
23
  )
24
+ MODULE_PATH_PLUGINS = 'esi.aiopenapi3.plugins'
20
25
 
21
26
 
22
27
  class TestClientFunctions(TestCase):
@@ -69,28 +74,80 @@ class TestClientFunctions(TestCase):
69
74
 
70
75
 
71
76
  class BuildUserAgentTests(TestCase):
72
- app_name = "TestsApp"
77
+ app_name = "TestApp"
73
78
  app_ver = "1.2.3"
74
79
  app_url = "https://tests.pass"
75
80
 
76
81
  def test_build_user_agent_with_url(self):
77
82
  ua = oc._build_user_agent(self.app_name, self.app_ver, self.app_url)
83
+
84
+ expected_app_name = "TestApp"
85
+ expected_title = "DjangoEsi"
86
+
87
+ self.assertEqual(
88
+ (
89
+ f"{expected_app_name}/{self.app_ver} "
90
+ f"({app_settings.ESI_USER_CONTACT_EMAIL}{f'; +{self.app_url})'} "
91
+ f"{expected_title}/{__version__} (+{__url__})"
92
+ ),
93
+ ua
94
+ )
95
+
96
+ def test_enforce_pascal_case_for_ua_appname_with_space(self):
97
+ """
98
+ Test that the application name is converted to PascalCase in the User-Agent string when it contains spaces.
99
+
100
+ :return:
101
+ :rtype:
102
+ """
103
+
104
+ ua = oc._build_user_agent("test app", self.app_ver, self.app_url)
105
+
106
+ expected_app_name = "TestApp"
107
+ expected_title = "DjangoEsi"
108
+
109
+ self.assertEqual(
110
+ (
111
+ f"{expected_app_name}/{self.app_ver} "
112
+ f"({app_settings.ESI_USER_CONTACT_EMAIL}{f'; +{self.app_url})'} "
113
+ f"{expected_title}/{__version__} (+{__url__})"
114
+ ),
115
+ ua
116
+ )
117
+
118
+ def test_enforce_pascal_case_for_ua_appname_with_hyphen(self):
119
+ """
120
+ Test that the application name is converted to PascalCase in the User-Agent string when it contains hyphens.
121
+
122
+ :return:
123
+ :rtype:
124
+ """
125
+
126
+ ua = oc._build_user_agent("test-app", self.app_ver, self.app_url)
127
+
128
+ expected_app_name = "TestApp"
129
+ expected_title = "DjangoEsi"
130
+
78
131
  self.assertEqual(
79
132
  (
80
- f"{self.app_name}/{self.app_ver} "
133
+ f"{expected_app_name}/{self.app_ver} "
81
134
  f"({app_settings.ESI_USER_CONTACT_EMAIL}{f'; +{self.app_url})'} "
82
- f"{__title__}/{__version__} (+{__url__})"
135
+ f"{expected_title}/{__version__} (+{__url__})"
83
136
  ),
84
137
  ua
85
138
  )
86
139
 
87
140
  def test_build_user_agent_without_url(self):
88
141
  ua = oc._build_user_agent(self.app_name, self.app_ver)
142
+
143
+ expected_app_name = "TestApp"
144
+ expected_title = "DjangoEsi"
145
+
89
146
  self.assertEqual(
90
147
  (
91
- f"{self.app_name}/{self.app_ver} "
148
+ f"{expected_app_name}/{self.app_ver} "
92
149
  f"({app_settings.ESI_USER_CONTACT_EMAIL}) "
93
- f"{__title__}/{__version__} (+{__url__})"
150
+ f"{expected_title}/{__version__} (+{__url__})"
94
151
  ),
95
152
  ua
96
153
  )
@@ -123,6 +180,7 @@ class BaseEsiOperationTests(TestCase):
123
180
  ]
124
181
  self.fake_op.tags = ["test"]
125
182
  self.fake_op.operationId = "fake_op"
183
+ self.fake_op.extensions = {}
126
184
  self.api = MagicMock(app_name="TestApp")
127
185
  self.op = oc.BaseEsiOperation(
128
186
  ("GET", "/fake_op", self.fake_op, {}),
@@ -193,6 +251,19 @@ class EsiOperationTests(TestCase):
193
251
  self.op_mock.parameters = []
194
252
  self.op_mock.tags = ["tag"]
195
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
+ }
196
267
 
197
268
  self.api_mock = MagicMock()
198
269
  self.api_mock.app_name = "TestApp"
@@ -207,6 +278,16 @@ class EsiOperationTests(TestCase):
207
278
  self.api_mock
208
279
  )
209
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
+
210
291
  @patch.object(oc.EsiOperation, "_make_request")
211
292
  def test_result_and_results(self, mock_make_request):
212
293
  data = {"data": "stuff"}
@@ -215,8 +296,37 @@ class EsiOperationTests(TestCase):
215
296
  data_resp = self.op(foo="bar").result()
216
297
  self.assertEqual(data, data_resp)
217
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)
304
+
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()
218
326
 
219
- class TestOpenapiClientProvider(TestCase):
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))
220
330
 
221
331
  def test_compatibilitydate_date_to_string(self):
222
332
  testdate_1 = date(2024, 1, 1)
@@ -227,9 +337,38 @@ class TestOpenapiClientProvider(TestCase):
227
337
 
228
338
  @patch.object(httpx.Client, "send")
229
339
  def test_ua(self, send: MagicMock):
340
+ send.return_value = httpx.Response(
341
+ 200,
342
+ json={
343
+ "players": 1234,
344
+ "server_version": "1234",
345
+ "start_time": "2029-09-19T11:02:08Z"
346
+ },
347
+ request=httpx.Request("GET", "test"),
348
+ )
349
+
350
+ status = self.esi.client.Status.GetStatus().result()
351
+ call_args, call_kwargs = send.call_args
352
+
353
+ expected_app_name = "TestsApp"
354
+ expected_title = 'DjangoEsi'
355
+
356
+ self.assertEqual(
357
+ call_args[0].headers["user-agent"],
358
+ (
359
+ f"{expected_app_name}/{self.app_ver} "
360
+ f"({app_settings.ESI_USER_CONTACT_EMAIL}{f'; +{self.app_url})'} "
361
+ f"{expected_title}/{__version__} (+{__url__})"
362
+ )
363
+ )
364
+ self.assertEqual(status.players, 1234)
365
+
366
+ @patch(MODULE_PATH_PLUGINS + '.settings.DEBUG', True)
367
+ def test_no_tag_no_op_debug(self):
230
368
  app_name = "TestsApp"
231
369
  app_ver = "1.2.3"
232
370
  app_url = "https://tests.pass"
371
+
233
372
  esi = ESIClientProvider(
234
373
  ua_appname=app_name,
235
374
  ua_url=app_url,
@@ -237,6 +376,87 @@ class TestOpenapiClientProvider(TestCase):
237
376
  compatibility_date="2020-01-01",
238
377
  spec_file=SPEC_PATH
239
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
+
388
+ esi = ESIClientProvider(
389
+ ua_appname=app_name,
390
+ ua_url=app_url,
391
+ ua_version=app_ver,
392
+ compatibility_date="2020-01-01",
393
+ tags=["Status"],
394
+ spec_file=SPEC_PATH
395
+ )
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'"
456
+
457
+ expires = (
458
+ timezone.now() + timedelta(minutes=5)
459
+ ).strftime('%a, %d %b %Y %H:%M:%S %Z')
240
460
 
241
461
  send.return_value = httpx.Response(
242
462
  200,
@@ -245,17 +465,291 @@ class TestOpenapiClientProvider(TestCase):
245
465
  "server_version": "1234",
246
466
  "start_time": "2029-09-19T11:02:08Z"
247
467
  },
248
- request=httpx.Request("GET", "test"),
468
+ headers={
469
+ "etag": etag,
470
+ "expires": expires
471
+ },
472
+ request=httpx.Request(
473
+ "GET",
474
+ "test",
475
+ ),
249
476
  )
250
477
 
251
- status = esi.client.Status.GetStatus().result()
252
- call_args, call_kwargs = send.call_args
478
+ self.esi.client.Status.GetStatus().result()
479
+
480
+ with self.assertRaises(HTTPNotModified):
481
+ self.esi.client.Status.GetStatus().result()
482
+
483
+ @patch.object(httpx.Client, "send")
484
+ def test_etag_not_hit_cached(self, send: MagicMock):
485
+ etag = "'123456789abcdef123456789abcdef'"
486
+
487
+ expires = (
488
+ timezone.now() + timedelta(minutes=5)
489
+ ).strftime('%a, %d %b %Y %H:%M:%S %Z')
490
+
491
+ send.return_value = httpx.Response(
492
+ 200,
493
+ json={
494
+ "players": 1234,
495
+ "server_version": "1234",
496
+ "start_time": "2029-09-19T11:02:08Z"
497
+ },
498
+ headers={
499
+ "etag": etag,
500
+ "expires": expires
501
+ },
502
+ request=httpx.Request(
503
+ "GET",
504
+ "test",
505
+ ),
506
+ )
507
+
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')
520
+
521
+ send.return_value = httpx.Response(
522
+ 200,
523
+ json={
524
+ "players": 1234,
525
+ "server_version": "1234",
526
+ "start_time": "2029-09-19T11:02:08Z"
527
+ },
528
+ headers={
529
+ "etag": etag,
530
+ "expires": expires
531
+ },
532
+ request=httpx.Request(
533
+ "GET",
534
+ "test",
535
+ ),
536
+ )
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()
253
656
  self.assertEqual(
254
- call_args[0].headers["user-agent"],
255
- (
256
- f"{app_name}/{app_ver} "
257
- f"({app_settings.ESI_USER_CONTACT_EMAIL}{f'; +{app_url})'} "
258
- f"{__title__}/{__version__} (+{__url__})"
259
- )
657
+ ESIRateLimits.get_bucket(self.esi.client.Status.GetStatus().bucket),
658
+ 598
260
659
  )
261
- self.assertEqual(status.players, 1234)
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)