From a7d5fc9ec679d63eb4d42203227fbb500a784a09 Mon Sep 17 00:00:00 2001 From: Lukas Juhrich Date: Sat, 1 Jul 2023 13:10:22 +0200 Subject: [PATCH 01/18] [tests] test `subnets` and `subnets_json` #643 --- tests/frontend/test_infrastructure.py | 29 +++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/tests/frontend/test_infrastructure.py b/tests/frontend/test_infrastructure.py index b9cb1efb9..4cabb94d1 100644 --- a/tests/frontend/test_infrastructure.py +++ b/tests/frontend/test_infrastructure.py @@ -3,11 +3,15 @@ # the Apache License, Version 2.0. See the LICENSE file for details. import pytest +from ipaddr import IPv4Network, IPv4Address from sqlalchemy.orm import Session from flask import url_for from pycroft.model.host import Switch +from pycroft.model.net import Subnet +from tests import factories as f from tests.factories import SwitchFactory +from web.blueprints.infrastructure import format_address_range from .assertions import TestClient @@ -16,6 +20,31 @@ def client(module_test_client: TestClient) -> TestClient: return module_test_client +def test_format_empty_address_range(): + with pytest.raises(ValueError): + format_address_range(IPv4Address("141.30.228.39"), amount=0) + + +@pytest.mark.usefixtures("admin_logged_in", "session") +class TestSubnets: + @pytest.fixture(scope="class", autouse=True) + def subnets(self, class_session: Session) -> list[Subnet]: + return f.SubnetFactory.create_batch(3) + [ + f.SubnetFactory(reserved_addresses_bottom=1, reserved_addresses_top=5), + f.SubnetFactory(reserved_addresses_bottom=5, reserved_addresses_top=1), + f.SubnetFactory(address=IPv4Network("141.30.228.1/32")), + ] + + def test_subnets(self, client): + with client.renders_template("infrastructure/subnets_list.html"): + client.assert_url_ok(url_for("infrastructure.subnets")) + + def test_subnets_json(self, client): + response = client.assert_url_ok(url_for("infrastructure.subnets_json")) + assert "items" in (j := response.json) + assert len(j["items"]) == 6 + + @pytest.mark.usefixtures("admin_logged_in") class TestSwitch: @pytest.fixture(scope="class") From 53df518f3e12a9a1d5d433d624980b9e7fed1506 Mon Sep 17 00:00:00 2001 From: Lukas Juhrich Date: Sat, 1 Jul 2023 13:14:23 +0200 Subject: [PATCH 02/18] [tests] Test 404 case for switch show endpoints #643 --- tests/frontend/test_infrastructure.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/frontend/test_infrastructure.py b/tests/frontend/test_infrastructure.py index 4cabb94d1..f3f07f0f5 100644 --- a/tests/frontend/test_infrastructure.py +++ b/tests/frontend/test_infrastructure.py @@ -57,12 +57,25 @@ def test_list_switches(self, client: TestClient): with client.renders_template("infrastructure/switches_list.html"): client.assert_url_ok(url_for("infrastructure.switches")) + def test_show_nonexistent_switch(self, client: TestClient): + with client.flashes_message("nicht gefunden", "error"): + client.assert_url_redirects( + url_for("infrastructure.switch_show", switch_id=999), + expected_location=url_for("infrastructure.switches"), + ) + def test_show_switch(self, client: TestClient, switch: Switch): with client.renders_template("infrastructure/switch_show.html"): client.assert_url_ok( url_for("infrastructure.switch_show", switch_id=switch.host_id) ) + def test_show_nonexistent_switch_table(self, client: TestClient): + client.assert_url_response_code( + url_for("infrastructure.switch_show_json", switch_id=999), + code=404, + ) + def test_show_switch_table(self, client: TestClient, switch: Switch): response = client.assert_url_ok( url_for("infrastructure.switch_show_json", switch_id=switch.host_id) From d7e8a1235867ff02415d6e93dea08fab4d961329 Mon Sep 17 00:00:00 2001 From: Lukas Juhrich Date: Sat, 1 Jul 2023 13:17:18 +0200 Subject: [PATCH 03/18] [tests] test `switches_json` #643 --- tests/frontend/test_infrastructure.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/frontend/test_infrastructure.py b/tests/frontend/test_infrastructure.py index f3f07f0f5..36b73065c 100644 --- a/tests/frontend/test_infrastructure.py +++ b/tests/frontend/test_infrastructure.py @@ -57,6 +57,15 @@ def test_list_switches(self, client: TestClient): with client.renders_template("infrastructure/switches_list.html"): client.assert_url_ok(url_for("infrastructure.switches")) + def test_switches_json(self, client: TestClient, switch): + response = client.assert_url_ok(url_for("infrastructure.switches_json")) + assert "items" in (j := response.json) + assert len(j["items"]) == 1 + [it] = j["items"] + assert it["id"] == switch.host_id + assert "edit_link" in it + assert "delete_link" in it + def test_show_nonexistent_switch(self, client: TestClient): with client.flashes_message("nicht gefunden", "error"): client.assert_url_redirects( From d368f243ba40be46cb29ac69498020858f3d5147 Mon Sep 17 00:00:00 2001 From: Lukas Juhrich Date: Sat, 1 Jul 2023 13:31:52 +0200 Subject: [PATCH 04/18] [tests] test `switch_create` #643 --- tests/frontend/test_infrastructure.py | 48 +++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/tests/frontend/test_infrastructure.py b/tests/frontend/test_infrastructure.py index 36b73065c..b30d1f0dc 100644 --- a/tests/frontend/test_infrastructure.py +++ b/tests/frontend/test_infrastructure.py @@ -7,6 +7,7 @@ from sqlalchemy.orm import Session from flask import url_for +from pycroft.model.facilities import Room from pycroft.model.host import Switch from pycroft.model.net import Subnet from tests import factories as f @@ -91,3 +92,50 @@ def test_show_switch_table(self, client: TestClient, switch: Switch): ) assert "items" in (j := response.json) assert len(j["items"]) == 1 + + +@pytest.mark.usefixtures("admin_logged_in", "session") +class TestCreateSwitch: + @pytest.fixture(scope="class") + def room(self, class_session: Session) -> Room: + return f.RoomFactory() + + def test_create_switch_get(self, client): + with client.renders_template("generic_form.html"): + client.assert_url_ok(url_for("infrastructure.switch_create")) + + def test_create_switch_post_no_data(self, client): + with client.renders_template("generic_form.html"): + client.assert_url_ok( + url_for("infrastructure.switch_create"), + data={}, + method="POST", + ) + + def test_create_switch_post_invalid_data(self, client): + with client.renders_template("generic_form.html"): + client.assert_url_ok( + url_for("infrastructure.switch_create"), + # data according to `class SwitchForm` + data={ + "name": "Test Switch", + "management_ip": "10.10.10.2", + # room number missing + }, + method="POST", + ) + + def test_create_switch_post_valid_data(self, client, room): + with client.flashes_message("erfolgreich erstellt", "success"): + client.assert_url_redirects( + url_for("infrastructure.switch_create"), + # data according to `class SwitchForm` + data={ + "name": "Test Switch", + "management_ip": "10.10.10.2", + "room_number": room.number, + "level": room.level, + "building": room.building_id, + }, + method="POST", + ) From 0c7ab3bdfc6e99fd20f7f30fd456b81e8274fbd7 Mon Sep 17 00:00:00 2001 From: Lukas Juhrich Date: Sun, 2 Jul 2023 11:12:06 +0200 Subject: [PATCH 05/18] [tests] test `switch_edit` endpoint #643 The `test_edit_nonexistent_switch` test fails due to a bug thus uncovered. --- tests/frontend/test_infrastructure.py | 66 ++++++++++++++++++++++++--- 1 file changed, 59 insertions(+), 7 deletions(-) diff --git a/tests/frontend/test_infrastructure.py b/tests/frontend/test_infrastructure.py index b30d1f0dc..228c90f36 100644 --- a/tests/frontend/test_infrastructure.py +++ b/tests/frontend/test_infrastructure.py @@ -11,11 +11,17 @@ from pycroft.model.host import Switch from pycroft.model.net import Subnet from tests import factories as f -from tests.factories import SwitchFactory from web.blueprints.infrastructure import format_address_range from .assertions import TestClient +@pytest.fixture(scope="module") +def switch(module_session: Session) -> Switch: + switch = f.SwitchFactory() + module_session.flush() + return switch + + @pytest.fixture(scope="module") def client(module_test_client: TestClient) -> TestClient: return module_test_client @@ -48,12 +54,6 @@ def test_subnets_json(self, client): @pytest.mark.usefixtures("admin_logged_in") class TestSwitch: - @pytest.fixture(scope="class") - def switch(self, class_session: Session) -> Switch: - switch = SwitchFactory() - class_session.flush() - return switch - def test_list_switches(self, client: TestClient): with client.renders_template("infrastructure/switches_list.html"): client.assert_url_ok(url_for("infrastructure.switches")) @@ -139,3 +139,55 @@ def test_create_switch_post_valid_data(self, client, room): }, method="POST", ) + + +@pytest.mark.usefixtures("admin_logged_in", "session") +class TestSwitchEdit: + def test_edit_nonexistent_switch(self, client): + with client.flashes_message("nicht gefunden", category="error"): + client.assert_url_redirects( + url_for("infrastructure.switch_edit", switch_id=999), + expected_location=url_for("infrastructure.switches"), + ) + + def test_edit_switch_get(self, client, switch): + with client.renders_template("generic_form.html"): + client.assert_url_ok( + url_for("infrastructure.switch_edit", switch_id=switch.host_id), + ) + + def test_edit_switch_post_no_data(self, client, switch): + with client.renders_template("generic_form.html"): + client.assert_url_ok( + url_for("infrastructure.switch_edit", switch_id=switch.host_id), + data={}, + method="POST", + ) + + def test_edit_switch_post_invalid_data(self, client, switch): + with client.renders_template("generic_form.html"): + client.assert_url_ok( + url_for("infrastructure.switch_edit", switch_id=switch.host_id), + # data according to `class SwitchForm` + data={ + "name": "Test Switch", + "management_ip": "This is not an IP", + # room number missing + }, + method="POST", + ) + + def test_edit_switch_post_valid_data(self, client, switch): + with client.flashes_message("erfolgreich bearbeitet", "success"): + client.assert_url_redirects( + url_for("infrastructure.switch_edit", switch_id=switch.host_id), + # data according to `class SwitchForm` + data={ + "name": "Test Switch (now with new name)", + "management_ip": "10.10.10.3", + "room_number": switch.host.room.number, + "level": switch.host.room.level, + "building": switch.host.room.building_id, + }, + method="POST", + ) From 0516ef5a0103459d28fd022a637118604ac494ed Mon Sep 17 00:00:00 2001 From: Lukas Juhrich Date: Sun, 2 Jul 2023 11:16:54 +0200 Subject: [PATCH 06/18] [web] Fix previously uncovered bug in `switch_edit` Relates to #643 --- web/blueprints/infrastructure/__init__.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/web/blueprints/infrastructure/__init__.py b/web/blueprints/infrastructure/__init__.py index 8a2ce205a..5219d346b 100644 --- a/web/blueprints/infrastructure/__init__.py +++ b/web/blueprints/infrastructure/__init__.py @@ -197,9 +197,8 @@ def switch_create(): @bp.route('/switch//edit', methods=['GET', 'POST']) @access.require('infrastructure_change') def switch_edit(switch_id): - switch = Switch.q.filter_by(host_id=switch_id).one() - - if not switch: + sess = session.session + if not (switch := sess.get(Switch, switch_id)): flash(f"Switch mit ID {switch_id} nicht gefunden!", "error") return redirect(url_for('.switches')) @@ -207,7 +206,6 @@ def switch_edit(switch_id): level=switch.host.room.level, room_number=switch.host.room.number) if form.validate_on_submit(): - sess = session.session room = Room.q.filter_by(number=form.room_number.data, level=form.level.data, building=form.building.data).one() From 072e4d3f5f65fcbf03810709540d6f6428aecd8a Mon Sep 17 00:00:00 2001 From: Lukas Juhrich Date: Sun, 2 Jul 2023 11:27:39 +0200 Subject: [PATCH 07/18] [tests] Test `switch_delete` #643 Once again, `test_delete_nonexistent_switch` uncovered a bug similar to the one before.. --- tests/frontend/test_infrastructure.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/tests/frontend/test_infrastructure.py b/tests/frontend/test_infrastructure.py index 228c90f36..04dd3df72 100644 --- a/tests/frontend/test_infrastructure.py +++ b/tests/frontend/test_infrastructure.py @@ -191,3 +191,26 @@ def test_edit_switch_post_valid_data(self, client, switch): }, method="POST", ) + + +@pytest.mark.usefixtures("admin_logged_in", "session") +class TestSwitchDelete: + def test_delete_nonexistent_switch(self, client): + with client.flashes_message("nicht gefunden", category="error"): + client.assert_url_redirects( + url_for("infrastructure.switch_delete", switch_id=999), + expected_location=url_for("infrastructure.switches"), + ) + + def test_delete_switch_get(self, client, switch): + with client.renders_template("generic_form.html"): + client.assert_url_ok( + url_for("infrastructure.switch_delete", switch_id=switch.host_id), + ) + + def test_delete_switch_post(self, client, switch): + with client.flashes_message("erfolgreich gelöscht", "success"): + client.assert_url_redirects( + url_for("infrastructure.switch_delete", switch_id=switch.host_id), + method="POST", + ) From 05c35ba78085404dc83e2f4a0e5cf58e837519e0 Mon Sep 17 00:00:00 2001 From: Lukas Juhrich Date: Sun, 2 Jul 2023 11:37:46 +0200 Subject: [PATCH 08/18] [web] Don't run `session.begin()` in web endpointoint The session has `autobegin` behavior, and is begun explicitly outside of the request when testing; therefore, `.begin` fails in this scenario. Refs #643 --- web/blueprints/infrastructure/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/blueprints/infrastructure/__init__.py b/web/blueprints/infrastructure/__init__.py index 5219d346b..fbce6e9a4 100644 --- a/web/blueprints/infrastructure/__init__.py +++ b/web/blueprints/infrastructure/__init__.py @@ -238,8 +238,8 @@ def switch_delete(switch_id): if form.validate_on_submit(): sess = session.session - with sess.begin(): - delete_switch(sess, switch, current_user) + delete_switch(sess, switch, current_user) + sess.commit() flash("Die Switch wurde erfolgreich gelöscht.", "success") return redirect(url_for('.switches')) From 9c49c12d470c19e0ebb1ad3ef279041b89418416 Mon Sep 17 00:00:00 2001 From: Lukas Juhrich Date: Sun, 2 Jul 2023 11:41:07 +0200 Subject: [PATCH 09/18] [web] fix previously uncovered bug in `switch_delete` Relates to #643 --- web/blueprints/infrastructure/__init__.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/web/blueprints/infrastructure/__init__.py b/web/blueprints/infrastructure/__init__.py index fbce6e9a4..47845639b 100644 --- a/web/blueprints/infrastructure/__init__.py +++ b/web/blueprints/infrastructure/__init__.py @@ -228,16 +228,14 @@ def switch_edit(switch_id): @bp.route('/switch//delete', methods=['GET', 'POST']) @access.require('infrastructure_change') def switch_delete(switch_id): - switch = Switch.q.filter_by(host_id=switch_id).one() - - if not switch: + sess = session.session + if not (switch := sess.get(Switch, switch_id)): flash(f"Switch mit ID {switch_id} nicht gefunden!", "error") return redirect(url_for('.switches')) form = Form() if form.validate_on_submit(): - sess = session.session delete_switch(sess, switch, current_user) sess.commit() flash("Die Switch wurde erfolgreich gelöscht.", "success") From b5666ae96e9cb0c2ad75bd81a0a4944ef7ed9a6a Mon Sep 17 00:00:00 2001 From: Lukas Juhrich Date: Sun, 2 Jul 2023 16:58:06 +0200 Subject: [PATCH 10/18] [tests] test `switch_port_create` #643 This required doing the `rollback` on a nested session (i.e., a `SessionTransaction`) instead of the outer one. --- tests/factories/facilities.py | 2 + tests/frontend/test_infrastructure.py | 73 +++++++++++++++++++++++ web/blueprints/infrastructure/__init__.py | 4 +- 3 files changed, 78 insertions(+), 1 deletion(-) diff --git a/tests/factories/facilities.py b/tests/factories/facilities.py index 07614292a..e5c893db3 100644 --- a/tests/factories/facilities.py +++ b/tests/factories/facilities.py @@ -71,6 +71,8 @@ class Params: @post_generation def post(obj: PatchPort, create, extracted, **kwargs): # Ensure that patched ports terminate in the switch room + if obj.switch_room: + return # customly set if obj.switch_port: obj.switch_room = obj.switch_port.switch.host.room else: diff --git a/tests/frontend/test_infrastructure.py b/tests/frontend/test_infrastructure.py index 04dd3df72..a0560c15d 100644 --- a/tests/frontend/test_infrastructure.py +++ b/tests/frontend/test_infrastructure.py @@ -1,6 +1,7 @@ # Copyright (c) 2015 The Pycroft Authors. See the AUTHORS file. # This file is part of the Pycroft project and licensed under the terms of # the Apache License, Version 2.0. See the LICENSE file for details. +import re import pytest from ipaddr import IPv4Network, IPv4Address @@ -10,6 +11,7 @@ from pycroft.model.facilities import Room from pycroft.model.host import Switch from pycroft.model.net import Subnet +from pycroft.model.port import PatchPort from tests import factories as f from web.blueprints.infrastructure import format_address_range from .assertions import TestClient @@ -214,3 +216,74 @@ def test_delete_switch_post(self, client, switch): url_for("infrastructure.switch_delete", switch_id=switch.host_id), method="POST", ) + + +@pytest.mark.usefixtures("admin_logged_in", "session") +class TestSwitchPortCreate: + @pytest.fixture(scope="class") + def patch_port(self, class_session, switch) -> PatchPort: + return f.PatchPortFactory(switch_room=switch.host.room) + + @pytest.fixture(scope="class") + def connected_patch_port(self, class_session) -> PatchPort: + # patch_port = f.PatchPortFactory(switch_room=switch.host.room, switch_port=sp) + return f.PatchPortFactory(patched=True) + + def test_create_port_at_nonexistent_switch(self, client): + with client.flashes_message("nicht gefunden", category="error"): + client.assert_url_redirects( + url_for("infrastructure.switch_port_create", switch_id=999), + expected_location=url_for("infrastructure.switches"), + ) + + def test_create_port_get(self, client, switch): + with client.renders_template("generic_form.html"): + client.assert_url_ok( + url_for("infrastructure.switch_port_create", switch_id=switch.host_id), + ) + + def test_create_port_no_data(self, client, switch): + with client.renders_template("generic_form.html"): + client.assert_url_ok( + url_for("infrastructure.switch_port_create", switch_id=switch.host_id), + data={}, + method="POST", + ) + + def test_create_port_invalid_data(self, client, switch): + with client.renders_template("generic_form.html"): + client.assert_url_ok( + url_for("infrastructure.switch_port_create", switch_id=switch.host_id), + data={ + "name": "Test Port", + "patch_port": "-1", # bad id + }, + method="POST", + ) + + def test_create_port_valid_data(self, client, switch, patch_port): + with client.flashes_message("erfolgreich erstellt", "success"): + client.assert_url_redirects( + url_for("infrastructure.switch_port_create", switch_id=switch.host_id), + data={ + "name": "Test Port", + "patch_port": str(patch_port.id), + "vlan": None, + }, + method="POST", + ) + + def test_create_port_already_patched(self, client, switch, connected_patch_port): + resp = client.assert_url_ok( + url_for( + "infrastructure.switch_port_create", + switch_id=connected_patch_port.switch_port.switch.host_id, + ), + data={ + "name": "Test Port", + "patch_port": str(connected_patch_port.id), + "vlan": None, + }, + method="POST", + ) + assert re.search("bereits.*verbunden", string=(resp.data.decode())) diff --git a/web/blueprints/infrastructure/__init__.py b/web/blueprints/infrastructure/__init__.py index 47845639b..d447945d0 100644 --- a/web/blueprints/infrastructure/__init__.py +++ b/web/blueprints/infrastructure/__init__.py @@ -273,6 +273,7 @@ def switch_port_create(switch_id): if form.validate_on_submit(): error = False + nested = session.session.begin_nested() switch_port = create_switch_port(switch, form.name.data, form.default_vlans.data, current_user) if form.patch_port.data: @@ -289,7 +290,8 @@ def switch_port_create(switch_id): return redirect(url_for('.switch_show', switch_id=switch.host_id)) else: - session.session.rollback() + # we don't want to keep the `switch_port` + nested.rollback() form_args = { 'form': form, From 53b6af68a1ce93769c5cfea8599171dd27451cea Mon Sep 17 00:00:00 2001 From: Lukas Juhrich Date: Sun, 2 Jul 2023 17:08:10 +0200 Subject: [PATCH 11/18] [web] make `handle_errors` session handling work with tests We add an exemplary test which replaces a lib function by an error to test this. --- tests/frontend/test_host.py | 11 +++++++++++ web/blueprints/helpers/exception.py | 9 ++++----- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/tests/frontend/test_host.py b/tests/frontend/test_host.py index d28d45892..e9f85a772 100644 --- a/tests/frontend/test_host.py +++ b/tests/frontend/test_host.py @@ -5,6 +5,8 @@ import pytest from flask import url_for +import web.blueprints.host +from pycroft.exc import PycroftException from pycroft.model.host import Host from pycroft.model.user import User from tests import factories as f @@ -47,6 +49,15 @@ def test_host_get_returns_form(self, client, host): with client.renders_template("generic_form.html"): client.assert_url_ok(url_for("host.host_delete", host_id=host.id)) + def test_host_delete_exception(self, client, host, monkeypatch): + def _r(*a, **kw): + raise PycroftException + monkeypatch.setattr(web.blueprints.host.lib_host, "host_delete", _r) + + with client.flashes_message("Fehler aufgetreten", category="error"): + client.assert_url_ok(url_for("host.host_delete", host_id=host.id), method="POST") + + @pytest.mark.usefixtures("admin_logged_in") class TestHostEdit: diff --git a/web/blueprints/helpers/exception.py b/web/blueprints/helpers/exception.py index 7b1473f2e..460a33c72 100644 --- a/web/blueprints/helpers/exception.py +++ b/web/blueprints/helpers/exception.py @@ -3,7 +3,7 @@ from contextlib import contextmanager from flask import flash -from sqlalchemy.orm import Session +from sqlalchemy.orm import Session, SessionTransaction from pycroft.exc import PycroftException from pycroft.lib.net import MacExistsException, SubnetFullException @@ -42,22 +42,21 @@ class UnexpectedException(PycroftException): @contextmanager -def handle_errors(session: Session): +def handle_errors(session: Session) -> SessionTransaction: """Flash a message, roll back the session, and wrap unknown errors in a ``PycroftException`` :raises PycroftException: """ try: - yield + with session.begin_nested() as n: + yield n except PycroftException as e: flash(exception_flash_message(e), 'error') - session.rollback() raise except Exception as e: traceback.print_exc() logger.exception("Unexpected error when handling web response", stack_info=True) flash(f"Es ist ein unerwarteter Fehler aufgetreten: {e}", "error") - session.rollback() raise UnexpectedException from e From 8899c96c15b4d5f3a8a46e9ba912d52072b95491 Mon Sep 17 00:00:00 2001 From: Lukas Juhrich Date: Sun, 2 Jul 2023 17:15:51 +0200 Subject: [PATCH 12/18] [tests] extract url generation from host tests #643 --- tests/frontend/test_host.py | 86 +++++++++++++++++++++---------------- 1 file changed, 48 insertions(+), 38 deletions(-) diff --git a/tests/frontend/test_host.py b/tests/frontend/test_host.py index e9f85a772..011a835e1 100644 --- a/tests/frontend/test_host.py +++ b/tests/frontend/test_host.py @@ -1,6 +1,7 @@ # Copyright (c) 2015 The Pycroft Authors. See the AUTHORS file. # This file is part of the Pycroft project and licensed under the terms of # the Apache License, Version 2.0. See the LICENSE file for details. +import typing as t import pytest from flask import url_for @@ -31,57 +32,61 @@ def host(module_session, owner) -> Host: @pytest.mark.usefixtures("admin_logged_in") class TestHostDelete: - def test_delete_nonexistent_host(self, client): - client.assert_url_response_code( - url_for("host.host_delete", host_id=999), code=404 - ) + @pytest.fixture(scope="class") + def url(self) -> t.Callable[[int], str]: + def _url(host_id: int) -> str: + return url_for("host.host_delete", host_id=host_id) + + return _url - def test_host_delete_successful(self, session, client, host, owner): + def test_delete_nonexistent_host(self, client, url): + client.assert_url_response_code(url(999), code=404) + + def test_host_delete_successful(self, session, client, host, owner, url): with client.flashes_message("Host.*gelöscht", category="success"): - client.assert_url_redirects( - url_for("host.host_delete", host_id=host.id), - method="POST", - ) + client.assert_url_redirects(url(host.id), method="POST") session.refresh(owner) assert owner.hosts == [] - def test_host_get_returns_form(self, client, host): + def test_host_get_returns_form(self, client, host, url): with client.renders_template("generic_form.html"): - client.assert_url_ok(url_for("host.host_delete", host_id=host.id)) + client.assert_url_ok(url(host.id)) - def test_host_delete_exception(self, client, host, monkeypatch): + def test_host_delete_exception(self, client, host, url, monkeypatch): def _r(*a, **kw): raise PycroftException monkeypatch.setattr(web.blueprints.host.lib_host, "host_delete", _r) with client.flashes_message("Fehler aufgetreten", category="error"): - client.assert_url_ok(url_for("host.host_delete", host_id=host.id), method="POST") + client.assert_url_ok(url(host.id), method="POST") @pytest.mark.usefixtures("admin_logged_in") class TestHostEdit: - def test_edit_nonexistent_host(self, client): - client.assert_url_response_code( - url_for("host.host_edit", host_id=999), code=404 - ) + @pytest.fixture(scope="class") + def url(self) -> t.Callable[[int], str]: + def _url(host_id: int) -> str: + return url_for("host.host_edit", host_id=host_id) + + return _url - def test_edit_host_get(self, client, host): + def test_edit_nonexistent_host(self, client, url): + client.assert_url_response_code(url(999), code=404) + + def test_edit_host_get(self, client, host, url): with client.renders_template("generic_form.html"): - client.assert_url_ok(url_for("host.host_edit", host_id=host.id)) + client.assert_url_ok(url(host.id)) - def test_post_without_data(self, client, host): + def test_post_without_data(self, client, host, url): """works because the room data is automatically derived from the host""" # HTTP 200 OK although form invalid - client.assert_url_ok( - url_for("host.host_edit", host_id=host.id), - method="POST", - ) + client.assert_url_ok(url(host.id), method="POST") - def test_post_with_data(self, client, host): + def test_post_with_data(self, client, host, url): with client.flashes_message("Host.*bearbeitet", category="success"): client.assert_url_redirects( - url_for("host.host_edit", host_id=host.id), + url(host.id), method="POST", data={ "owner": host.owner.id, @@ -92,9 +97,9 @@ def test_post_with_data(self, client, host): }, ) - def test_post_with_invalid_data(self, client, host): + def test_post_with_invalid_data(self, client, host, url): client.assert_url_ok( - url_for("host.host_edit", host_id=host.id), + url(host.id), method="POST", data={ "owner": host.owner.id, @@ -108,19 +113,24 @@ def test_post_with_invalid_data(self, client, host): @pytest.mark.usefixtures("admin_logged_in") class TestHostCreate: - def test_create_host_nonexistent_owner(self, client): - client.assert_url_response_code( - url_for("host.host_create", user_id=999), code=404 - ) + @pytest.fixture(scope="class") + def url(self) -> t.Callable[[int], str]: + def _url(user_id: int) -> str: + return url_for("host.host_create", user_id=user_id) + + return _url + + def test_create_host_nonexistent_owner(self, client, url): + client.assert_url_response_code(url(999), code=404) - def test_create_host_get(self, client, owner): + def test_create_host_get(self, client, owner, url): with client.renders_template("generic_form.html"): - client.assert_url_ok(url_for("host.host_create", user_id=owner.id)) + client.assert_url_ok(url(owner.id)) - def test_create_host_post(self, session, client, owner, host): + def test_create_host_post(self, session, client, owner, host, url): with client.flashes_message("Host.*erstellt", category="success"): client.assert_url_redirects( - url_for("host.host_create", user_id=owner.id), + url(owner.id), method="POST", data={ "name": "test-host", @@ -136,9 +146,9 @@ def test_create_host_post(self, session, client, owner, host): assert len(new_hosts) == 1 assert list(new_hosts)[0].name == "test-host" - def test_create_host_post_invalid_data(self, session, client, owner): + def test_create_host_post_invalid_data(self, session, client, owner, url): client.assert_url_ok( - url_for("host.host_create", user_id=owner.id), + url(owner.id), method="POST", data={ "name": "test-host", From 488a279607bb59442cddb6644e9902fb566cc346 Mon Sep 17 00:00:00 2001 From: Lukas Juhrich Date: Sun, 2 Jul 2023 17:17:59 +0200 Subject: [PATCH 13/18] [tests] add type hints to hades fixtures --- tests/model/test_hades.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/tests/model/test_hades.py b/tests/model/test_hades.py index fd2aa3dc9..18b84d70f 100644 --- a/tests/model/test_hades.py +++ b/tests/model/test_hades.py @@ -4,13 +4,15 @@ from pycroft.helpers.interval import closedopen from pycroft.model import hades +from pycroft.model.host import Switch from pycroft.model.net import VLAN +from pycroft.model.user import PropertyGroup, User, Membership from tests.factories import PropertyGroupFactory, MembershipFactory, \ SwitchFactory, PatchPortFactory, UserFactory @pytest.fixture(scope='module', autouse=True) -def network_access_group(module_session): +def network_access_group(module_session) -> PropertyGroup: return PropertyGroupFactory.create( name="Member", granted={'network_access'}, @@ -18,7 +20,7 @@ def network_access_group(module_session): @pytest.fixture(scope='module', autouse=True) -def payment_in_default_group(module_session): +def payment_in_default_group(module_session) -> PropertyGroup: return PropertyGroupFactory.create( name="Blocked (finance)", granted={'payment_in_default'}, @@ -27,7 +29,7 @@ def payment_in_default_group(module_session): @pytest.fixture(scope='module', autouse=True) -def traffic_limit_exceeded_group(module_session): +def traffic_limit_exceeded_group(module_session) -> PropertyGroup: return PropertyGroupFactory.create( name="Blocked (traffic)", granted={'traffic_limit_exceeded'}, @@ -36,12 +38,12 @@ def traffic_limit_exceeded_group(module_session): @pytest.fixture(scope='module', autouse=True) -def user(module_session): +def user(module_session) -> User: return UserFactory(with_host=True) @pytest.fixture(scope='module', autouse=True) -def switch(module_session, user): +def switch(module_session, user) -> Switch: # the user's room needs to be connected to provide `nasipaddress` and `nasportid` switch = SwitchFactory.create(host__owner=user) PatchPortFactory.create_batch( @@ -53,12 +55,12 @@ def switch(module_session, user): @pytest.fixture(scope='module') -def now(): +def now() -> datetime: return datetime.now() @pytest.fixture(scope='module', autouse=True) -def membership(module_session, user, network_access_group, now): +def membership(module_session, user, network_access_group, now) -> Membership: return MembershipFactory.create( user=user, group=network_access_group, active_during=closedopen(now + timedelta(-1), now + timedelta(1)) @@ -66,7 +68,7 @@ def membership(module_session, user, network_access_group, now): @pytest.fixture(scope='module', autouse=True) -def mapped_radius_properties(module_session): +def mapped_radius_properties(module_session) -> None: module_session.execute(hades.radius_property.insert().values([ ('payment_in_default',), ('traffic_limit_exceeded',), From 466e6f4878afeaa3965b90b1ab6716aba1b91fc7 Mon Sep 17 00:00:00 2001 From: Lukas Juhrich Date: Sun, 2 Jul 2023 17:27:31 +0200 Subject: [PATCH 14/18] [tests] extract url generation from infrastructure tests #643 --- tests/frontend/test_infrastructure.py | 125 +++++++++++++------------- 1 file changed, 62 insertions(+), 63 deletions(-) diff --git a/tests/frontend/test_infrastructure.py b/tests/frontend/test_infrastructure.py index a0560c15d..81861ad50 100644 --- a/tests/frontend/test_infrastructure.py +++ b/tests/frontend/test_infrastructure.py @@ -2,6 +2,7 @@ # This file is part of the Pycroft project and licensed under the terms of # the Apache License, Version 2.0. See the LICENSE file for details. import re +import typing as t import pytest from ipaddr import IPv4Network, IPv4Address @@ -98,6 +99,10 @@ def test_show_switch_table(self, client: TestClient, switch: Switch): @pytest.mark.usefixtures("admin_logged_in", "session") class TestCreateSwitch: + @pytest.fixture(scope="class") + def url(self) -> str: + return url_for("infrastructure.switch_create") + @pytest.fixture(scope="class") def room(self, class_session: Session) -> Room: return f.RoomFactory() @@ -106,18 +111,18 @@ def test_create_switch_get(self, client): with client.renders_template("generic_form.html"): client.assert_url_ok(url_for("infrastructure.switch_create")) - def test_create_switch_post_no_data(self, client): + def test_create_switch_post_no_data(self, client, url): with client.renders_template("generic_form.html"): client.assert_url_ok( - url_for("infrastructure.switch_create"), + url, data={}, method="POST", ) - def test_create_switch_post_invalid_data(self, client): + def test_create_switch_post_invalid_data(self, client, url): with client.renders_template("generic_form.html"): client.assert_url_ok( - url_for("infrastructure.switch_create"), + url, # data according to `class SwitchForm` data={ "name": "Test Switch", @@ -127,10 +132,10 @@ def test_create_switch_post_invalid_data(self, client): method="POST", ) - def test_create_switch_post_valid_data(self, client, room): + def test_create_switch_post_valid_data(self, client, room, url): with client.flashes_message("erfolgreich erstellt", "success"): client.assert_url_redirects( - url_for("infrastructure.switch_create"), + url, # data according to `class SwitchForm` data={ "name": "Test Switch", @@ -145,31 +150,29 @@ def test_create_switch_post_valid_data(self, client, room): @pytest.mark.usefixtures("admin_logged_in", "session") class TestSwitchEdit: - def test_edit_nonexistent_switch(self, client): + @pytest.fixture(scope="class") + def url(self) -> t.Callable[[int], str]: + def _url(switch_id): + return url_for("infrastructure.switch_edit", switch_id=switch_id) + + return _url + + def test_edit_nonexistent_switch(self, client, url): with client.flashes_message("nicht gefunden", category="error"): - client.assert_url_redirects( - url_for("infrastructure.switch_edit", switch_id=999), - expected_location=url_for("infrastructure.switches"), - ) + client.assert_url_redirects(url(999), url_for("infrastructure.switches")) - def test_edit_switch_get(self, client, switch): + def test_edit_switch_get(self, client, switch, url): with client.renders_template("generic_form.html"): - client.assert_url_ok( - url_for("infrastructure.switch_edit", switch_id=switch.host_id), - ) + client.assert_url_ok(url(switch.host_id)) - def test_edit_switch_post_no_data(self, client, switch): + def test_edit_switch_post_no_data(self, client, switch, url): with client.renders_template("generic_form.html"): - client.assert_url_ok( - url_for("infrastructure.switch_edit", switch_id=switch.host_id), - data={}, - method="POST", - ) + client.assert_url_ok(url(switch.host_id), data={}, method="POST") - def test_edit_switch_post_invalid_data(self, client, switch): + def test_edit_switch_post_invalid_data(self, client, switch, url): with client.renders_template("generic_form.html"): client.assert_url_ok( - url_for("infrastructure.switch_edit", switch_id=switch.host_id), + url(switch.host_id), # data according to `class SwitchForm` data={ "name": "Test Switch", @@ -179,10 +182,10 @@ def test_edit_switch_post_invalid_data(self, client, switch): method="POST", ) - def test_edit_switch_post_valid_data(self, client, switch): + def test_edit_switch_post_valid_data(self, client, switch, url): with client.flashes_message("erfolgreich bearbeitet", "success"): client.assert_url_redirects( - url_for("infrastructure.switch_edit", switch_id=switch.host_id), + url(switch.host_id), # data according to `class SwitchForm` data={ "name": "Test Switch (now with new name)", @@ -197,29 +200,35 @@ def test_edit_switch_post_valid_data(self, client, switch): @pytest.mark.usefixtures("admin_logged_in", "session") class TestSwitchDelete: - def test_delete_nonexistent_switch(self, client): + @pytest.fixture(scope="class") + def url(self) -> t.Callable[[int], str]: + def _url(switch_id): + return url_for("infrastructure.switch_delete", switch_id=switch_id) + + return _url + + def test_delete_nonexistent_switch(self, client, url): with client.flashes_message("nicht gefunden", category="error"): - client.assert_url_redirects( - url_for("infrastructure.switch_delete", switch_id=999), - expected_location=url_for("infrastructure.switches"), - ) + client.assert_url_redirects(url(999), url_for("infrastructure.switches")) - def test_delete_switch_get(self, client, switch): + def test_delete_switch_get(self, client, switch, url): with client.renders_template("generic_form.html"): - client.assert_url_ok( - url_for("infrastructure.switch_delete", switch_id=switch.host_id), - ) + client.assert_url_ok(url(switch.host_id)) - def test_delete_switch_post(self, client, switch): + def test_delete_switch_post(self, client, switch, url): with client.flashes_message("erfolgreich gelöscht", "success"): - client.assert_url_redirects( - url_for("infrastructure.switch_delete", switch_id=switch.host_id), - method="POST", - ) + client.assert_url_redirects(url(switch.host_id), method="POST") @pytest.mark.usefixtures("admin_logged_in", "session") class TestSwitchPortCreate: + @pytest.fixture(scope="class") + def url(self) -> t.Callable[[int], str]: + def _url(switch_id): + return url_for("infrastructure.switch_port_create", switch_id=switch_id) + + return _url + @pytest.fixture(scope="class") def patch_port(self, class_session, switch) -> PatchPort: return f.PatchPortFactory(switch_room=switch.host.room) @@ -229,31 +238,22 @@ def connected_patch_port(self, class_session) -> PatchPort: # patch_port = f.PatchPortFactory(switch_room=switch.host.room, switch_port=sp) return f.PatchPortFactory(patched=True) - def test_create_port_at_nonexistent_switch(self, client): + def test_create_port_at_nonexistent_switch(self, client, url): with client.flashes_message("nicht gefunden", category="error"): - client.assert_url_redirects( - url_for("infrastructure.switch_port_create", switch_id=999), - expected_location=url_for("infrastructure.switches"), - ) + client.assert_url_redirects(url(999), url_for("infrastructure.switches")) - def test_create_port_get(self, client, switch): + def test_create_port_get(self, client, switch, url): with client.renders_template("generic_form.html"): - client.assert_url_ok( - url_for("infrastructure.switch_port_create", switch_id=switch.host_id), - ) + client.assert_url_ok(url(switch.host_id)) - def test_create_port_no_data(self, client, switch): + def test_create_port_no_data(self, client, switch, url): with client.renders_template("generic_form.html"): - client.assert_url_ok( - url_for("infrastructure.switch_port_create", switch_id=switch.host_id), - data={}, - method="POST", - ) + client.assert_url_ok(url(switch.host_id), data={}, method="POST") - def test_create_port_invalid_data(self, client, switch): + def test_create_port_invalid_data(self, client, switch, url): with client.renders_template("generic_form.html"): client.assert_url_ok( - url_for("infrastructure.switch_port_create", switch_id=switch.host_id), + url(switch.host_id), data={ "name": "Test Port", "patch_port": "-1", # bad id @@ -261,10 +261,10 @@ def test_create_port_invalid_data(self, client, switch): method="POST", ) - def test_create_port_valid_data(self, client, switch, patch_port): + def test_create_port_valid_data(self, client, switch, patch_port, url): with client.flashes_message("erfolgreich erstellt", "success"): client.assert_url_redirects( - url_for("infrastructure.switch_port_create", switch_id=switch.host_id), + url(switch.host_id), data={ "name": "Test Port", "patch_port": str(patch_port.id), @@ -273,12 +273,11 @@ def test_create_port_valid_data(self, client, switch, patch_port): method="POST", ) - def test_create_port_already_patched(self, client, switch, connected_patch_port): + def test_create_port_already_patched( + self, client, switch, connected_patch_port, url + ): resp = client.assert_url_ok( - url_for( - "infrastructure.switch_port_create", - switch_id=connected_patch_port.switch_port.switch.host_id, - ), + url(connected_patch_port.switch_port.switch.host_id), data={ "name": "Test Port", "patch_port": str(connected_patch_port.id), From 48915ef7268ce0c6836298c057b50077d6792feb Mon Sep 17 00:00:00 2001 From: Lukas Juhrich Date: Sun, 2 Jul 2023 19:24:39 +0200 Subject: [PATCH 15/18] [tests] test `switch_port_edit` #643 --- tests/frontend/test_infrastructure.py | 83 ++++++++++++++++++++++- web/blueprints/infrastructure/__init__.py | 5 +- 2 files changed, 86 insertions(+), 2 deletions(-) diff --git a/tests/frontend/test_infrastructure.py b/tests/frontend/test_infrastructure.py index 81861ad50..0f4f82390 100644 --- a/tests/frontend/test_infrastructure.py +++ b/tests/frontend/test_infrastructure.py @@ -10,7 +10,7 @@ from flask import url_for from pycroft.model.facilities import Room -from pycroft.model.host import Switch +from pycroft.model.host import Switch, SwitchPort from pycroft.model.net import Subnet from pycroft.model.port import PatchPort from tests import factories as f @@ -286,3 +286,84 @@ def test_create_port_already_patched( method="POST", ) assert re.search("bereits.*verbunden", string=(resp.data.decode())) + + +@pytest.mark.usefixtures("admin_logged_in", "session") +class TestSwitchPortEdit: + @pytest.fixture(scope="class") + def url(self) -> t.Callable[[int, int], str]: + def _url(switch_id: int, switch_port_id: int) -> str: + return url_for( + "infrastructure.switch_port_edit", + switch_id=switch_id, + switch_port_id=switch_port_id, + ) + + return _url + + @pytest.fixture(scope="class") + def connected_patch_port(self, class_session, switch) -> PatchPort: + return f.PatchPortFactory(patched=True, switch_port__switch=switch) + + @pytest.fixture(scope="class") + def switch_port(self, class_session, connected_patch_port) -> SwitchPort: + return connected_patch_port.switch_port + + @pytest.fixture(scope="class") + def switch_port_2(self, class_session, switch) -> SwitchPort: + return f.SwitchPortFactory(switch__host__room=switch.host.room) + + def test_get_nonexistent_switch(self, client, url): + with client.flashes_message("nicht gefunden", category="error"): + client.assert_url_redirects(url(999, 999)) + + def test_get_nonexistent_switch_port(self, client, switch, url): + with client.flashes_message("nicht gefunden", category="error"): + client.assert_url_redirects(url(switch.host_id, 999)) + + def test_get_switch_port_wrong_switch(self, client, switch, switch_port_2, url): + with client.flashes_message("SwitchPort.*gehört nicht zu", category="error"): + client.assert_url_redirects(url(switch.host_id, switch_port_2.id)) + + def test_get_switch_port(self, client, switch, switch_port, url): + client.assert_url_ok(url(switch.host_id, switch_port.id)) + + def test_post_switch_port_no_data(self, client, switch, switch_port, url): + URL = url(switch.host_id, switch_port.id) + with client.flashes_message("erfolgreich", category="success"): + client.assert_url_redirects(URL, data={}, method="POST") + + def test_post_switch_port_invalid_data(self, client, switch, switch_port, url): + URL = url(switch.host_id, switch_port.id) + bad_data = { + "name": "Test Port", + "patch_port": "-1", # bad id + } + with client.renders_template("generic_form.html"): + client.assert_url_ok(URL, data=bad_data, method="POST") + + def test_edit_switch_port_new_port(self, client, session, switch, switch_port, url): + URL = url(switch.host_id, switch_port.id) + new_port = f.PatchPortFactory(switch_room=switch.host.room) + session.flush() + good_data = { + "name": "Test Port", + "patch_port": str(new_port.id), + "vlan": None, + } + with client.flashes_message("erfolgreich", "success"): + client.assert_url_redirects(URL, data=good_data, method="POST") + + def test_edit_switch_port_patched_port( + self, client, switch, switch_port_2, url, connected_patch_port + ): + # need `switch_port_2`, so patching actually does something + URL = url(switch_port_2.switch.host_id, switch_port_2.id) + data = { + "name": "Test Port", + "patch_port": str(connected_patch_port.id), + "vlan": None, + } + with client.renders_template("generic_form.html"): + resp = client.assert_url_ok(URL, data=data, method="POST") + assert re.search("bereits.*verbunden", string=(resp.data.decode())) diff --git a/web/blueprints/infrastructure/__init__.py b/web/blueprints/infrastructure/__init__.py index d447945d0..ef0635c92 100644 --- a/web/blueprints/infrastructure/__init__.py +++ b/web/blueprints/infrastructure/__init__.py @@ -328,6 +328,8 @@ def switch_port_edit(switch_id, switch_port_id): if form.validate_on_submit(): error = False + # TODO use `handle_errors` instead + nested = session.session.begin_nested() edit_switch_port(switch_port, form.name.data, form.default_vlans.data, current_user) if switch_port.patch_port != form.patch_port.data: @@ -342,13 +344,14 @@ def switch_port_edit(switch_id, switch_port_id): error = True if not error: + nested.commit() session.session.commit() flash("Der Switch-Port wurde erfolgreich bearbeitet.", "success") return redirect(url_for('.switch_show', switch_id=switch_port.switch_id)) else: - session.session.rollback() + nested.rollback() form_args = { 'form': form, From f7b47d801ee4e5d4fd5baba528dc6fb967b4912c Mon Sep 17 00:00:00 2001 From: Lukas Juhrich Date: Sun, 2 Jul 2023 19:34:48 +0200 Subject: [PATCH 16/18] [tests] test `switch_port_delete` #643 --- tests/frontend/test_infrastructure.py | 42 +++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/tests/frontend/test_infrastructure.py b/tests/frontend/test_infrastructure.py index 0f4f82390..a5d7d24bc 100644 --- a/tests/frontend/test_infrastructure.py +++ b/tests/frontend/test_infrastructure.py @@ -367,3 +367,45 @@ def test_edit_switch_port_patched_port( with client.renders_template("generic_form.html"): resp = client.assert_url_ok(URL, data=data, method="POST") assert re.search("bereits.*verbunden", string=(resp.data.decode())) + + +@pytest.mark.usefixtures("admin_logged_in", "session") +class TestSwitchPortDelete: + @pytest.fixture(scope="class") + def url(self) -> t.Callable[[int, int], str]: + def _url(switch_id: int, switch_port_id: int) -> str: + return url_for( + "infrastructure.switch_port_delete", + switch_id=switch_id, + switch_port_id=switch_port_id, + ) + + return _url + + @pytest.fixture(scope="class") + def switch_port(self, class_session, switch): + return f.SwitchPortFactory(switch=switch) + + @pytest.fixture(scope="class") + def switch_port_2(self, class_session, switch) -> SwitchPort: + return f.SwitchPortFactory(switch__host__room=switch.host.room) + + def test_delete_nonexistent_switch(self, client, url): + with client.flashes_message("nicht gefunden", category="error"): + client.assert_url_redirects(url(999, 999)) + + def test_delete_nonexistent_switch_port(self, client, switch, url): + with client.flashes_message("nicht gefunden", category="error"): + client.assert_url_redirects(url(switch.host_id, 999)) + + def test_delete_switch_port_other_switch(self, client, switch_port_2, switch, url): + with client.flashes_message("SwitchPort.*gehört nicht zu", category="error"): + client.assert_url_redirects(url(switch.host_id, switch_port_2.id)) + + def test_delete_switch_port_get(self, client, switch, switch_port, url): + client.assert_url_ok(url(switch.host_id, switch_port.id)) + + def test_delete_switch_port_post(self, client, switch, switch_port, url): + URL = url(switch.host_id, switch_port.id) + with client.flashes_message("erfolgreich", category="success"): + client.assert_url_redirects(URL, method="POST") From 62a41a5527c8e0a12561959bb972fe442b968bce Mon Sep 17 00:00:00 2001 From: Lukas Juhrich Date: Sun, 2 Jul 2023 19:38:13 +0200 Subject: [PATCH 17/18] [tests] test `vlans` and `vlans_json` #643 --- tests/frontend/test_infrastructure.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/frontend/test_infrastructure.py b/tests/frontend/test_infrastructure.py index a5d7d24bc..a821a724d 100644 --- a/tests/frontend/test_infrastructure.py +++ b/tests/frontend/test_infrastructure.py @@ -409,3 +409,14 @@ def test_delete_switch_port_post(self, client, switch, switch_port, url): URL = url(switch.host_id, switch_port.id) with client.flashes_message("erfolgreich", category="success"): client.assert_url_redirects(URL, method="POST") + + +def test_vlans(client, session): + with client.renders_template("infrastructure/vlan_list.html"): + client.assert_ok("infrastructure.vlans") + + +def test_vlan_table(client, session): + resp = client.assert_ok("infrastructure.vlans_json") + assert "items" in resp.json + assert len(resp.json["items"]) >= 1 From 541a724597dcadf705659fc19af403a47ba28b9e Mon Sep 17 00:00:00 2001 From: Lukas Juhrich Date: Sun, 2 Jul 2023 19:40:00 +0200 Subject: [PATCH 18/18] [tests] make `darker` happy --- tests/frontend/test_host.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/frontend/test_host.py b/tests/frontend/test_host.py index 011a835e1..84192bfe3 100644 --- a/tests/frontend/test_host.py +++ b/tests/frontend/test_host.py @@ -55,13 +55,13 @@ def test_host_get_returns_form(self, client, host, url): def test_host_delete_exception(self, client, host, url, monkeypatch): def _r(*a, **kw): raise PycroftException + monkeypatch.setattr(web.blueprints.host.lib_host, "host_delete", _r) with client.flashes_message("Fehler aufgetreten", category="error"): client.assert_url_ok(url(host.id), method="POST") - @pytest.mark.usefixtures("admin_logged_in") class TestHostEdit: @pytest.fixture(scope="class")