tests/e2e: replace apply_default_data with @apply_migration decorator

This commit is contained in:
Jens Langhammer 2021-02-27 22:32:48 +01:00
parent 07379acf7f
commit 55c408a8bf
5 changed files with 43 additions and 45 deletions

View File

@ -17,7 +17,7 @@ from authentik.flows.models import Flow, FlowStageBinding
from authentik.stages.authenticator_static.models import AuthenticatorStaticStage from authentik.stages.authenticator_static.models import AuthenticatorStaticStage
from authentik.stages.authenticator_totp.models import AuthenticatorTOTPStage from authentik.stages.authenticator_totp.models import AuthenticatorTOTPStage
from authentik.stages.authenticator_validate.models import AuthenticatorValidateStage from authentik.stages.authenticator_validate.models import AuthenticatorValidateStage
from tests.e2e.utils import USER, SeleniumTestCase, retry from tests.e2e.utils import USER, SeleniumTestCase, apply_migration, retry
@skipUnless(platform.startswith("linux"), "requires local docker") @skipUnless(platform.startswith("linux"), "requires local docker")
@ -25,6 +25,8 @@ class TestFlowsAuthenticator(SeleniumTestCase):
"""test flow with otp stages""" """test flow with otp stages"""
@retry() @retry()
@apply_migration("authentik_core", "0003_default_user")
@apply_migration("authentik_flows", "0008_default_flows")
def test_totp_validate(self): def test_totp_validate(self):
"""test flow with otp stages""" """test flow with otp stages"""
sleep(1) sleep(1)
@ -61,6 +63,9 @@ class TestFlowsAuthenticator(SeleniumTestCase):
self.assert_user(USER()) self.assert_user(USER())
@retry() @retry()
@apply_migration("authentik_core", "0003_default_user")
@apply_migration("authentik_flows", "0008_default_flows")
@apply_migration("authentik_stages_authenticator_totp", "0006_default_setup_flow")
def test_totp_setup(self): def test_totp_setup(self):
"""test TOTP Setup stage""" """test TOTP Setup stage"""
flow: Flow = Flow.objects.get(slug="default-authentication-flow") flow: Flow = Flow.objects.get(slug="default-authentication-flow")
@ -108,6 +113,9 @@ class TestFlowsAuthenticator(SeleniumTestCase):
self.assertTrue(TOTPDevice.objects.filter(user=USER(), confirmed=True).exists()) self.assertTrue(TOTPDevice.objects.filter(user=USER(), confirmed=True).exists())
@retry() @retry()
@apply_migration("authentik_core", "0003_default_user")
@apply_migration("authentik_flows", "0008_default_flows")
@apply_migration("authentik_stages_authenticator_static", "0005_default_setup_flow")
def test_static_setup(self): def test_static_setup(self):
"""test Static OTP Setup stage""" """test Static OTP Setup stage"""
flow: Flow = Flow.objects.get(slug="default-authentication-flow") flow: Flow = Flow.objects.get(slug="default-authentication-flow")

View File

@ -16,7 +16,7 @@ from authentik.stages.identification.models import IdentificationStage
from authentik.stages.prompt.models import FieldTypes, Prompt, PromptStage from authentik.stages.prompt.models import FieldTypes, Prompt, PromptStage
from authentik.stages.user_login.models import UserLoginStage from authentik.stages.user_login.models import UserLoginStage
from authentik.stages.user_write.models import UserWriteStage from authentik.stages.user_write.models import UserWriteStage
from tests.e2e.utils import USER, SeleniumTestCase, retry from tests.e2e.utils import USER, SeleniumTestCase, apply_migration, retry
@skipUnless(platform.startswith("linux"), "requires local docker") @skipUnless(platform.startswith("linux"), "requires local docker")
@ -37,6 +37,8 @@ class TestFlowsEnroll(SeleniumTestCase):
} }
@retry() @retry()
@apply_migration("authentik_core", "0003_default_user")
@apply_migration("authentik_flows", "0008_default_flows")
# pylint: disable=too-many-locals # pylint: disable=too-many-locals
def test_enroll_2_step(self): def test_enroll_2_step(self):
"""Test 2-step enroll flow""" """Test 2-step enroll flow"""
@ -101,6 +103,8 @@ class TestFlowsEnroll(SeleniumTestCase):
self.assertEqual(user.email, "foo@bar.baz") self.assertEqual(user.email, "foo@bar.baz")
@retry() @retry()
@apply_migration("authentik_core", "0003_default_user")
@apply_migration("authentik_flows", "0008_default_flows")
@override_settings(EMAIL_BACKEND="django.core.mail.backends.smtp.EmailBackend") @override_settings(EMAIL_BACKEND="django.core.mail.backends.smtp.EmailBackend")
def test_enroll_email(self): def test_enroll_email(self):
"""Test enroll with Email verification""" """Test enroll with Email verification"""

