Skip to content

Commit

Permalink
coverage
Browse files Browse the repository at this point in the history
  • Loading branch information
domdinicola committed Jan 9, 2025
1 parent 990a16a commit 6c710f6
Show file tree
Hide file tree
Showing 23 changed files with 1,279 additions and 576 deletions.
5 changes: 2 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,5 @@ docs/_build/
.pytest_cache
coverage.xml

Pipfile
Pipfile.lock
celerybeat.pid
celerybeat.pid
.cache
17 changes: 17 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,20 @@ repos:
- id: ruff-format
args:
- --check
- repo: https://github.com/adamchainz/djade-pre-commit
rev: 1.3.2
hooks:
- id: djade
args:
- --target-version
- '5.1'
# - repo: https://github.com/saxix/pch
# rev: v0.1
# hooks:
# - id: check-missed-migrations
# args:
# - src
# stages:
# - pre-commit
# additional_dependencies:
# - setuptools
11 changes: 10 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ classifiers = [
"Framework :: Django",
"Framework :: Django :: 3.2",
"Framework :: Django :: 4.2",
"Framework :: Django :: 5.0",
"Framework :: Django :: 5.1",
"Intended Audience :: Developers",
"Programming Language :: Python :: 3.11",
Expand Down Expand Up @@ -56,3 +55,13 @@ dev-dependencies = [

[project.urls]
Homepage = "https://github.com/unicef/unicef-security"


[uv]
package = true

[tool.nitpick]
style = [
"github://unicef/hope-code-conventions@main/django/django.toml"
]
cache = "1 day"
2 changes: 1 addition & 1 deletion ruff.toml
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ ignore = [
"D106", # Missing docstring in public nested class
"D107", # Missing docstring in `__init__`
"D203", # one-blank-line-before-class
# "D212", # multi-line-summary-first-line
"D212", # multi-line-summary-first-line
"D213", # multi-line-summary-second-line
"E731", # lambda-assignment: lambdas are substential in maintenance of py2/3 codebase
"ISC001", # conflicts with ruff format command
Expand Down
26 changes: 18 additions & 8 deletions src/unicef_security/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
logger = logging.getLogger(__name__)


def is_superuser(request, *args, **kwargs):
def is_superuser(request, *args, **kwargs): # pragma: no cover
return request.user.is_superuser


Expand All @@ -31,10 +31,20 @@ class UNICEFUserFilter(SimpleListFilter):
parameter_name = "email"

def lookups(self, request, model_admin):
return [
("unicef", "UNICEF"),
("external", "External"),
]
return (
(
"unicef",
_(
"UNICEF",
),
),
(
"external",
_(
"External",
),
),
)

def queryset(self, request, queryset):
if self.value() == "unicef":
Expand Down Expand Up @@ -114,7 +124,7 @@ def sync_user(self, request, pk):
try:
synchronizer = Synchronizer()
synchronizer.sync_user(obj)
except ValueError as e:
except ValueError as e: # pragma: no cover
self.message_user(request, str(e), messages.ERROR)

self.message_user(request, "User synchronized")
Expand Down Expand Up @@ -147,7 +157,7 @@ def link_user_data(self, request, pk):
ctx["data"] = data
return TemplateResponse(request, "admin/link_user.html", ctx)

except ValueError as e:
except ValueError as e: # pragma: no cover
self.message_user(request, str(e), messages.ERROR)

@button()
Expand Down Expand Up @@ -190,7 +200,7 @@ def ad(self, request, pk):
try:
synchronizer = Synchronizer()
context = synchronizer.get_user(obj.username)
except ValueError as e:
except ValueError as e: # pragma: no cover
self.message_user(request, str(e), messages.ERROR)

return TemplateResponse(request, "admin/ad.html", {"ctx": context, "opts": self.model._meta})
48 changes: 25 additions & 23 deletions src/unicef_security/graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ def default_group(**kwargs):
user.is_staff = True
user.is_superuser = True
user.save()
elif group_name := constance.get("DEFAULT_GROUP"):
elif group_name := constance.DEFAULT_GROUP:
group = Group.objects.filter(name=group_name).first()
if group:
user.groups.add(group)
Expand Down Expand Up @@ -70,7 +70,7 @@ def get_unicef_user(backend, details, response, *args, **kwargs):
for k, v in data.items():
details[k] = v

except (ValueError, KeyError) as e:
except (ValueError, KeyError) as e: # pragma: no cover
logger.error(e)

user, created = User.objects.get_or_create(
Expand All @@ -84,7 +84,7 @@ def get_unicef_user(backend, details, response, *args, **kwargs):
"azure_id": details.get("id"),
},
)
social, __ = UserSocialAuth.objects.get_or_create(user=user, provider=backend.name, uid=user.username)
social, _ = UserSocialAuth.objects.get_or_create(user=user, provider=backend.name, uid=user.username)
user.social_user = social
return {"user": user, "social": social, "uid": details.get("id"), "is_new": created}

Expand Down Expand Up @@ -113,9 +113,9 @@ def log(self, *result):
def __add__(self, other):
if isinstance(other, SyncResult):
ret = SyncResult()
ret.created.extend(other.created)
ret.updated.extend(other.updated)
ret.skipped.extend(other.skipped)
ret.created = self.created + other.created
ret.updated = self.updated + other.updated
ret.skipped = self.skipped + other.skipped
return ret
raise ValueError("Cannot add %s to SyncResult object" % type(other))

Expand Down Expand Up @@ -147,7 +147,7 @@ def __init__(self, user_model=None, mapping=None, echo=None, identifier=None, se
self.echo = echo or (lambda lmn: True)

def get_token(self):
if not self.id and self.secret:
if not self.id and self.secret: # pragma: no cover
raise ValueError("Configure AZURE_CLIENT_ID and/or AZURE_CLIENT_SECRET")
post_dict = {
"grant_type": "client_credentials",
Expand Down Expand Up @@ -175,18 +175,18 @@ def get_page(self, url, single=False):
headers = {"Authorization": f"Bearer {self.get_token()}"}
try:
response = requests.get(url, headers=headers, timeout=60)
if response.status_code == 401:
if response.status_code == 401: # pragma: no cover
data = response.json()
if data["error"]["message"] == "Access token has expired.":
continue
raise ConnectionError(f"400: Error processing the response {response.content}")

if response.status_code != 200:
if response.status_code != 200: # pragma: no cover
raise ConnectionError(
f"Code {response.status_code}. " f"Error processing the response {response.content}"
f"Code {response.status_code}. Error processing the response {response.content}"
)
break
except ConnectionError as e:
except ConnectionError as e: # pragma: no cover
logger.exception(e)
raise

Expand All @@ -210,9 +210,7 @@ def __iter__(self):
values = self.get_page(self.next_link)
logger.debug(f"fetched page {pages}")
pages += 1
except KeyboardInterrupt:
break
except BaseException as e:
except GeneratorExit as e:
logger.exception(e)
break

Expand All @@ -221,19 +219,23 @@ def get_record(self, user_info: dict) -> (dict, dict):
pk = {fieldname: data.pop(fieldname) for fieldname in self.user_pk_fields}
return pk, data

def fetch_users(self, filter_params, callback=None):
def fetch_users(self, filter_params, max_records=None, callback=None):
self.startUrl = "%s?$filter=%s" % (self._baseurl, filter_params)
return self.synchronize(callback=callback)
return self.synchronize(max_records=max_records, callback=callback)

def search_users(self, record):
url = "%s?$filter=" % self._baseurl
filters = []
if record.email:
filters.append("mail eq '%s'" % record.email)
if record.last_name:
filters.append("surname eq '%s'" % record.last_name)
if record.first_name:
filters.append("givenName eq '%s'" % record.first_name)
field_map = {
"email": "mail eq '{value}'",
"last_name": "surname eq '{value}'",
"first_name": "givenName eq '{value}'",
}

for field, filter_template in field_map.items():
value = getattr(record, field, None)
if value:
filters.append(filter_template.format(value=value))

page = self.get_page(url + " or ".join(filters), single=True)
return page["value"]
Expand Down Expand Up @@ -284,7 +286,7 @@ def synchronize(self, max_records=None, callback=None):
results.log(user_info)
if max_records and i > max_records:
break
except BaseException as e:
except BaseException as e: # pragma: no cover
logger.exception(e)
raise
logger.debug(f"End Azure user synchronization: {results}")
Expand Down
2 changes: 1 addition & 1 deletion tests/.coveragerc
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ source = unicef_security
omit = *migrations*

[report]
fail_under = 20
fail_under = 90
ignore_errors = True
exclude_lines =
pragma: no cover
Expand Down
11 changes: 10 additions & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import pytest

from .factories import UserFactory
from .factories import UserFactory, SuperUserFactory


@pytest.fixture
Expand All @@ -14,3 +14,12 @@ def mocked_responses():
@pytest.fixture
def auth_user():
return UserFactory()


@pytest.fixture
def app(django_app_factory, mocked_responses):
django_app = django_app_factory(csrf_checks=False)
admin_user = SuperUserFactory(username="superuser")
django_app.set_user(admin_user)
django_app._user = admin_user
return django_app
4 changes: 3 additions & 1 deletion tests/demoproject/demo/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,9 +90,11 @@
AUTH_USER_MODEL = "demo.User"

CONSTANCE_CONFIG = {
"DEFAULT_GROUP": ("", "test_grp"),
"DEFAULT_GROUP": ("test_grp", "Test Group"),
}

CONSTANCE_BACKEND = "constance.backends.database.DatabaseBackend"

LOGOUT_URL = "/"

ADMINS = (("Me", "[email protected]"),)
62 changes: 59 additions & 3 deletions tests/test_admin.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,63 @@
from django.urls import reverse
from mock import patch
import pytest

from demo.admin import UserPlus
from demo.models import User
from unicef_security.admin import UNICEFUserFilter
from .factories import UserFactory

def test_changelist(django_app, admin_user):
url = reverse("admin:demo_user_changelist")
res = django_app.get(url, user=admin_user)

@pytest.mark.django_db
@patch("unicef_security.admin.Synchronizer.get_token")
@patch("unicef_security.admin.Synchronizer.get_user")
@patch("unicef_security.admin.Synchronizer.sync_user")
@patch("unicef_security.admin.Synchronizer.search_users")
def test_link_user_data(patch1, patch2, patch3, patch4, app):
user = UserFactory()
url = reverse("admin:demo_user_link_user_data", args=[user.pk])
res = app.post(url)
assert res.status_code == 200


@pytest.mark.django_db
@patch("unicef_security.admin.Synchronizer.get_token")
@patch("unicef_security.admin.Synchronizer.fetch_users")
def test_link_load(patch1, patch2, app):
url = reverse("admin:demo_user_load")
res = app.post(url)
assert res.status_code == 200


@pytest.mark.django_db
def test_unicef_admin_filter():
UserFactory(email="[email protected]", username="[email protected]")
UserFactory(email="[email protected]", username="[email protected]")
qs = User.objects.all()
assert qs.count() == 2

filters = UNICEFUserFilter(
None,
{
"email": [
"unicef",
]
},
User,
UserPlus,
)
unicef_records = filters.queryset(None, qs)
assert unicef_records.count() == 1

filters = UNICEFUserFilter(
None,
{
"email": [
"external",
]
},
User,
UserPlus,
)
external_records = filters.queryset(None, qs)
assert external_records.count() == 1
Loading

0 comments on commit 6c710f6

Please sign in to comment.