From f389761b14c0f23eb66ba25ddfe438378b23e305 Mon Sep 17 00:00:00 2001 From: Rob Martin Date: Mon, 21 Sep 2026 17:32:58 -0600 Subject: [PATCH] test(auth): stop proxy provisioning tests depending on run order Two tests each assert that a proxy-authenticated user comes back as an admin. Since #1356 that only holds for the bootstrap account, so whichever runs second sees an instance that already has an admin and fails. Running them in the other order just moves which one breaks. It stays green on CI because the suite runs under xdist and each worker builds its own CONFIG_DIR, so the two land in separate databases and both get to be first. That makes it luck: changing the test count reshuffles the workers and can put them together. Give every test an empty user table instead, and cover the rule directly, which nothing did before. The bootstrap account is an admin and the next one is not, PROXY_AUTH_DEFAULT_ROLE=admin promotes later accounts, and a known user keeps its stored role rather than picking up the default. Noticed while building a Debrid-Link client (#1380); the extra tests moved the shards and turned these red. --- tests/e2e/test_proxy_auth_middleware.py | 105 ++++++++++++++++++++++++ 1 file changed, 105 insertions(+) diff --git a/tests/e2e/test_proxy_auth_middleware.py b/tests/e2e/test_proxy_auth_middleware.py index 3db86079e..230474554 100644 --- a/tests/e2e/test_proxy_auth_middleware.py +++ b/tests/e2e/test_proxy_auth_middleware.py @@ -36,6 +36,25 @@ def main_module(): return main +@pytest.fixture(autouse=True) +def _empty_user_table(main_module): + """Give every test an instance with no accounts yet. + + Proxy provisioning keys off whether the instance already has an admin, so + two tests sharing a user table are really asserting the order they happened + to run in. The suite runs with xdist, where each worker gets its own + CONFIG_DIR, so that order changes whenever the test count does. + """ + + def _clear() -> None: + for user in main_module.user_db.list_users(): + main_module.user_db.delete_user(user["id"]) + + _clear() + yield + _clear() + + class TestProxyAuthMiddleware: def test_skips_for_non_proxy_mode(self, main_module): with ( @@ -231,6 +250,92 @@ def test_reprovisions_when_session_db_user_points_to_other_username(self, main_m assert db_user is not None assert db_user["username"] == username + def test_first_account_is_admin_and_later_ones_are_not(self, main_module): + """The bootstrap account is an admin; after that the default role decides.""" + with ( + patch.object(main_module, "get_auth_mode", return_value="proxy"), + patch.object( + main_module.app_config, + "get", + side_effect=_config_getter({"PROXY_AUTH_USER_HEADER": "X-Auth-User"}), + ), + ): + with main_module.app.test_request_context( + "/api/releases", + headers={"X-Auth-User": "first_proxy_user"}, + ): + assert main_module.proxy_auth_middleware() is None + assert main_module.session.get("is_admin") is True + + # The instance now has an admin, so nobody is at risk of being locked + # out and the next account follows PROXY_AUTH_DEFAULT_ROLE. + with main_module.app.test_request_context( + "/api/releases", + headers={"X-Auth-User": "second_proxy_user"}, + ): + assert main_module.proxy_auth_middleware() is None + assert main_module.session.get("is_admin") is False + + def test_default_role_admin_promotes_later_accounts(self, main_module): + main_module.user_db.create_user( + username="existing_admin", + role="admin", + auth_source="proxy", + ) + + with ( + patch.object(main_module, "get_auth_mode", return_value="proxy"), + patch.object( + main_module.app_config, + "get", + side_effect=_config_getter( + { + "PROXY_AUTH_USER_HEADER": "X-Auth-User", + "PROXY_AUTH_DEFAULT_ROLE": "admin", + } + ), + ), + main_module.app.test_request_context( + "/api/releases", + headers={"X-Auth-User": "later_proxy_admin"}, + ), + ): + assert main_module.proxy_auth_middleware() is None + assert main_module.session.get("is_admin") is True + + def test_existing_user_keeps_its_stored_role(self, main_module): + main_module.user_db.create_user( + username="existing_admin", + role="admin", + auth_source="proxy", + ) + main_module.user_db.create_user( + username="known_plain_user", + role="user", + auth_source="proxy", + ) + + with ( + patch.object(main_module, "get_auth_mode", return_value="proxy"), + patch.object( + main_module.app_config, + "get", + side_effect=_config_getter( + { + "PROXY_AUTH_USER_HEADER": "X-Auth-User", + "PROXY_AUTH_DEFAULT_ROLE": "admin", + } + ), + ), + main_module.app.test_request_context( + "/api/releases", + headers={"X-Auth-User": "known_plain_user"}, + ), + ): + assert main_module.proxy_auth_middleware() is None + # The stored role wins; the default only applies to new accounts. + assert main_module.session.get("is_admin") is False + def test_returns_401_when_header_missing_on_protected_path(self, main_module): with ( patch.object(main_module, "get_auth_mode", return_value="proxy"),