Skip to content

Commit 725b201

Browse files
committed
Add tests for user permission based API throttling
Signed-off-by: Keshav Priyadarshi <git@keshav.space>
1 parent eb929dc commit 725b201

File tree

3 files changed

+158
-103
lines changed

3 files changed

+158
-103
lines changed

vulnerabilities/tests/test_api.py

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import os
1212
from urllib.parse import quote
1313

14+
from django.core.cache import cache
1415
from django.test import TestCase
1516
from django.test import TransactionTestCase
1617
from django.test.client import RequestFactory
@@ -452,10 +453,12 @@ def add_aliases(vuln, aliases):
452453

453454
class APIPerformanceTest(TestCase):
454455
def setUp(self):
455-
self.user = ApiUser.objects.create_api_user(username="e@mail.com", is_staff=True)
456-
self.auth = f"Token {self.user.auth_token.key}"
456+
# Reset the api throttling to properly test the rate limit on anon users.
457+
# DRF stores throttling state in cache, clear cache to reset throttling.
458+
# See https://www.django-rest-framework.org/api-guide/throttling/#setting-up-the-cache
459+
cache.clear()
460+
457461
self.csrf_client = APIClient(enforce_csrf_checks=True)
458-
self.csrf_client.credentials(HTTP_AUTHORIZATION=self.auth)
459462

460463
# This setup creates the following data:
461464
# vulnerabilities: vul1, vul2, vul3
@@ -503,7 +506,7 @@ def setUp(self):
503506
set_as_fixing(package=self.pkg_2_13_2, vulnerability=self.vul1)
504507

505508
def test_api_packages_all_num_queries(self):
506-
with self.assertNumQueries(4):
509+
with self.assertNumQueries(3):
507510
# There are 4 queries:
508511
# 1. SAVEPOINT
509512
# 2. Authenticating user
@@ -519,22 +522,22 @@ def test_api_packages_all_num_queries(self):
519522
]
520523

521524
def test_api_packages_single_num_queries(self):
522-
with self.assertNumQueries(8):
525+
with self.assertNumQueries(7):
523526
self.csrf_client.get(f"/api/packages/{self.pkg_2_14_0_rc1.id}", format="json")
524527

525528
def test_api_packages_single_with_purl_in_query_num_queries(self):
526-
with self.assertNumQueries(9):
529+
with self.assertNumQueries(8):
527530
self.csrf_client.get(f"/api/packages/?purl={self.pkg_2_14_0_rc1.purl}", format="json")
528531

529532
def test_api_packages_single_with_purl_no_version_in_query_num_queries(self):
530-
with self.assertNumQueries(64):
533+
with self.assertNumQueries(63):
531534
self.csrf_client.get(
532535
f"/api/packages/?purl=pkg:maven/com.fasterxml.jackson.core/jackson-databind",
533536
format="json",
534537
)
535538

536539
def test_api_packages_bulk_search(self):
537-
with self.assertNumQueries(45):
540+
with self.assertNumQueries(44):
538541
packages = [self.pkg_2_12_6, self.pkg_2_12_6_1, self.pkg_2_13_1]
539542
purls = [p.purl for p in packages]
540543

@@ -547,7 +550,7 @@ def test_api_packages_bulk_search(self):
547550
).json()
548551

549552
def test_api_packages_with_lookup(self):
550-
with self.assertNumQueries(14):
553+
with self.assertNumQueries(13):
551554
data = {"purl": self.pkg_2_12_6.purl}
552555

553556
resp = self.csrf_client.post(
@@ -557,7 +560,7 @@ def test_api_packages_with_lookup(self):
557560
).json()
558561

559562
def test_api_packages_bulk_lookup(self):
560-
with self.assertNumQueries(45):
563+
with self.assertNumQueries(44):
561564
packages = [self.pkg_2_12_6, self.pkg_2_12_6_1, self.pkg_2_13_1]
562565
purls = [p.purl for p in packages]
563566

