Coverage for product_risk_suite/api/tests.py: 100%
69 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-07-16 14:44 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-07-16 14:44 +0000
1import json
3from django.contrib.auth.models import User
4from django.test import TestCase
5from django.utils import timezone
6from datetime import timedelta
8from guardian.shortcuts import assign_perm
10from .models import APIToken
11from product.models import Product
14class APITokenModelTest(TestCase):
15 def setUp(self):
16 self.owner = User.objects.create_user(username="svc-account", password="unused")
18 def test_authenticate_round_trip(self):
19 raw_token = APIToken.generate_raw_token()
20 token = APIToken.objects.create(
21 name="test token",
22 owner=self.owner,
23 display_prefix=raw_token[:12],
24 hashed_key=APIToken.hash_token(raw_token),
25 )
27 self.assertNotEqual(token.hashed_key, raw_token)
29 authenticated = APIToken.authenticate(raw_token)
30 self.assertEqual(authenticated, token)
31 self.assertIsNotNone(APIToken.objects.get(pk=token.pk).last_used_at)
33 def test_authenticate_rejects_garbage(self):
34 self.assertIsNone(APIToken.authenticate("not-a-real-token"))
35 self.assertIsNone(APIToken.authenticate(""))
37 def test_authenticate_rejects_inactive(self):
38 raw_token = APIToken.generate_raw_token()
39 APIToken.objects.create(
40 name="disabled token",
41 owner=self.owner,
42 display_prefix=raw_token[:12],
43 hashed_key=APIToken.hash_token(raw_token),
44 is_active=False,
45 )
46 self.assertIsNone(APIToken.authenticate(raw_token))
48 def test_authenticate_rejects_expired(self):
49 raw_token = APIToken.generate_raw_token()
50 APIToken.objects.create(
51 name="expired token",
52 owner=self.owner,
53 display_prefix=raw_token[:12],
54 hashed_key=APIToken.hash_token(raw_token),
55 expires_at=timezone.now() - timedelta(days=1),
56 )
57 self.assertIsNone(APIToken.authenticate(raw_token))
60class GraphQLTokenAuthTest(TestCase):
61 def setUp(self):
62 self.owner = User.objects.create_user(username="svc-account", password="unused")
63 self.product = Product.objects.create(title="Token Product", description="d", slug="token-product")
64 self.raw_token = APIToken.generate_raw_token()
65 self.token = APIToken.objects.create(
66 name="ci token",
67 owner=self.owner,
68 display_prefix=self.raw_token[:12],
69 hashed_key=APIToken.hash_token(self.raw_token),
70 )
72 def _query(self, query, auth_header=None):
73 kwargs = {}
74 if auth_header is not None:
75 kwargs["HTTP_AUTHORIZATION"] = auth_header
76 return self.client.post(
77 "/graphql",
78 data=json.dumps({"query": query}),
79 content_type="application/json",
80 **kwargs,
81 )
83 def test_valid_token_with_permission_returns_product(self):
84 assign_perm("product.view_product", self.owner, self.product)
85 response = self._query("{ products { title } }", auth_header=f"Bearer {self.raw_token}")
86 self.assertEqual(response.status_code, 200)
87 titles = [p["title"] for p in response.json()["data"]["products"]]
88 self.assertIn("Token Product", titles)
90 def test_valid_token_without_permission_returns_empty(self):
91 response = self._query("{ products { title } }", auth_header=f"Bearer {self.raw_token}")
92 self.assertEqual(response.status_code, 200)
93 self.assertEqual(response.json()["data"]["products"], [])
95 def test_invalid_token_returns_401(self):
96 response = self._query("{ products { title } }", auth_header="Bearer not-a-real-token")
97 self.assertEqual(response.status_code, 401)
99 def test_expired_token_returns_401(self):
100 self.token.expires_at = timezone.now() - timedelta(days=1)
101 self.token.save()
102 response = self._query("{ products { title } }", auth_header=f"Bearer {self.raw_token}")
103 self.assertEqual(response.status_code, 401)
105 def test_no_auth_header_falls_back_to_anonymous(self):
106 response = self._query("{ products { title } }")
107 self.assertEqual(response.status_code, 200)
108 self.assertEqual(response.json()["data"]["products"], [])
110 def test_session_login_is_unaffected_by_middleware(self):
111 assign_perm("product.view_product", self.owner, self.product)
112 self.client.force_login(self.owner)
113 response = self._query("{ products { title } }", auth_header="Bearer garbage-should-be-ignored")
114 self.assertEqual(response.status_code, 200)
115 titles = [p["title"] for p in response.json()["data"]["products"]]
116 self.assertIn("Token Product", titles)