View File

@ -2,7 +2,7 @@
from sys import platform from sys import platform
from unittest.case import skipUnless from unittest.case import skipUnless
from tests.e2e.utils import USER, SeleniumTestCase, retry from tests.e2e.utils import USER, SeleniumTestCase, apply_migration, retry
@skipUnless(platform.startswith("linux"), "requires local docker") @skipUnless(platform.startswith("linux"), "requires local docker")
@ -10,6 +10,8 @@ class TestFlowsLogin(SeleniumTestCase):
"""test default login flow""" """test default login flow"""
@retry() @retry()
@apply_migration("authentik_core", "0003_default_user")
@apply_migration("authentik_flows", "0008_default_flows")
def test_login(self): def test_login(self):
"""test default login flow""" """test default login flow"""
self.driver.get(f"{self.live_server_url}/flows/default-authentication-flow/") self.driver.get(f"{self.live_server_url}/flows/default-authentication-flow/")

View File

@ -1,5 +1,6 @@
"""test stage setup flows (password change)""" """test stage setup flows (password change)"""
from sys import platform from sys import platform
from time import sleep
from unittest.case import skipUnless from unittest.case import skipUnless
from selenium.webdriver.common.by import By from selenium.webdriver.common.by import By
@ -9,7 +10,7 @@ from authentik.core.models import User
from authentik.flows.models import Flow, FlowDesignation from authentik.flows.models import Flow, FlowDesignation
from authentik.providers.oauth2.generators import generate_client_secret from authentik.providers.oauth2.generators import generate_client_secret
from authentik.stages.password.models import PasswordStage from authentik.stages.password.models import PasswordStage
from tests.e2e.utils import USER, SeleniumTestCase, retry from tests.e2e.utils import USER, SeleniumTestCase, apply_migration, retry
@skipUnless(platform.startswith("linux"), "requires local docker") @skipUnless(platform.startswith("linux"), "requires local docker")
@ -17,6 +18,9 @@ class TestFlowsStageSetup(SeleniumTestCase):
"""test stage setup flows""" """test stage setup flows"""
@retry() @retry()
@apply_migration("authentik_core", "0003_default_user")
@apply_migration("authentik_flows", "0008_default_flows")
@apply_migration("authentik_stages_password", "0002_passwordstage_change_flow")
def test_password_change(self): def test_password_change(self):
"""test password change flow""" """test password change flow"""
# Ensure that password stage has change_flow set # Ensure that password stage has change_flow set
@ -34,10 +38,7 @@ class TestFlowsStageSetup(SeleniumTestCase):
self.driver.get( self.driver.get(
f"{self.live_server_url}/flows/default-authentication-flow/?next=%2F" f"{self.live_server_url}/flows/default-authentication-flow/?next=%2F"
) )
self.driver.find_element(By.ID, "id_uid_field").send_keys(USER().username) self.login()
self.driver.find_element(By.ID, "id_uid_field").send_keys(Keys.ENTER)
self.driver.find_element(By.ID, "id_password").send_keys(USER().username)
self.driver.find_element(By.ID, "id_password").send_keys(Keys.ENTER)
self.wait_for_url(self.shell_url("/library")) self.wait_for_url(self.shell_url("/library"))
self.driver.get( self.driver.get(
@ -46,10 +47,19 @@ class TestFlowsStageSetup(SeleniumTestCase):
stage_uuid=PasswordStage.objects.first().stage_uuid, stage_uuid=PasswordStage.objects.first().stage_uuid,
) )
) )
self.driver.find_element(By.ID, "id_password").send_keys(new_password)
self.driver.find_element(By.ID, "id_password_repeat").click() flow_executor = self.get_shadow_root("ak-flow-executor")
self.driver.find_element(By.ID, "id_password_repeat").send_keys(new_password) prompt_stage = self.get_shadow_root("ak-stage-prompt", flow_executor)
self.driver.find_element(By.CSS_SELECTOR, ".pf-c-button").click()
prompt_stage.find_element(By.CSS_SELECTOR, "input[name=password]").send_keys(
new_password
)
prompt_stage.find_element(
By.CSS_SELECTOR, "input[name=password_repeat]"
).send_keys(new_password)
prompt_stage.find_element(
By.CSS_SELECTOR, "input[name=password_repeat]"
).send_keys(Keys.ENTER)
self.wait_for_url(self.shell_url("/library")) self.wait_for_url(self.shell_url("/library"))
# Because USER() is cached, we need to get the user manually here # Because USER() is cached, we need to get the user manually here