@@ -572,10 +575,12 @@ def test_api_packages_bulk_lookup(self):
572575

573576
class APITestCasePackage(TestCase):
574577
def setUp(self):
575-
self.user = ApiUser.objects.create_api_user(username="e@mail.com", is_staff=True)
576-
self.auth = f"Token {self.user.auth_token.key}"
578+
# Reset the api throttling to properly test the rate limit on anon users.
579+
# DRF stores throttling state in cache, clear cache to reset throttling.
580+
# See https://www.django-rest-framework.org/api-guide/throttling/#setting-up-the-cache
581+
cache.clear()
582+
577583
self.csrf_client = APIClient(enforce_csrf_checks=True)
578-
self.csrf_client.credentials(HTTP_AUTHORIZATION=self.auth)
579584

580585
# This setup creates the following data:
581586
# vulnerabilities: vul1, vul2, vul3
@@ -766,7 +771,7 @@ def test_api_with_wrong_namespace_filter(self):
766771
self.assertEqual(response["count"], 0)
767772

768773
def test_api_with_all_vulnerable_packages(self):
769-
with self.assertNumQueries(4):
774+
with self.assertNumQueries(3):
770775
# There are 4 queries:
771776
# 1. SAVEPOINT
772777
# 2. Authenticating user

vulnerabilities/tests/test_api_v2.py

Lines changed: 37 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -61,10 +61,12 @@ def setUp(self):
6161
)
6262
self.reference2.vulnerabilities.add(self.vuln2)
6363

64-
self.user = ApiUser.objects.create_api_user(username="e@mail.com", is_staff=True)
65-
self.auth = f"Token {self.user.auth_token.key}"
64+
# Reset the api throttling to properly test the rate limit on anon users.
65+
# DRF stores throttling state in cache, clear cache to reset throttling.
66+
# See https://www.django-rest-framework.org/api-guide/throttling/#setting-up-the-cache
67+
cache.clear()
68+
6669
self.client = APIClient(enforce_csrf_checks=True)
67-
self.client.credentials(HTTP_AUTHORIZATION=self.auth)
6870

6971
def test_list_vulnerabilities(self):
7072
"""
@@ -73,7 +75,7 @@ def test_list_vulnerabilities(self):
7375
"""
7476
url = reverse("vulnerability-v2-list")
7577
response = self.client.get(url, format="json")
76-
with self.assertNumQueries(5):
78+
with self.assertNumQueries(4):
7779
response = self.client.get(url, format="json")
7880
self.assertEqual(response.status_code, status.HTTP_200_OK)
7981
self.assertIn("results", response.data)
@@ -88,7 +90,7 @@ def test_retrieve_vulnerability_detail(self):
8890
Test retrieving vulnerability details by vulnerability_id.
8991
"""
9092
url = reverse("vulnerability-v2-detail", kwargs={"vulnerability_id": "VCID-1234"})
91-
with self.assertNumQueries(8):
93+
with self.assertNumQueries(7):
9294
response = self.client.get(url, format="json")
9395
self.assertEqual(response.status_code, status.HTTP_200_OK)
9496
self.assertEqual(response.data["vulnerability_id"], "VCID-1234")
@@ -102,7 +104,7 @@ def test_filter_vulnerability_by_vulnerability_id(self):
102104
Test filtering vulnerabilities by vulnerability_id.
103105
"""
104106
url = reverse("vulnerability-v2-list")
105-
with self.assertNumQueries(4):
107+
with self.assertNumQueries(3):
106108
response = self.client.get(url, {"vulnerability_id": "VCID-1234"}, format="json")
107109
self.assertEqual(response.status_code, status.HTTP_200_OK)
108110
self.assertEqual(response.data["vulnerability_id"], "VCID-1234")
@@ -112,7 +114,7 @@ def test_filter_vulnerability_by_alias(self):
112114
Test filtering vulnerabilities by alias.
113115
"""
114116
url = reverse("vulnerability-v2-list")
115-
with self.assertNumQueries(5):
117+
with self.assertNumQueries(4):
116118
response = self.client.get(url, {"alias": "CVE-2021-5678"}, format="json")
117119
self.assertEqual(response.status_code, status.HTTP_200_OK)
118120
self.assertIn("results", response.data)
@@ -127,7 +129,7 @@ def test_filter_vulnerabilities_multiple_ids(self):
127129
Test filtering vulnerabilities by multiple vulnerability_ids.
128130
"""
129131
url = reverse("vulnerability-v2-list")
130-
with self.assertNumQueries(5):
132+
with self.assertNumQueries(4):
131133
response = self.client.get(
132134
url, {"vulnerability_id": ["VCID-1234", "VCID-5678"]}, format="json"
133135
)
@@ -139,7 +141,7 @@ def test_filter_vulnerabilities_multiple_aliases(self):
139141
Test filtering vulnerabilities by multiple aliases.
140142
"""
141143
url = reverse("vulnerability-v2-list")
142-
with self.assertNumQueries(5):
144+
with self.assertNumQueries(4):
143145
response = self.client.get(
144146
url, {"alias": ["CVE-2021-1234", "CVE-2021-5678"]}, format="json"
145147
)
@@ -152,7 +154,7 @@ def test_invalid_vulnerability_id(self):
152154
Should return 404 Not Found.
153155
"""
154156
url = reverse("vulnerability-v2-detail", kwargs={"vulnerability_id": "VCID-9999"})
155-
with self.assertNumQueries(5):
157+
with self.assertNumQueries(4):
156158
response = self.client.get(url, format="json")
157159
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
158160

