Skip to content

Commit 313684d

Browse files
Reach 100% test coverage (#211)
1 parent ead1b3e commit 313684d

File tree

7 files changed

+122
-6
lines changed

7 files changed

+122
-6
lines changed

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ black==22.3.0
1919
flake8
2020
flake8-bugbear
2121
flake8-comprehensions
22+
django-test-migrations==1.2.0
2223
isort==5.*
2324
mkdocs==1.3.0
2425
mkdocs-material==8.3.1

setup.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,4 @@ addopts =
1717
--cov=rest_framework_api_key
1818
--cov=tests
1919
--cov-report=term-missing
20-
--cov-fail-under=80
20+
--cov-fail-under=100

src/rest_framework_api_key/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from .__version__ import __version__
44

5-
if django.VERSION < (3, 2):
5+
if django.VERSION < (3, 2): # pragma: no cover
66
default_app_config = "rest_framework_api_key.apps.RestFrameworkApiKeyConfig"
77

88
__all__ = ["__version__", "default_app_config"]

tests/test_admin.py

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ def build_admin_request(rf: RequestFactory) -> HttpRequest:
1717
request = rf.post("/")
1818

1919
def get_response(request: HttpRequest) -> HttpResponse:
20-
raise NotImplementedError # Unused in these tests.
20+
raise NotImplementedError # pragma: no cover # Unused in these tests.
2121

2222
# NOTE: all middleware must be instantiated before
2323
# any middleware can process the request.
@@ -30,6 +30,26 @@ def get_response(request: HttpRequest) -> HttpResponse:
3030
return request
3131

3232

33+
@pytest.mark.django_db
34+
def test_admin_readonly_fields(rf: RequestFactory) -> None:
35+
request = build_admin_request(rf)
36+
37+
admin = APIKeyModelAdmin(APIKey, site)
38+
39+
assert admin.get_readonly_fields(request) == ("prefix",)
40+
41+
api_key = APIKey(name="test")
42+
assert admin.get_readonly_fields(request, obj=api_key) == ("prefix",)
43+
44+
api_key = APIKey(name="test", revoked=True)
45+
assert admin.get_readonly_fields(request, obj=api_key) == (
46+
"prefix",
47+
"name",
48+
"revoked",
49+
"expiry_date",
50+
)
51+
52+
3353
@pytest.mark.django_db
3454
def test_admin_create_api_key(rf: RequestFactory) -> None:
3555
request = build_admin_request(rf)
@@ -58,3 +78,16 @@ def test_admin_create_custom_api_key(rf: RequestFactory) -> None:
5878

5979
messages = get_messages(request)
6080
assert len(messages) == 1
81+
82+
83+
@pytest.mark.django_db
84+
def test_admin_update_api_key(rf: RequestFactory) -> None:
85+
request = build_admin_request(rf)
86+
87+
admin = APIKeyModelAdmin(APIKey, site)
88+
api_key, _ = APIKey.objects.create_key(name="test")
89+
90+
api_key.name = "another-test"
91+
admin.save_model(request, obj=api_key)
92+
refreshed = APIKey.objects.get(pk=api_key.pk)
93+
assert refreshed.name == "another-test"

tests/test_compatibility.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,12 @@
77
@pytest.mark.skipif(
88
django.VERSION < (3, 2), reason="app config is automatically defined by django"
99
)
10-
def test_app_config_not_defined() -> None:
10+
def test_app_config_not_defined() -> None: # pragma: no cover
1111
assert hasattr(rest_framework_api_key, "default_app_config") is False
1212

1313

1414
@pytest.mark.skipif(
1515
django.VERSION >= (3, 2), reason="app config is not automatically defined by django"
1616
)
17-
def test_app_config_defined() -> None:
17+
def test_app_config_defined() -> None: # pragma: no cover
1818
assert hasattr(rest_framework_api_key, "default_app_config") is True

tests/test_migrations.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
from typing import Any, Tuple
2+
3+
import pytest
4+
from django_test_migrations.migrator import Migrator
5+
6+
7+
@pytest.mark.django_db
8+
def test_migrations_0001_initial(migrator: Migrator) -> None:
9+
old_state = migrator.apply_initial_migration(("rest_framework_api_key", None))
10+
11+
with pytest.raises(LookupError):
12+
old_state.apps.get_model("rest_framework_api_key", "APIKey")
13+
14+
new_state = migrator.apply_tested_migration(
15+
("rest_framework_api_key", "0001_initial")
16+
)
17+
APIKey = new_state.apps.get_model("rest_framework_api_key", "APIKey")
18+
assert APIKey.objects.count() == 0
19+
20+
21+
@pytest.mark.django_db
22+
def test_migrations_0004_prefix_hashed_key(migrator: Migrator) -> None:
23+
from django.contrib.auth.hashers import make_password
24+
from django.utils.crypto import get_random_string
25+
26+
def _generate() -> Tuple[str, str]:
27+
# Replicate bejavior before PR #62 (i.e. before v1.4).
28+
prefix = get_random_string(8)
29+
secret_key = get_random_string(32)
30+
key = prefix + "." + secret_key
31+
key_id = prefix + "." + make_password(key)
32+
return key, key_id
33+
34+
def _assign_key(obj: Any) -> None:
35+
# Replicate bejavior before PR #62 (i.e. before v1.4).
36+
_, hashed_key = _generate()
37+
pk = hashed_key
38+
prefix, _, hashed_key = hashed_key.partition(".")
39+
40+
obj.id = pk
41+
obj.prefix = prefix
42+
obj.hashed_key = hashed_key
43+
44+
old_state = migrator.apply_initial_migration(
45+
("rest_framework_api_key", "0003_auto_20190623_1952")
46+
)
47+
48+
APIKey = old_state.apps.get_model("rest_framework_api_key", "APIKey")
49+
50+
# Create a key as it if were created before PR #62 (i.e. before v1.4).
51+
api_key = APIKey.objects.create(name="test")
52+
_assign_key(api_key)
53+
api_key.save()
54+
prefix, _, hashed_key = api_key.id.partition(".")
55+
56+
# Apply migration added by PR #62.
57+
new_state = migrator.apply_tested_migration(
58+
("rest_framework_api_key", "0004_prefix_hashed_key")
59+
)
60+
APIKey = new_state.apps.get_model("rest_framework_api_key", "APIKey")
61+
62+
# Ensure new `prefix`` and `hashed_key` fields were successfully populated.
63+
api_key = APIKey.objects.get(id=api_key.id)
64+
assert api_key.prefix == prefix
65+
assert api_key.hashed_key == hashed_key

tests/test_permissions.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,23 @@ def test_if_invalid_api_key_then_permission_denied(
8989
assert response.status_code == 403
9090

9191

92+
@pytest.mark.parametrize(
93+
"authorization_fmt",
94+
[
95+
pytest.param("X-Key {key}", id="wrong-scheme"),
96+
pytest.param("Api-Key:{key}", id="not-space-separated"),
97+
],
98+
)
99+
def test_if_malformed_authorization_then_permission_denied(
100+
rf: RequestFactory, authorization_fmt: str
101+
) -> None:
102+
_, key = APIKey.objects.create_key(name="test")
103+
authorization = authorization_fmt.format(key=key)
104+
request = rf.get("/test/", HTTP_AUTHORIZATION=authorization)
105+
response = view(request)
106+
assert response.status_code == 403
107+
108+
92109
def test_if_invalid_api_key_custom_header_then_permission_denied(
93110
rf: RequestFactory,
94111
) -> None:
@@ -134,7 +151,7 @@ class View(generics.GenericAPIView):
134151

135152
def get(self, request: Request) -> Response:
136153
self.check_object_permissions(request, object())
137-
return Response()
154+
return Response() # pragma: no cover # Never reached.
138155

139156
view = View.as_view()
140157

0 commit comments

Comments
 (0)