View File

@ -1,6 +1,6 @@
"""authentik e2e testing utilities""" """authentik e2e testing utilities"""
import json import json
from functools import wraps from functools import lru_cache, wraps
from glob import glob from glob import glob
from importlib.util import module_from_spec, spec_from_file_location from importlib.util import module_from_spec, spec_from_file_location
from inspect import getmembers, isfunction from inspect import getmembers, isfunction
@ -57,7 +57,6 @@ class SeleniumTestCase(StaticLiveServerTestCase):
self.driver.maximize_window() self.driver.maximize_window()
self.driver.implicitly_wait(30) self.driver.implicitly_wait(30)
self.wait = WebDriverWait(self.driver, self.wait_timeout) self.wait = WebDriverWait(self.driver, self.wait_timeout)
self.apply_default_data()
self.logger = get_logger() self.logger = get_logger()
if specs := self.get_container_specs(): if specs := self.get_container_specs():
self.container = self._start_container(specs) self.container = self._start_container(specs)
@ -166,35 +165,12 @@ class SeleniumTestCase(StaticLiveServerTestCase):
self.assertEqual(user["name"].value, expected_user.name) self.assertEqual(user["name"].value, expected_user.name)
self.assertEqual(user["email"].value, expected_user.email) self.assertEqual(user["email"].value, expected_user.email)
def apply_default_data(self):
"""apply objects created by migrations after tables have been truncated"""
# Not all default objects are managed, like users for example
# Hence we still have to load all migrations and apply them, then run the ObjectManager
# Find all migration files
# load all functions
migration_files = glob("**/migrations/*.py", recursive=True)
matches = []
for migration in migration_files:
with open(migration, "r+") as migration_file:
# Check if they have a `RunPython`
if "RunPython" in migration_file.read():
matches.append(migration)
with connection.schema_editor() as schema_editor: @lru_cache
for match in matches: def get_loader():
# Load module from file path """Thin wrapper to lazily get a Migration Loader, only when it's needed
spec = spec_from_file_location("", match) and only once"""
migration_module = module_from_spec(spec) return MigrationLoader(connection)
# pyright: reportGeneralTypeIssues=false
spec.loader.exec_module(migration_module)
# Call all functions from module
for _, func in getmembers(migration_module, isfunction):
with transaction.atomic():
try:
func(apps, schema_editor)
except IntegrityError:
pass
ObjectManager().run()
def apply_migration(app_name: str, migration_name: str): def apply_migration(app_name: str, migration_name: str):
@ -203,11 +179,9 @@ def apply_migration(app_name: str, migration_name: str):
def wrapper_outter(func: Callable): def wrapper_outter(func: Callable):
"""Retry test multiple times""" """Retry test multiple times"""
loader = MigrationLoader(connection)
@wraps(func) @wraps(func)
def wrapper(self: TransactionTestCase, *args, **kwargs): def wrapper(self: TransactionTestCase, *args, **kwargs):
migration = loader.get_migration(app_name, migration_name) migration = get_loader().get_migration(app_name, migration_name)
with connection.schema_editor() as schema_editor: with connection.schema_editor() as schema_editor:
for operation in migration.operations: for operation in migration.operations:
if not isinstance(operation, RunPython): if not isinstance(operation, RunPython):