@@ -210,18 +212,20 @@ def setUp(self):
210212
self.package1.affected_by_vulnerabilities.add(self.vuln1)
211213
self.package2.fixing_vulnerabilities.add(self.vuln2)
212214

213-
self.user = ApiUser.objects.create_api_user(username="e@mail.com", is_staff=True)
214-
self.auth = f"Token {self.user.auth_token.key}"
215+
# Reset the api throttling to properly test the rate limit on anon users.
216+
# DRF stores throttling state in cache, clear cache to reset throttling.
217+
# See https://www.django-rest-framework.org/api-guide/throttling/#setting-up-the-cache
218+
cache.clear()
219+
215220
self.client = APIClient(enforce_csrf_checks=True)
216-
self.client.credentials(HTTP_AUTHORIZATION=self.auth)
217221

218222
def test_list_packages(self):
219223
"""
220224
Test listing packages without filters.
221225
Should return a list of packages with their details and associated vulnerabilities.
222226
"""
223227
url = reverse("package-v2-list")
224-
with self.assertNumQueries(32):
228+
with self.assertNumQueries(31):
225229
response = self.client.get(url, format="json")
226230
self.assertEqual(response.status_code, status.HTTP_200_OK)
227231
self.assertIn("results", response.data)
@@ -243,7 +247,7 @@ def test_filter_packages_by_purl(self):
243247
Test filtering packages by one or more PURLs.
244248
"""
245249
url = reverse("package-v2-list")
246-
with self.assertNumQueries(20):
250+
with self.assertNumQueries(19):
247251
response = self.client.get(url, {"purl": "pkg:pypi/django@3.2"}, format="json")
248252
self.assertEqual(response.status_code, status.HTTP_200_OK)
249253
self.assertEqual(len(response.data["results"]["packages"]), 1)
@@ -254,7 +258,7 @@ def test_filter_packages_by_affected_vulnerability(self):
254258
Test filtering packages by affected_by_vulnerability.
255259
"""
256260
url = reverse("package-v2-list")
257-
with self.assertNumQueries(20):
261+
with self.assertNumQueries(19):
258262
response = self.client.get(
259263
url, {"affected_by_vulnerability": "VCID-1234"}, format="json"
260264
)
@@ -267,7 +271,7 @@ def test_filter_packages_by_fixing_vulnerability(self):
267271
Test filtering packages by fixing_vulnerability.
268272
"""
269273
url = reverse("package-v2-list")
270-
with self.assertNumQueries(18):
274+
with self.assertNumQueries(17):
271275
response = self.client.get(url, {"fixing_vulnerability": "VCID-5678"}, format="json")
272276
self.assertEqual(response.status_code, status.HTTP_200_OK)
273277
self.assertEqual(len(response.data["results"]["packages"]), 1)
@@ -356,7 +360,7 @@ def test_invalid_vulnerability_filter(self):
356360
Should return an empty list.
357361
"""
358362
url = reverse("package-v2-list")
359-
with self.assertNumQueries(4):
363+
with self.assertNumQueries(3):
360364
response = self.client.get(
361365
url, {"affected_by_vulnerability": "VCID-9999"}, format="json"
362366
)
@@ -369,7 +373,7 @@ def test_invalid_purl_filter(self):
369373
Should return an empty list.
370374
"""
371375
url = reverse("package-v2-list")
372-
with self.assertNumQueries(4):
376+
with self.assertNumQueries(3):
373377
response = self.client.get(
374378
url, {"purl": "pkg:nonexistent/package@1.0.0"}, format="json"
375379
)
@@ -421,7 +425,7 @@ def test_bulk_lookup_with_valid_purls(self):
421425
"""
422426
url = reverse("package-v2-bulk-lookup")
423427
data = {"purls": ["pkg:pypi/django@3.2", "pkg:npm/lodash@4.17.20"]}
424-
with self.assertNumQueries(28):
428+
with self.assertNumQueries(27):
425429
response = self.client.post(url, data, format="json")
426430
self.assertEqual(response.status_code, status.HTTP_200_OK)
427431
self.assertIn("packages", response.data)
@@ -446,7 +450,7 @@ def test_bulk_lookup_with_invalid_purls(self):
446450
"""
447451
url = reverse("package-v2-bulk-lookup")
448452
data = {"purls": ["pkg:pypi/nonexistent@1.0.0", "pkg:npm/unknown@0.0.1"]}
449-
with self.assertNumQueries(4):
453+
with self.assertNumQueries(3):
450454
response = self.client.post(url, data, format="json")
451455
self.assertEqual(response.status_code, status.HTTP_200_OK)
452456
# Since the packages don't exist, the response should be empty
@@ -460,7 +464,7 @@ def test_bulk_lookup_with_empty_purls(self):
460464
"""
461465
url = reverse("package-v2-bulk-lookup")
462466
data = {"purls": []}
463-
with self.assertNumQueries(3):
467+
with self.assertNumQueries(2):
464468
response = self.client.post(url, data, format="json")
465469
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
466470
self.assertIn("error", response.data)
@@ -474,7 +478,7 @@ def test_bulk_search_with_valid_purls(self):
474478
"""
475479
url = reverse("package-v2-bulk-search")
476480
data = {"purls": ["pkg:pypi/django@3.2", "pkg:npm/lodash@4.17.20"]}
477-
with self.assertNumQueries(28):
481+
with self.assertNumQueries(27):
478482
response = self.client.post(url, data, format="json")
479483
self.assertEqual(response.status_code, status.HTTP_200_OK)
480484
self.assertIn("packages", response.data)
@@ -502,7 +506,7 @@ def test_bulk_search_with_purl_only_true(self):
502506
"purls": ["pkg:pypi/django@3.2", "pkg:npm/lodash@4.17.20"],
503507
"purl_only": True,
504508
}
505-
with self.assertNumQueries(17):
509+
with self.assertNumQueries(16):
506510
response = self.client.post(url, data, format="json")
507511
self.assertEqual(response.status_code, status.HTTP_200_OK)
508512
# Since purl_only=True, response should be a list of PURLs
@@ -529,7 +533,7 @@ def test_bulk_search_with_plain_purl_true(self):
529533
"purls": ["pkg:pypi/django@3.2", "pkg:pypi/django@3.2?extension=tar.gz"],
530534
"plain_purl": True,
531535
}
532-
with self.assertNumQueries(16):
536+
with self.assertNumQueries(15):
533537
response = self.client.post(url, data, format="json")
534538
self.assertEqual(response.status_code, status.HTTP_200_OK)
535539
self.assertIn("packages", response.data)
@@ -550,7 +554,7 @@ def test_bulk_search_with_purl_only_and_plain_purl_true(self):
550554
"purl_only": True,
551555
"plain_purl": True,
552556
}
553-
with self.assertNumQueries(11):
557+
with self.assertNumQueries(10):
554558
response = self.client.post(url, data, format="json")
555559
self.assertEqual(response.status_code, status.HTTP_200_OK)
556560
# Response should be a list of plain PURLs
@@ -566,7 +570,7 @@ def test_bulk_search_with_invalid_purls(self):
566570
"""
567571
url = reverse("package-v2-bulk-search")
568572
data = {"purls": ["pkg:pypi/nonexistent@1.0.0", "pkg:npm/unknown@0.0.1"]}
569-
with self.assertNumQueries(4):
573+
with self.assertNumQueries(3):
570574
response = self.client.post(url, data, format="json")
571575
self.assertEqual(response.status_code, status.HTTP_200_OK)
572576
# Since the packages don't exist, the response should be empty
@@ -580,7 +584,7 @@ def test_bulk_search_with_empty_purls(self):
580584
"""
581585
url = reverse("package-v2-bulk-search")
582586
data = {"purls": []}
583-
with self.assertNumQueries(3):
587+
with self.assertNumQueries(2):
584588
response = self.client.post(url, data, format="json")
585589
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
586590
self.assertIn("error", response.data)
@@ -592,7 +596,7 @@ def test_all_vulnerable_packages(self):
592596
Test the 'all' endpoint that returns all vulnerable package URLs.
593597
"""
594598
url = reverse("package-v2-all")
595-
with self.assertNumQueries(4):
599+
with self.assertNumQueries(3):
596600
response = self.client.get(url, format="json")
597601
self.assertEqual(response.status_code, status.HTTP_200_OK)
598602
# Since package1 is vulnerable, it should be returned
@@ -606,7 +610,7 @@ def test_lookup_with_valid_purl(self):
606610
"""
607611
url = reverse("package-v2-lookup")
608612
data = {"purl": "pkg:pypi/django@3.2"}
609-
with self.assertNumQueries(13):
613+
with self.assertNumQueries(12):
610614
response = self.client.post(url, data, format="json")
611615
self.assertEqual(response.status_code, status.HTTP_200_OK)
612616
self.assertEqual(1, len(response.data))
@@ -635,7 +639,7 @@ def test_lookup_with_invalid_purl(self):
635639
"""
636640
url = reverse("package-v2-lookup")
637641
data = {"purl": "pkg:pypi/nonexistent@1.0.0"}
638-
with self.assertNumQueries(4):
642+
with self.assertNumQueries(3):
639643
response = self.client.post(url, data, format="json")
640644
self.assertEqual(response.status_code, status.HTTP_200_OK)
641645
# No packages or vulnerabilities should be returned
@@ -648,7 +652,7 @@ def test_lookup_with_missing_purl(self):
648652
"""
649653
url = reverse("package-v2-lookup")
650654
data = {}
651-
with self.assertNumQueries(3):
655+
with self.assertNumQueries(2):
652656
response = self.client.post(url, data, format="json")
653657
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
654658
self.assertIn("error", response.data)
@@ -662,7 +666,7 @@ def test_lookup_with_invalid_purl_format(self):
662666
"""
663667
url = reverse("package-v2-lookup")
664668
data = {"purl": "invalid_purl_format"}
665-
with self.assertNumQueries(4):
669+
with self.assertNumQueries(3):
666670
response = self.client.post(url, data, format="json")
667671
self.assertEqual(response.status_code, status.HTTP_200_OK)
668672
# No packages or vulnerabilities should be returned

0 commit comments

Comments
 (0)