diff --git a/src/hope_api_auth/admin.py b/src/hope_api_auth/admin.py index 610827e..02711e2 100644 --- a/src/hope_api_auth/admin.py +++ b/src/hope_api_auth/admin.py @@ -25,7 +25,6 @@ Key: {obj.key} Grants: {obj.grants} Expires: {expire} -Business Areas: {areas} Regards diff --git a/src/hope_payment_gateway/apps/fsp/moneygram/client.py b/src/hope_payment_gateway/apps/fsp/moneygram/client.py index b388c23..d436000 100644 --- a/src/hope_payment_gateway/apps/fsp/moneygram/client.py +++ b/src/hope_payment_gateway/apps/fsp/moneygram/client.py @@ -7,7 +7,7 @@ import phonenumbers import requests from phonenumbers import NumberParseException -from urllib3.connectionpool import HTTPSConnectionPool +from urllib3.exceptions import PoolError from hope_payment_gateway.apps.core.models import Singleton from hope_payment_gateway.apps.gateway.flows import PaymentRecordFlow @@ -43,7 +43,7 @@ def get_token(self): try: response = requests.get(url, headers=headers) - except HTTPSConnectionPool: + except PoolError: self.token = None self.token_response = None else: diff --git a/src/hope_payment_gateway/apps/fsp/western_union/endpoints/client.py b/src/hope_payment_gateway/apps/fsp/western_union/endpoints/client.py index 3091f7d..272a7f6 100644 --- a/src/hope_payment_gateway/apps/fsp/western_union/endpoints/client.py +++ b/src/hope_payment_gateway/apps/fsp/western_union/endpoints/client.py @@ -26,7 +26,7 @@ def __init__(self, wsdl_filename) -> None: self.client.set_ns_prefix("xrsi", "http://www.westernunion.com/schema/xrsi") def response_context(self, service_name, payload, wsdl_name=None, port=None): - response = "" + response = dict() error = "" format = "string" try: @@ -63,7 +63,7 @@ def response_context(self, service_name, payload, wsdl_name=None, port=None): code = 400 logger.exception(exc) except Exception as exc: - title = f"{exc.message} [{exc.code}]" + title = type(exc).__name__ code = 400 error = str(exc) logger.exception(exc) diff --git a/src/hope_payment_gateway/apps/fsp/western_union/endpoints/das.py b/src/hope_payment_gateway/apps/fsp/western_union/endpoints/das.py index b73c0a1..14db3d8 100644 --- a/src/hope_payment_gateway/apps/fsp/western_union/endpoints/das.py +++ b/src/hope_payment_gateway/apps/fsp/western_union/endpoints/das.py @@ -106,18 +106,19 @@ def das_delivery_services(destination_country, destination_currency, create_corr client = WesternUnionClient("DAS_Service_H2HService.wsdl") response = client.response_context("DAS_Service", payload, "DAS_Service_H2H", f"SOAP_HTTP_Port_{wu_env}") - context = response["content"]["MTML"]["REPLY"]["DATA_CONTEXT"]["RECORDSET"] - if create_corridors and context: - for ds in context["GETDELIVERYSERVICES"]: - if ds["SVC_CODE"] == "800": - Corridor.objects.get_or_create( - destination_country=destination_country, - destination_currency=destination_currency, - defaults={ - "description": f"{destination_country}: {destination_currency}", - "template_code": ds["TEMPLT"], - }, - ) + if "content" in response and "MTML" in response["content"]: + context = response["content"]["MTML"]["REPLY"]["DATA_CONTEXT"]["RECORDSET"] + if create_corridors and context: + for ds in context["GETDELIVERYSERVICES"]: + if ds["SVC_CODE"] == "800": + Corridor.objects.get_or_create( + destination_country=destination_country, + destination_currency=destination_currency, + defaults={ + "description": f"{destination_country}: {destination_currency}", + "template_code": ds["TEMPLT"], + }, + ) return response @@ -139,7 +140,7 @@ def das_delivery_option_template(destination_country, destination_currency, temp client = WesternUnionClient("DAS_Service_H2HService.wsdl") context = client.response_context("DAS_Service", payload, "DAS_Service_H2H", f"SOAP_HTTP_Port_{wu_env}") - if "content" in context: + if "content" in context and "MTML" in context["content"]: rows = context["content"]["MTML"]["REPLY"]["DATA_CONTEXT"]["RECORDSET"] template = {} structure = [] @@ -170,7 +171,7 @@ def das_delivery_option_template(destination_country, destination_currency, temp else: base[structure[-1]] = [base[structure[-1]], code] if service_provider_code: - sp, created = ServiceProviderCode.objects.get_or_create( + ServiceProviderCode.objects.get_or_create( code=code, description=description, country=destination_country, @@ -193,4 +194,4 @@ def das_delivery_option_template(destination_country, destination_currency, temp service_provider_code = False obj.template = template obj.save() - return context + return context diff --git a/tests/conftest.py b/tests/conftest.py index d69822a..a1e502f 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -2,6 +2,7 @@ import tempfile import pytest +import responses from factories import ( APITokenFactory, CorridorFactory, @@ -31,6 +32,12 @@ def use_override_settings(settings): settings.WESTERN_UNION_BASE_URL = "https://wugateway2pi.westernunion.com/" +@pytest.fixture() +def mocked_responses(): + with responses.RequestsMock(assert_all_requests_are_fired=False) as rsps: + yield rsps + + @pytest.fixture() def user(request, db): return UserFactory() diff --git a/tests/factories.py b/tests/factories.py deleted file mode 100644 index ec4100a..0000000 --- a/tests/factories.py +++ /dev/null @@ -1,138 +0,0 @@ -from django.contrib.auth import get_user_model -from django.contrib.auth.models import Group -from django.db.models import signals -from django.utils import timezone - -import factory -from factory import fuzzy -from strategy_field.utils import fqn - -from hope_api_auth.models import APIToken, Grant -from hope_payment_gateway.apps.core.models import System -from hope_payment_gateway.apps.fsp.western_union.models import Corridor, ServiceProviderCode -from hope_payment_gateway.apps.gateway.models import ( - DeliveryMechanism, - FinancialServiceProvider, - FinancialServiceProviderConfig, - PaymentInstruction, - PaymentRecord, -) -from hope_payment_gateway.apps.gateway.registry import DefaultProcessor - - -@factory.django.mute_signals(signals.post_save) -class UserFactory(factory.django.DjangoModelFactory): - class Meta: - model = get_user_model() - django_get_or_create = ("username",) - - username = factory.Sequence(lambda n: "user%03d" % n) - - last_name = factory.Faker("last_name") - first_name = factory.Faker("first_name") - - email = factory.Sequence(lambda n: "m%03d@mailinator.com" % n) - password = "password" - is_superuser = False - is_active = True - - -class AdminFactory(UserFactory): - is_superuser = True - - -class AnonUserFactory(UserFactory): - username = "anonymous" - - -class GroupFactory(factory.django.DjangoModelFactory): - name = factory.Sequence(lambda n: "name%03d" % n) - - @factory.post_generation - def permissions(self, create, extracted, **kwargs): - if not create: - return # Simple build, do nothing. - - if extracted: - for permission in extracted: # A list of groups were passed in, use them - self.permissions.add(permission) - - class Meta: - model = Group - django_get_or_create = ("name",) - - -class CorridorFactory(factory.django.DjangoModelFactory): - class Meta: - model = Corridor - - -class ServiceProviderCodeFactory(factory.django.DjangoModelFactory): - class Meta: - model = ServiceProviderCode - - -class SystemFactory(factory.django.DjangoModelFactory): - name = fuzzy.FuzzyText() - owner = factory.SubFactory(UserFactory) - - class Meta: - model = System - - -class DeliveryMechanismFactory(factory.django.DjangoModelFactory): - code = fuzzy.FuzzyText() - name = fuzzy.FuzzyText() - - class Meta: - model = DeliveryMechanism - - -class FinancialServiceProviderFactory(factory.django.DjangoModelFactory): - remote_id = fuzzy.FuzzyText() - name = fuzzy.FuzzyText() - vendor_number = fuzzy.FuzzyText() - strategy = fqn(DefaultProcessor) - - class Meta: - model = FinancialServiceProvider - - -class FinancialServiceProviderConfigFactory(factory.django.DjangoModelFactory): - key = fuzzy.FuzzyText() - label = fuzzy.FuzzyText() - fsp = factory.SubFactory(FinancialServiceProviderFactory) - delivery_mechanism = factory.SubFactory(DeliveryMechanismFactory) - - class Meta: - model = FinancialServiceProviderConfig - - -class PaymentInstructionFactory(factory.django.DjangoModelFactory): - fsp = factory.SubFactory(FinancialServiceProviderFactory) - system = factory.SubFactory(SystemFactory) - remote_id = fuzzy.FuzzyText() - - class Meta: - model = PaymentInstruction - - -class PaymentRecordFactory(factory.django.DjangoModelFactory): - remote_id = fuzzy.FuzzyText() - record_code = fuzzy.FuzzyText() - parent = factory.SubFactory(PaymentInstructionFactory) - - class Meta: - model = PaymentRecord - - -class APITokenFactory(factory.django.DjangoModelFactory): - user = factory.SubFactory(UserFactory) - allowed_ips = "" - grants = [Grant.API_READ_ONLY] - valid_from = timezone.now - valid_to = None - - class Meta: - model = APIToken - django_get_or_create = ("user",) diff --git a/tests/factories/__init__.py b/tests/factories/__init__.py new file mode 100644 index 0000000..77b76dd --- /dev/null +++ b/tests/factories/__init__.py @@ -0,0 +1,33 @@ +import importlib +import pkgutil +from pathlib import Path + +from factory.django import DjangoModelFactory +from pytest_factoryboy import register + +from .base import AutoRegisterModelFactory, TAutoRegisterModelFactory, factories_registry +from .payment import * # noqa +from .social import SocialAuthUserFactory # noqa +from .user import GroupFactory, SuperUserFactory, SystemFactory, User, UserFactory # noqa + +for _, name, _ in pkgutil.iter_modules([str(Path(__file__).parent)]): + importlib.import_module(f".{name}", __package__) + + +django_model_factories = {factory._meta.model: factory for factory in DjangoModelFactory.__subclasses__()} + + +def get_factory_for_model( + _model, +) -> type[TAutoRegisterModelFactory] | type[DjangoModelFactory]: + class Meta: + model = _model + + bases = (AutoRegisterModelFactory,) + if _model in factories_registry: + return factories_registry[_model] # noqa + + if _model in django_model_factories: + return django_model_factories[_model] + + return register(type(f"{_model._meta.model_name}AutoCreatedFactory", bases, {"Meta": Meta})) # noqa diff --git a/tests/factories/base.py b/tests/factories/base.py new file mode 100644 index 0000000..31b5bb1 --- /dev/null +++ b/tests/factories/base.py @@ -0,0 +1,19 @@ +import typing + +import factory +from factory.base import FactoryMetaClass + +TAutoRegisterModelFactory = typing.TypeVar("TAutoRegisterModelFactory", bound="AutoRegisterModelFactory") + +factories_registry: dict[str, TAutoRegisterModelFactory] = {} + + +class AutoRegisterFactoryMetaClass(FactoryMetaClass): + def __new__(mcs, class_name, bases, attrs): + new_class = super().__new__(mcs, class_name, bases, attrs) + factories_registry[new_class._meta.model] = new_class + return new_class + + +class AutoRegisterModelFactory(factory.django.DjangoModelFactory, metaclass=AutoRegisterFactoryMetaClass): + pass diff --git a/tests/factories/contenttypes.py b/tests/factories/contenttypes.py new file mode 100644 index 0000000..848bdaf --- /dev/null +++ b/tests/factories/contenttypes.py @@ -0,0 +1,12 @@ +from django.contrib.contenttypes.models import ContentType + +from .base import AutoRegisterModelFactory + + +class ContentTypeFactory(AutoRegisterModelFactory): + app_label = "auth" + model = "user" + + class Meta: + model = ContentType + django_get_or_create = ("app_label", "model") diff --git a/tests/factories/django_auth.py b/tests/factories/django_auth.py new file mode 100644 index 0000000..69a4136 --- /dev/null +++ b/tests/factories/django_auth.py @@ -0,0 +1,32 @@ +from django.contrib.auth.models import Group, Permission + +import factory + +from .base import AutoRegisterModelFactory +from .contenttypes import ContentTypeFactory + + +class PermissionFactory(AutoRegisterModelFactory): + content_type = factory.SubFactory(ContentTypeFactory) + + class Meta: + model = Permission + + +# class GroupFactory(AutoRegisterModelFactory): +# name = factory.Sequence(lambda n: "group %s" % n) +# +# class Meta: +# model = Group +# django_get_or_create = ("name",) +# +# @factory.post_generation +# def permissions(self, create, extracted, **kwargs): +# if not create: +# # Simple build, do nothing. +# return +# +# if extracted: +# # A list of groups were passed in, use them +# for perm in extracted: +# self.permissions.add(perm) diff --git a/tests/factories/django_celery_beat.py b/tests/factories/django_celery_beat.py new file mode 100644 index 0000000..f198a2a --- /dev/null +++ b/tests/factories/django_celery_beat.py @@ -0,0 +1,31 @@ +from django.utils import timezone + +from django_celery_beat.models import SOLAR_SCHEDULES, ClockedSchedule, IntervalSchedule, SolarSchedule +from factory.fuzzy import FuzzyChoice + +from .base import AutoRegisterModelFactory + + +class IntervalScheduleFactory(AutoRegisterModelFactory): + every = 1 + period = IntervalSchedule.HOURS + + class Meta: + model = IntervalSchedule + + +class SolarScheduleFactory(AutoRegisterModelFactory): + event = FuzzyChoice([x[0] for x in SOLAR_SCHEDULES]) + + latitude = 10.1 + longitude = 10.1 + + class Meta: + model = SolarSchedule + + +class ClockedScheduleFactory(AutoRegisterModelFactory): + clocked_time = timezone.now() + + class Meta: + model = ClockedSchedule diff --git a/tests/factories/log.py b/tests/factories/log.py new file mode 100644 index 0000000..15267bd --- /dev/null +++ b/tests/factories/log.py @@ -0,0 +1,11 @@ +from django.contrib.admin.models import LogEntry + +from .base import AutoRegisterModelFactory + + +class LogEntryFactory(AutoRegisterModelFactory): + level = "INFO" + message = "Message for {{ event.name }} on channel {{channel.name}}" + + class Meta: + model = LogEntry diff --git a/tests/factories/payment.py b/tests/factories/payment.py new file mode 100644 index 0000000..c0923b5 --- /dev/null +++ b/tests/factories/payment.py @@ -0,0 +1,106 @@ +from django.utils import timezone + +import factory +from factory import fuzzy +from strategy_field.utils import fqn + +from hope_api_auth.models import APILogEntry, APIToken, Grant +from hope_payment_gateway.apps.fsp.western_union.handlers import WesternUnionHandler +from hope_payment_gateway.apps.fsp.western_union.models import Corridor, ServiceProviderCode +from hope_payment_gateway.apps.gateway.models import ( + DeliveryMechanism, + ExportTemplate, + FinancialServiceProvider, + FinancialServiceProviderConfig, + PaymentInstruction, + PaymentRecord, +) +from hope_payment_gateway.apps.gateway.registry import DefaultProcessor + +from . import AutoRegisterModelFactory +from .user import SystemFactory, UserFactory + + +class CorridorFactory(AutoRegisterModelFactory): + class Meta: + model = Corridor + + +class ServiceProviderCodeFactory(AutoRegisterModelFactory): + class Meta: + model = ServiceProviderCode + + +class DeliveryMechanismFactory(AutoRegisterModelFactory): + code = fuzzy.FuzzyText() + name = fuzzy.FuzzyText() + + class Meta: + model = DeliveryMechanism + + +class FinancialServiceProviderFactory(AutoRegisterModelFactory): + remote_id = fuzzy.FuzzyText() + name = fuzzy.FuzzyText() + vendor_number = fuzzy.FuzzyText() + strategy = fqn(DefaultProcessor) + + class Meta: + model = FinancialServiceProvider + + +class FinancialServiceProviderConfigFactory(AutoRegisterModelFactory): + key = fuzzy.FuzzyText() + label = fuzzy.FuzzyText() + fsp = factory.SubFactory(FinancialServiceProviderFactory) + delivery_mechanism = factory.SubFactory(DeliveryMechanismFactory) + + class Meta: + model = FinancialServiceProviderConfig + + +class PaymentInstructionFactory(AutoRegisterModelFactory): + fsp = factory.SubFactory(FinancialServiceProviderFactory) + system = factory.SubFactory(SystemFactory) + remote_id = fuzzy.FuzzyText() + + class Meta: + model = PaymentInstruction + + +class PaymentRecordFactory(AutoRegisterModelFactory): + remote_id = fuzzy.FuzzyText() + record_code = fuzzy.FuzzyText() + parent = factory.SubFactory(PaymentInstructionFactory) + + class Meta: + model = PaymentRecord + + +class APITokenFactory(AutoRegisterModelFactory): + user = factory.SubFactory(UserFactory) + allowed_ips = "" + grants = [Grant.API_READ_ONLY] + valid_from = timezone.now + valid_to = None + + class Meta: + model = APIToken + django_get_or_create = ("user",) + + +class APILogEntryFactory(AutoRegisterModelFactory): + token = factory.SubFactory(APITokenFactory) + status_code = fuzzy.FuzzyDecimal(200, 599) + + class Meta: + model = APILogEntry + + +class ExportTemplateFactory(AutoRegisterModelFactory): + fsp = factory.SubFactory(FinancialServiceProviderFactory) + delivery_mechanism = factory.SubFactory(DeliveryMechanismFactory) + strategy = fqn(WesternUnionHandler) + + class Meta: + model = ExportTemplate diff --git a/tests/factories/social.py b/tests/factories/social.py new file mode 100644 index 0000000..f703c03 --- /dev/null +++ b/tests/factories/social.py @@ -0,0 +1,12 @@ +from uuid import uuid4 + +import factory +from social_django.models import UserSocialAuth + +from .user import UserFactory + + +class SocialAuthUserFactory(UserFactory): + @factory.post_generation + def sso(obj, create, extracted, **kwargs): + UserSocialAuth.objects.get_or_create(user=obj, provider="test", uid=uuid4()) diff --git a/tests/factories/user.py b/tests/factories/user.py new file mode 100644 index 0000000..4915fd0 --- /dev/null +++ b/tests/factories/user.py @@ -0,0 +1,85 @@ +from django.contrib.auth import get_user_model +from django.contrib.auth.models import Group +from django.db.models import signals + +import factory.fuzzy + +from hope_payment_gateway.apps.core.models import System, User + +from .base import AutoRegisterModelFactory + + +@factory.django.mute_signals(signals.post_save) +class UserFactory(factory.django.DjangoModelFactory): + class Meta: + model = get_user_model() + django_get_or_create = ("username",) + + username = factory.Sequence(lambda n: "user%03d" % n) + + last_name = factory.Faker("last_name") + first_name = factory.Faker("first_name") + + email = factory.Sequence(lambda n: "m%03d@mailinator.com" % n) + password = "password" + is_superuser = False + is_active = True + + +class SystemFactory(AutoRegisterModelFactory): + name = factory.Sequence(lambda n: "System-%03d" % n) + owner = factory.SubFactory(UserFactory) + + class Meta: + model = System + django_get_or_create = ("name",) + + +class UserFactory(AutoRegisterModelFactory): + _password = "password" + username = factory.Sequence(lambda n: "m%03d@example.com" % n) + password = factory.django.Password(_password) + email = factory.Sequence(lambda n: "m%03d@example.com" % n) + + class Meta: + model = User + django_get_or_create = ("username",) + + @classmethod + def _create(cls, model_class, *args, **kwargs): + ret = super()._create(model_class, *args, **kwargs) + ret._password = cls._password + return ret + + +class AdminFactory(UserFactory): + is_superuser = True + + +class AnonUserFactory(UserFactory): + username = "anonymous" + + +class SuperUserFactory(UserFactory): + username = factory.Sequence(lambda n: "superuser%03d@example.com" % n) + email = factory.Sequence(lambda n: "superuser%03d@example.com" % n) + is_superuser = True + is_staff = True + is_active = True + + +class GroupFactory(factory.django.DjangoModelFactory): + name = factory.Sequence(lambda n: "name%03d" % n) + + @factory.post_generation + def permissions(self, create, extracted, **kwargs): + if not create: + return # Simple build, do nothing. + + if extracted: + for permission in extracted: # A list of groups were passed in, use them + self.permissions.add(permission) + + class Meta: + model = Group + django_get_or_create = ("name",) diff --git a/tests/test_admin_smoke.py b/tests/test_admin_smoke.py new file mode 100644 index 0000000..49e15fb --- /dev/null +++ b/tests/test_admin_smoke.py @@ -0,0 +1,203 @@ +from unittest.mock import Mock + +from django.contrib.admin.sites import site +from django.contrib.admin.templatetags.admin_urls import admin_urlname +from django.db.models.options import Options +from django.urls import reverse + +import pytest +from admin_extra_buttons.handlers import ChoiceHandler +from django_regex.utils import RegexList as _RegexList +from factories import SuperUserFactory + +pytestmark = [pytest.mark.admin, pytest.mark.smoke, pytest.mark.django_db] + + +class RegexList(_RegexList): + def extend(self, __iterable) -> None: + for e in __iterable: + self.append(e) + + +GLOBAL_EXCLUDED_MODELS = RegexList( + [ + r"django_celery_beat\.ClockedSchedule", + r"contenttypes\.ContentType", + r"faces\.DummyModel", + "authtoken", + "social_django", + "depot", + "power_query", + "django_celery_beat", + "advanced_filters", + r"core\.User", + ] +) + +GLOBAL_EXCLUDED_BUTTONS = RegexList( + [ + r"social.SocialProviderAdmin:test", + r"western_union.CorridorAdmin:request", + ] +) + +KWARGS = {} + + +def log_submit_error(res): + try: + return f"Submit failed with: {repr(res.context['form'].errors)}" + except KeyError: + return "Submit failed" + + +def pytest_generate_tests(metafunc): + import django + + markers = metafunc.definition.own_markers + excluded_models = RegexList(GLOBAL_EXCLUDED_MODELS) + excluded_buttons = RegexList(GLOBAL_EXCLUDED_BUTTONS) + if "skip_models" in [m.name for m in markers]: + skip_rule = list(filter(lambda m: m.name == "skip_models", markers))[0] + excluded_models.extend(skip_rule.args) + if "skip_buttons" in [m.name for m in markers]: + skip_rule = list(filter(lambda m: m.name == "skip_buttons", markers))[0] + excluded_buttons.extend(skip_rule.args) + django.setup() + if "button_handler" in metafunc.fixturenames: + m = [] + ids = [] + for model, admin in site._registry.items(): + if hasattr(admin, "get_changelist_buttons"): + name = model._meta.object_name + assert admin.urls # we need to force this call + # admin.get_urls() # we need to force this call + buttons = admin.extra_button_handlers.values() + full_name = f"{model._meta.app_label}.{name}" + admin_name = f"{model._meta.app_label}.{admin.__class__.__name__}" + if not (full_name in excluded_models): + for btn in buttons: + tid = f"{admin_name}:{btn.name}" + if tid not in excluded_buttons: + m.append([admin, btn]) + ids.append(tid) + metafunc.parametrize("modeladmin,button_handler", m, ids=ids) + elif "modeladmin" in metafunc.fixturenames: + m = [] + ids = [] + for model, admin in site._registry.items(): + name = model._meta.object_name + full_name = f"{model._meta.app_label}.{name}" + if not (full_name in excluded_models): + m.append(admin) + ids.append(f"{admin.__class__.__name__}:{full_name}") + metafunc.parametrize("modeladmin", m, ids=ids) + + +@pytest.fixture() +def record(db, request): + from factories import get_factory_for_model + + modeladmin = request.getfixturevalue("modeladmin") + instance = modeladmin.model.objects.first() + if not instance: + full_name = f"{modeladmin.model._meta.app_label}.{modeladmin.model._meta.object_name}" + factory = get_factory_for_model(modeladmin.model) + try: + instance = factory(**KWARGS.get(full_name, {})) + except Exception as e: + raise Exception(f"Error creating fixture for {factory} using {KWARGS}") from e + return instance + + +@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 + + +def test_admin_index(app): + url = reverse("admin:index") + + res = app.get(url) + assert res.status_code == 200 + + +@pytest.mark.skip_models( + "constance.Config", +) +def test_admin_changelist(app, modeladmin, record): + url = reverse(admin_urlname(modeladmin.model._meta, "changelist")) + opts: Options = modeladmin.model._meta + res = app.get(url) + assert res.status_code == 200, res.location + assert str(opts.app_config.verbose_name) in str(res.content) + if modeladmin.has_change_permission(Mock(user=app._user)): + assert f"/{record.pk}/change/" in res.body.decode() + + +def show_error(res): + errors = [] + for k, v in dict(res.context["adminform"].form.errors).items(): + errors.append(f'{k}: {"".join(v)}') + return (f"Form submitting failed: {res.status_code}: {errors}",) + + +@pytest.mark.skip_models("constance.Config", "advanced_filters.AdvancedFilter") +def test_admin_changeform(app, modeladmin, record): + opts: Options = modeladmin.model._meta + url = reverse(admin_urlname(opts, "change"), args=[record.pk]) + + res = app.get(url) + assert str(opts.app_config.verbose_name) in res.body.decode() + if modeladmin.has_change_permission(Mock(user=app._user)): + res = res.forms[1].submit() + assert res.status_code in [302, 200] + + +@pytest.mark.skip_models("constance.Config") +def test_admin_add(app, modeladmin): + url = reverse(admin_urlname(modeladmin.model._meta, "add")) + if modeladmin.has_add_permission(Mock(user=app._user)): + res = app.get(url) + res = res.forms[1].submit() + assert res.status_code in [200, 302], log_submit_error(res) + else: + pytest.skip("No 'add' permission") + + +@pytest.mark.skip_models( + "constance.Config", + "hope", +) +def test_admin_delete(app, modeladmin, record, monkeypatch): + url = reverse(admin_urlname(modeladmin.model._meta, "delete"), args=[record.pk]) + if modeladmin.has_delete_permission(Mock(user=app._user)): + res = app.get(url) + res.forms[1].submit() + assert res.status_code in [200, 302] + else: + pytest.skip("No 'delete' permission") + + +@pytest.mark.skip_buttons("security.UserAdmin:link_user_data") +def test_admin_buttons(app, modeladmin, button_handler, record, monkeypatch): + from admin_extra_buttons.handlers import LinkHandler + + if isinstance(button_handler, ChoiceHandler): + pass + elif isinstance(button_handler, LinkHandler): + btn = button_handler.get_button({"original": record}) + button_handler.func(None, btn) + else: + if len(button_handler.sig.parameters) == 2: + url = reverse(f"admin:{button_handler.url_name}") + else: + url = reverse(f"admin:{button_handler.url_name}", args=[record.pk]) + + res = app.get(url) + assert res.status_code in [200, 302]