From 4a3890d553e1c971294e59e2b5103638796345d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gregor=20D=C3=BCster?= Date: Sat, 16 Mar 2024 09:54:15 +0100 Subject: [PATCH 1/6] Add owner column to BankAccount --- pycroft/helpers/printing/__init__.py | 3 ++- .../bc0e0dd480d4_add_bankaccount_owner.py | 26 +++++++++++++++++++ pycroft/model/finance.py | 1 + web/blueprints/finance/__init__.py | 1 + web/blueprints/finance/forms.py | 1 + 5 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 pycroft/model/alembic/versions/bc0e0dd480d4_add_bankaccount_owner.py diff --git a/pycroft/helpers/printing/__init__.py b/pycroft/helpers/printing/__init__.py index 431555ccf..eb0d6e50e 100644 --- a/pycroft/helpers/printing/__init__.py +++ b/pycroft/helpers/printing/__init__.py @@ -31,6 +31,7 @@ class BankAccount(t.Protocol): bank: t.Any iban: t.Any bic: t.Any + owner: t.Any class Building(t.Protocol): @@ -289,7 +290,7 @@ def generate_user_sheet( six monthly contributions at once.'''.format( contribution / 100), style['JustifyText'])) - recipient = 'Studierendenrat TUD - AG DSN' + recipient = bank_account.owner if user.room: purpose = '{id}, {name}, {dorm} {level} {room}'.format( diff --git a/pycroft/model/alembic/versions/bc0e0dd480d4_add_bankaccount_owner.py b/pycroft/model/alembic/versions/bc0e0dd480d4_add_bankaccount_owner.py new file mode 100644 index 000000000..1ea80ecc6 --- /dev/null +++ b/pycroft/model/alembic/versions/bc0e0dd480d4_add_bankaccount_owner.py @@ -0,0 +1,26 @@ +"""add bankaccount owner + +Revision ID: bc0e0dd480d4 +Revises: 55e9f0d9b5f4 +Create Date: 2024-03-16 08:42:48.684471 + +""" + +from alembic import op +import sqlalchemy as sa + +# revision identifiers, used by Alembic. +revision = "bc0e0dd480d4" +down_revision = "55e9f0d9b5f4" +branch_labels = None +depends_on = None + + +def upgrade(): + op.add_column( + "bank_account", sa.Column("owner", sa.String(length=255), nullable=False, server_default="") + ) + + +def downgrade(): + op.drop_column("bank_account", "owner") diff --git a/pycroft/model/finance.py b/pycroft/model/finance.py index e1bb95462..8a26afb57 100644 --- a/pycroft/model/finance.py +++ b/pycroft/model/finance.py @@ -339,6 +339,7 @@ def check_split_on_update(mapper, connection, target): class BankAccount(IntegerIdModel): name: Mapped[str255] bank: Mapped[str255] + owner: Mapped[str255] account_number: Mapped[str] = mapped_column(String(10)) routing_number: Mapped[str] = mapped_column(String(8)) iban: Mapped[str] = mapped_column(String(34)) diff --git a/web/blueprints/finance/__init__.py b/web/blueprints/finance/__init__.py index daff02296..a4856d50d 100644 --- a/web/blueprints/finance/__init__.py +++ b/web/blueprints/finance/__init__.py @@ -447,6 +447,7 @@ def bank_accounts_create() -> ResponseReturnValue: new_bank_account = BankAccount( name=form.name.data, bank=form.bank.data, + owner=form.owner.data, account_number=form.account_number.data, routing_number=form.routing_number.data, iban=form.iban.data, diff --git a/web/blueprints/finance/forms.py b/web/blueprints/finance/forms.py index a6ff1e68b..039725832 100644 --- a/web/blueprints/finance/forms.py +++ b/web/blueprints/finance/forms.py @@ -78,6 +78,7 @@ class MembershipFeeEditForm(MembershipFeeCreateForm): class BankAccountCreateForm(Form): name = TextField("Name") bank = TextField("Bank") + owner = TextField("Kontoinhaber") account_number = TextField("Kontonummer") routing_number = TextField("Bankleitzahl (BLZ)") iban = TextField("IBAN") From 21962fad65cb4b7b2c677cf3d560e2ed94922455 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gregor=20D=C3=BCster?= Date: Fri, 15 Mar 2024 13:30:14 +0100 Subject: [PATCH 2/6] Enable export of non-attributable transfers --- pycroft/lib/finance/__init__.py | 4 ++ pycroft/lib/finance/retransfer.py | 46 +++++++++++++ tests/factories/finance.py | 15 +++-- tests/lib/test_finance.py | 67 ++++++++++++++++++- web/blueprints/finance/__init__.py | 66 +++++++++++++++++- .../bank_account_activities_return.html | 41 ++++++++++++ web/templates/finance/bank_accounts_list.html | 1 + 7 files changed, 231 insertions(+), 9 deletions(-) create mode 100644 pycroft/lib/finance/retransfer.py create mode 100644 web/templates/finance/bank_account_activities_return.html diff --git a/pycroft/lib/finance/__init__.py b/pycroft/lib/finance/__init__.py index 7549aec15..0287fef5b 100644 --- a/pycroft/lib/finance/__init__.py +++ b/pycroft/lib/finance/__init__.py @@ -41,6 +41,10 @@ take_actions_for_payment_in_default_users, get_pid_csv, ) +from .retransfer import ( + get_activities_to_return, + generate_activities_return_sepaxml, +) from .transaction_crud import ( simple_transaction, complex_transaction, diff --git a/pycroft/lib/finance/retransfer.py b/pycroft/lib/finance/retransfer.py new file mode 100644 index 000000000..c043713f9 --- /dev/null +++ b/pycroft/lib/finance/retransfer.py @@ -0,0 +1,46 @@ +from collections.abc import Sequence +from datetime import datetime, timedelta + +from sepaxml import SepaTransfer +from sqlalchemy import select +from sqlalchemy.orm import joinedload, Session + +from pycroft import config +from pycroft.helpers.utc import ensure_tz +from pycroft.model.finance import BankAccountActivity + + +def get_activities_to_return(session: Session) -> Sequence[BankAccountActivity]: + statement = ( + select(BankAccountActivity) + .options(joinedload(BankAccountActivity.bank_account)) + .filter(BankAccountActivity.transaction_id.is_(None)) + .filter(BankAccountActivity.amount > 0) + .filter(BankAccountActivity.imported_at < ensure_tz(datetime.utcnow() - timedelta(days=14))) + ) + + return session.scalars(statement).all() + + +def generate_activities_return_sepaxml(activities: list[BankAccountActivity]) -> bytes: + transfer_config: dict = { + "name": config.membership_fee_bank_account.owner, + "IBAN": config.membership_fee_bank_account.iban, + "BIC": config.membership_fee_bank_account.bic, + "batch": False, + "currency": "EUR", + } + sepa = SepaTransfer(transfer_config, clean=False) + + for activity in activities: + payment = { + "name": activity.other_name, + "IBAN": activity.other_account_number, + "BIC": activity.other_routing_number, + "amount": int(activity.amount * 100), + "execution_date": datetime.now().date(), + "description": f"Rücküberweisung nicht zuordenbarer Überweisung vom {activity.posted_on} mit Referenz {activity.reference}", + } + sepa.add_payment(payment) + + return sepa.export() diff --git a/tests/factories/finance.py b/tests/factories/finance.py index 0df53d555..d3ce48218 100644 --- a/tests/factories/finance.py +++ b/tests/factories/finance.py @@ -34,11 +34,12 @@ class BankAccountFactory(BaseFactory): class Meta: model = BankAccount - name = Faker('word') - bank = Faker('word') - account_number = Faker('random_number', digits=10) - routing_number = Faker('random_number', digits=8) - iban = Faker('iban') + name = Faker("word") + bank = Faker("word") + owner = Faker("word") + account_number = Faker("random_number", digits=10) + routing_number = Faker("random_number", digits=8) + iban = Faker("iban") bic = Faker("swift", length=11) fints_endpoint = Faker('url') account = SubFactory(AccountFactory, type='BANK_ASSET') @@ -51,8 +52,8 @@ class Meta: bank_account = SubFactory(BankAccountFactory) amount = Faker('random_number', digits=5) reference = Sequence(lambda n: f"Reference {n}") - other_account_number = Faker('random_number', digits=10) - other_routing_number = Faker('random_number', digits=8) + other_account_number = Faker("iban") + other_routing_number = Faker("swift") other_name = Faker('word') imported_at = LazyAttribute(lambda o: session.utcnow().date() - timedelta(days=4)) posted_on = LazyAttribute(lambda o: o.imported_at + timedelta(days=1)) diff --git a/tests/lib/test_finance.py b/tests/lib/test_finance.py index 016952c1d..a7bcd49de 100644 --- a/tests/lib/test_finance.py +++ b/tests/lib/test_finance.py @@ -18,7 +18,10 @@ estimate_balance, post_transactions_for_membership_fee, get_users_with_payment_in_default, end_payment_in_default_memberships, - take_actions_for_payment_in_default_users) + take_actions_for_payment_in_default_users, + get_activities_to_return, + generate_activities_return_sepaxml, +) from pycroft.model.finance import ( Transaction, Split, @@ -654,3 +657,65 @@ def test_last_imported_at(self, session: Session): assert finance.get_last_import_date(session) == datetime( 2020, 1, 1, tzinfo=timezone.utc ) + + +class TestReturnNonAttributable: + @pytest.mark.parametrize( + "expected, set_transaction_id, amount_negative, imported_at_old", + [ + (0, False, False, False), # too young + (0, True, False, False), # too young, already attributed + (1, False, False, True), + (0, True, False, True), # already attributed + (0, False, True, False), # negative amount, too young + (0, True, True, False), # negative amount, too young, already attributed + (0, False, True, True), # negative amount + (0, True, True, True), # negative amount, already attributed + ], + ) + def test_activities_to_return( + self, + session: Session, + utcnow, + expected, + set_transaction_id, + amount_negative, + imported_at_old, + ): + kwargs = {} + + if amount_negative: + kwargs["amount"] = -1000 + if imported_at_old: + kwargs["imported_at"] = utcnow.date() - timedelta(days=20) + + activity = BankAccountActivityFactory.create(**kwargs) + + if set_transaction_id: + user = UserFactory.create() + + debit_account = user.account + credit_account = activity.bank_account.account + transaction = finance.simple_transaction( + description=activity.reference, + debit_account=debit_account, + credit_account=credit_account, + amount=activity.amount, + author=user, + valid_on=activity.valid_on, + ) + activity.split = next( + split for split in transaction.splits if split.account_id == credit_account.id + ) + + session.add(activity) + + activities_to_return = get_activities_to_return(session) + + assert len(activities_to_return) == expected + + def test_generate_sepa_xml(self, session: Session, utcnow): + BankAccountActivityFactory.create(imported_at=utcnow.date() - timedelta(days=20)) + BankAccountActivityFactory.create(imported_at=utcnow.date() - timedelta(days=21)) + + generate_activities_return_sepaxml(get_activities_to_return(session)) diff --git a/web/blueprints/finance/__init__.py b/web/blueprints/finance/__init__.py index a4856d50d..9e5e320c8 100644 --- a/web/blueprints/finance/__init__.py +++ b/web/blueprints/finance/__init__.py @@ -11,11 +11,12 @@ """ import typing as t from decimal import Decimal -from collections.abc import Iterable +from collections.abc import Iterable, Sequence from datetime import date from datetime import timedelta, datetime from functools import partial from itertools import zip_longest, chain +from io import BytesIO import wtforms from fints.dialog import FinTSDialogError @@ -34,6 +35,7 @@ request, url_for, make_response, + send_file, ) from flask.typing import ResponseReturnValue from flask_login import current_user @@ -67,6 +69,8 @@ get_system_accounts, ImportedTransactions, match_activities, + get_activities_to_return, + generate_activities_return_sepaxml, get_all_bank_accounts, get_unassigned_bank_account_activities, get_all_mt940_errors, @@ -549,6 +553,66 @@ def bank_account_activities_match() -> ResponseReturnValue: activities_team=matched_activities_team) +class ActivityEntry(t.TypedDict): + bank_account: str + name: str + valid_on: date + reference: str + amount: int + + +@bp.route("/bank-account-activities/return/") +@access.require("finance_change") +def bank_account_activities_return() -> ResponseReturnValue: + field_list: BooleanFieldList = [] + activities: dict[str, ActivityEntry] = {} + + for activity in get_activities_to_return(session): + activities[str(activity.id)] = { + "bank_account": activity.bank_account.name, + "name": activity.other_name, + "valid_on": activity.valid_on, + "reference": activity.reference, + "amount": activity.amount, + } + + field_list.append((str(activity.id), BooleanField(str(activity.id), default=True))) + + form: t.Any = _create_form(field_list) + + return render_template( + "finance/bank_account_activities_return.html", + form=form(), + activities=activities, + ) + + +@bp.route("/bank-account-activities/return/do/", methods=["POST"]) +@access.require("finance_change") +def bank_account_activities_return_do() -> ResponseReturnValue: + field_list: BooleanFieldList = [] + activities_to_return: Sequence[BankAccountActivity] = get_activities_to_return(session) + + for activity in activities_to_return: + field_list.append((str(activity.id), BooleanField(str(activity.id), default=True))) + + form: t.Any = _create_form(field_list)() + + if form.validate_on_submit(): + selected_activities: list[BankAccountActivity] = [ + activity for activity in activities_to_return if form[str(activity.id)].data + ] + + sepa_xml: bytes = generate_activities_return_sepaxml(selected_activities) + + return send_file( + BytesIO(sepa_xml), + as_attachment=True, + download_name=f"non-attributable-transactions-{datetime.now().date()}.xml", + ) + + + class UserMatch(t.TypedDict): purpose: str name: str diff --git a/web/templates/finance/bank_account_activities_return.html b/web/templates/finance/bank_account_activities_return.html new file mode 100644 index 000000000..0afcad2f8 --- /dev/null +++ b/web/templates/finance/bank_account_activities_return.html @@ -0,0 +1,41 @@ +{% extends "layout.html" %} + +{% set page_title = "Unzuordenbare Überweisungen zurücküberweisen" %} + +{% block content %} +
+
+
+ {{ form.csrf_token }} + + + + + + + + + + + {% for field in form %}{% if field.type != 'CSRFTokenField' %} + + + + + + + + + {% endif %}{% endfor %} + +
BankkontoNameGültig amVerwendungszweckBetrag
{{ field }}{{ activities[field.id]["bank_account"] }}{{ activities[field.id]["name"] }}{{ activities[field.id]["valid_on"] }}{{ activities[field.id]["reference"] }}{{ activities[field.id]["amount"] }} €
+
+
+ +
+
+ +
+
+
+{% endblock %} diff --git a/web/templates/finance/bank_accounts_list.html b/web/templates/finance/bank_accounts_list.html index f700c47ea..07b02b18b 100644 --- a/web/templates/finance/bank_accounts_list.html +++ b/web/templates/finance/bank_accounts_list.html @@ -14,6 +14,7 @@
Kontobewegungen matchen + Unzugeordnete Kontobewegungen rücküberweisen {{ bank_account_activity_table.render('bank_accounts_activities') }}
{% endblock %} From 73179be1d62618d659f68048fabffd86d6790706 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gregor=20D=C3=BCster?= Date: Fri, 15 Mar 2024 16:09:37 +0100 Subject: [PATCH 3/6] Add sepaxml stubs --- stubs/sepaxml/__init__.pyi | 4 ++++ stubs/sepaxml/debit.pyi | 9 +++++++++ stubs/sepaxml/shared.pyi | 10 ++++++++++ stubs/sepaxml/transfer.pyi | 9 +++++++++ stubs/sepaxml/utils.pyi | 11 +++++++++++ stubs/sepaxml/validation.pyi | 3 +++ 6 files changed, 46 insertions(+) create mode 100644 stubs/sepaxml/__init__.pyi create mode 100644 stubs/sepaxml/debit.pyi create mode 100644 stubs/sepaxml/shared.pyi create mode 100644 stubs/sepaxml/transfer.pyi create mode 100644 stubs/sepaxml/utils.pyi create mode 100644 stubs/sepaxml/validation.pyi diff --git a/stubs/sepaxml/__init__.pyi b/stubs/sepaxml/__init__.pyi new file mode 100644 index 000000000..150a42131 --- /dev/null +++ b/stubs/sepaxml/__init__.pyi @@ -0,0 +1,4 @@ +from .debit import SepaDD as SepaDD +from .transfer import SepaTransfer as SepaTransfer + +version: str diff --git a/stubs/sepaxml/debit.pyi b/stubs/sepaxml/debit.pyi new file mode 100644 index 000000000..2d05255ac --- /dev/null +++ b/stubs/sepaxml/debit.pyi @@ -0,0 +1,9 @@ +from .shared import SepaPaymentInitn as SepaPaymentInitn +from .utils import ADDRESS_MAPPING as ADDRESS_MAPPING, int_to_decimal_str as int_to_decimal_str, make_id as make_id + +class SepaDD(SepaPaymentInitn): + root_el: str + def __init__(self, config, schema: str = 'pain.008.003.02', clean: bool = True) -> None: ... + def check_config(self, config): ... + def check_payment(self, payment): ... + def add_payment(self, payment) -> None: ... diff --git a/stubs/sepaxml/shared.pyi b/stubs/sepaxml/shared.pyi new file mode 100644 index 000000000..efd208c1a --- /dev/null +++ b/stubs/sepaxml/shared.pyi @@ -0,0 +1,10 @@ +from .utils import decimal_str_to_int as decimal_str_to_int, int_to_decimal_str as int_to_decimal_str, make_msg_id as make_msg_id +from .validation import try_valid_xml as try_valid_xml +from _typeshed import Incomplete + +class SepaPaymentInitn: + schema: Incomplete + msg_id: Incomplete + clean: Incomplete + def __init__(self, config, schema, clean: bool = True) -> None: ... + def export(self, validate: bool = True, pretty_print: bool = False) -> bytes: ... diff --git a/stubs/sepaxml/transfer.pyi b/stubs/sepaxml/transfer.pyi new file mode 100644 index 000000000..4db17622f --- /dev/null +++ b/stubs/sepaxml/transfer.pyi @@ -0,0 +1,9 @@ +from .shared import SepaPaymentInitn as SepaPaymentInitn +from .utils import ADDRESS_MAPPING as ADDRESS_MAPPING, int_to_decimal_str as int_to_decimal_str, make_id as make_id + +class SepaTransfer(SepaPaymentInitn): + root_el: str + def __init__(self, config, schema: str = 'pain.001.001.03', clean: bool = True) -> None: ... + def check_config(self, config): ... + def check_payment(self, payment): ... + def add_payment(self, payment) -> None: ... diff --git a/stubs/sepaxml/utils.pyi b/stubs/sepaxml/utils.pyi new file mode 100644 index 000000000..7fc6509a0 --- /dev/null +++ b/stubs/sepaxml/utils.pyi @@ -0,0 +1,11 @@ +from _typeshed import Incomplete + +using_sysrandom: bool + +def get_rand_string(length: int = 12, allowed_chars: str = '0123456789abcdef'): ... +def make_msg_id(): ... +def make_id(name): ... +def int_to_decimal_str(integer): ... +def decimal_str_to_int(decimal_string): ... + +ADDRESS_MAPPING: Incomplete diff --git a/stubs/sepaxml/validation.pyi b/stubs/sepaxml/validation.pyi new file mode 100644 index 000000000..c2ab88973 --- /dev/null +++ b/stubs/sepaxml/validation.pyi @@ -0,0 +1,3 @@ +class ValidationError(Exception): ... + +def try_valid_xml(xmlout, schema) -> None: ... From 2444c0f8bbfb296bc4d342a187c57f747f444a65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gregor=20D=C3=BCster?= Date: Wed, 8 May 2024 09:26:00 +0200 Subject: [PATCH 4/6] Remove unused finance test data The tests using the data were deleted in 426851d64844f13d5b61221fc9f61a3a96a1de5b --- tests/lib/data_test_finance.csv | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 tests/lib/data_test_finance.csv diff --git a/tests/lib/data_test_finance.csv b/tests/lib/data_test_finance.csv deleted file mode 100644 index beafa8705..000000000 --- a/tests/lib/data_test_finance.csv +++ /dev/null @@ -1,5 +0,0 @@ -"Auftragskonto";"Buchungstag";"Valutadatum";"Buchungstext";"Verwendungszweck";"Beguenstigter/Zahlungspflichtiger";"Kontonummer";"BLZ";"Betrag";"Waehrung";"Info" -"3120219540";"09.04.14";"09.04.14";"KRAM";"BESTELLUNG FUER WAS ANDERES";"KRAM GMBH";"23486452";"84651336";"50,00";"EUR";"Umsatz gebucht" -"3120219540";"29.12.13";"10.01.13";"AUFTRAG";"BESTELLUNG SUPERMEGATOLLER SERVER";"MEDIAMARKT GMBH";"3211234567";"85050300";"-9000,58";"EUR";"Umsatz gebucht" -"3120219540";"02.01.13";"02.01.13";"UEBERWEISUNGSGUTSCHRIFT";"0000-3, SCH, AAA, ZW41D/01 99 1, SS 13";"SCH, AAA";"12345678";"80040400";"9000,00";"EUR";"Umsatz gebucht" -"3120219540";"24.12.12";"24.12.12";"ENTGELTABSCHLUSS";"Pauschalen";"";"9000932103";"85050300";"-6,00";"EUR";"Umsatz gebucht" \ No newline at end of file From d277bb1a3a0a88a41e12abdec97b2b691515397e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gregor=20D=C3=BCster?= Date: Thu, 16 May 2024 11:11:01 +0200 Subject: [PATCH 5/6] Accept url as forbidden if HTTP method not supported --- tests/frontend/assertions.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/frontend/assertions.py b/tests/frontend/assertions.py index b197fe500..32abb3982 100644 --- a/tests/frontend/assertions.py +++ b/tests/frontend/assertions.py @@ -89,9 +89,7 @@ def assert_url_forbidden(self, url: str, method: str = "HEAD", **kw) -> Response __tracebackhide__ = True resp = self.open(url, method=method, **kw) status = resp.status_code - assert ( - status == 403 - ), f"Access to {url} expected to be forbidden, got status {status}" + assert status == 403, f"Access to {url} expected to be forbidden, got status {status}" return resp def assert_forbidden(self, endpoint: str, method: str = "HEAD", **kw) -> Response: From e364506f67cf6a55d7bba643c6cbd9d0a67fbb4a Mon Sep 17 00:00:00 2001 From: Lukas Juhrich Date: Fri, 17 May 2024 18:52:19 +0200 Subject: [PATCH 6/6] tests: Adapt url assertion to respect HTTP method(s) --- tests/frontend/conftest.py | 7 +++++-- tests/frontend/fixture_helpers.py | 2 +- tests/frontend/test_permissions.py | 20 ++++++++++---------- 3 files changed, 16 insertions(+), 13 deletions(-) diff --git a/tests/frontend/conftest.py b/tests/frontend/conftest.py index 8bb29cfbf..3a38046fb 100644 --- a/tests/frontend/conftest.py +++ b/tests/frontend/conftest.py @@ -72,10 +72,13 @@ def config(module_session: Session) -> Config: @pytest.fixture(scope="session") def blueprint_urls(app: PycroftFlask) -> BlueprintUrls: - def _blueprint_urls(blueprint_name: str) -> list[str]: + def _blueprint_urls( + blueprint_name: str, methods: set[str] = {"GET", "POST"} # noqa: B006 + ) -> list[str]: return [ - _build_rule(request_ctx.url_adapter, rule) + (_build_rule(request_ctx.url_adapter, rule), method) for rule in app.url_map.iter_rules() + for method in rule.methods & methods if rule.endpoint.startswith(f"{blueprint_name}.") ] return _blueprint_urls diff --git a/tests/frontend/fixture_helpers.py b/tests/frontend/fixture_helpers.py index 0fca0629e..a8b9aab09 100644 --- a/tests/frontend/fixture_helpers.py +++ b/tests/frontend/fixture_helpers.py @@ -26,7 +26,7 @@ def login_context(test_client: TestClient, login: str, password: str): test_client.get("/logout") -BlueprintUrls: t.TypeAlias = t.Callable[[str], list[str]] +BlueprintUrls: t.TypeAlias = t.Callable[[str], list[str, str]] _argument_creator_map = { IntegerConverter: lambda c: 1, UnicodeConverter: lambda c: "test", diff --git a/tests/frontend/test_permissions.py b/tests/frontend/test_permissions.py index 8904c9a89..9f6a3c3ff 100644 --- a/tests/frontend/test_permissions.py +++ b/tests/frontend/test_permissions.py @@ -104,26 +104,26 @@ def member_logged_in( yield def test_access_user(self, client: TestClient, blueprint_urls: BlueprintUrls): - for url in blueprint_urls("user"): - client.assert_url_forbidden(url) + for url, method in blueprint_urls("user"): + client.assert_url_forbidden(url, method=method) def test_access_finance(self, client: TestClient, blueprint_urls: BlueprintUrls): - for url in blueprint_urls("finance"): - client.assert_url_forbidden(url) + for url, method in blueprint_urls("finance"): + client.assert_url_forbidden(url, method=method) def test_access_buildings(self, client: TestClient, blueprint_urls: BlueprintUrls): - for url in blueprint_urls("facilities"): - client.assert_url_forbidden(url) + for url, method in blueprint_urls("facilities"): + client.assert_url_forbidden(url, method=method) def test_access_infrastructure( self, client: TestClient, blueprint_urls: BlueprintUrls ): - for url in blueprint_urls("infrastructure"): - client.assert_url_forbidden(url) + for url, method in blueprint_urls("infrastructure"): + client.assert_url_forbidden(url, method=method) def test_access_properties(self, client: TestClient, blueprint_urls: BlueprintUrls): - for url in blueprint_urls("properties"): - client.assert_url_forbidden(url) + for url, method in blueprint_urls("properties"): + client.assert_url_forbidden(url, method=method) def test_access_login(self, client: TestClient, blueprint_urls: BlueprintUrls): # Login see Test_010_Anonymous