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

1import json 

2 

3from django.contrib.auth.models import User 

4from django.test import TestCase 

5from django.utils import timezone 

6from datetime import timedelta 

7 

8from guardian.shortcuts import assign_perm 

9 

10from .models import APIToken 

11from product.models import Product 

12 

13 

14class APITokenModelTest(TestCase): 

15 def setUp(self): 

16 self.owner = User.objects.create_user(username="svc-account", password="unused") 

17 

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 ) 

26 

27 self.assertNotEqual(token.hashed_key, raw_token) 

28 

29 authenticated = APIToken.authenticate(raw_token) 

30 self.assertEqual(authenticated, token) 

31 self.assertIsNotNone(APIToken.objects.get(pk=token.pk).last_used_at) 

32 

33 def test_authenticate_rejects_garbage(self): 

34 self.assertIsNone(APIToken.authenticate("not-a-real-token")) 

35 self.assertIsNone(APIToken.authenticate("")) 

36 

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)) 

47 

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)) 

58 

59 

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 ) 

71 

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 ) 

82 

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) 

89 

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"], []) 

94 

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) 

98 

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) 

104 

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"], []) 

109 

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)