From cb6544f5aa18a8d2eabee8134508511c66485224 Mon Sep 17 00:00:00 2001 From: Daniel Date: Thu, 30 Nov 2023 11:16:43 +0100 Subject: [PATCH 01/73] add cleanup routine to runner and coordinator --- src/coordinator.py | 2 ++ src/runner.py | 9 +++++++++ 2 files changed, 11 insertions(+) diff --git a/src/coordinator.py b/src/coordinator.py index b466e1c..dfe7e06 100644 --- a/src/coordinator.py +++ b/src/coordinator.py @@ -59,6 +59,8 @@ class Coordinator: self.runners: list[Runner] = self._load_runners(self.env_paths.values()) for runner in self.runners: runner.run_tests() + for runner in self.runners: + runner.run_cleanup() logger.info("run_test() finished") def _load_runners(self, env_files: list[Path]) -> list[Runner]: diff --git a/src/runner.py b/src/runner.py index 7d0011a..27bd026 100644 --- a/src/runner.py +++ b/src/runner.py @@ -18,6 +18,7 @@ class Runner: test_dir_name: str = "" main_setup_name: Optional[str] = None main_test_name: Optional[str] = None + main_cleanup_name: Optional[str] = None dependencies: list[type["Runner"]] = [] sub_tests: list[SubTest] = [] prevent_skip = False @@ -62,6 +63,14 @@ class Runner: else: self._create_result_file(result=-1, identifier_string=identifier_string) + def run_cleanup(self): + if isinstance(self.main_cleanup_name, str): + identifier_string = self.combine_names(self.name, self.main_cleanup_name) + test_path = self.root_dir / self.test_dir_name / self.main_cleanup_name + logger.info(f"running {identifier_string}") + result = self._call_pytest(test_path) + self._create_result_file(result=result, identifier_string=identifier_string) + def _run_or_skip_test(self, identifier_string: str, test_path: Path): if not self.prevent_skip and self._is_test_passed(identifier_string, remove_existing=True): logger.info(f"skipping {identifier_string}") -- 2.47.2 From ca974d902486d57ec5e782622366fa615100b461 Mon Sep 17 00:00:00 2001 From: Daniel Date: Thu, 30 Nov 2023 11:42:52 +0100 Subject: [PATCH 02/73] WIP: add nextcloud tests --- src/tests_nextcloud/cleanup_nextcloud.py | 22 ++++++++++++ src/tests_nextcloud/conftest.py | 1 + src/tests_nextcloud/runner_nextcloud.py | 16 +++++++++ src/tests_nextcloud/setup_nextcloud.py | 35 +++++++++++++++++++ src/tests_nextcloud/tests_nextcloud.py | 13 +++++++ .../tests_nextcloud_onlyoffice.py | 19 ++++++++++ 6 files changed, 106 insertions(+) create mode 100644 src/tests_nextcloud/cleanup_nextcloud.py create mode 100644 src/tests_nextcloud/conftest.py create mode 100644 src/tests_nextcloud/runner_nextcloud.py create mode 100644 src/tests_nextcloud/setup_nextcloud.py create mode 100644 src/tests_nextcloud/tests_nextcloud.py create mode 100644 src/tests_nextcloud/tests_nextcloud_onlyoffice.py diff --git a/src/tests_nextcloud/cleanup_nextcloud.py b/src/tests_nextcloud/cleanup_nextcloud.py new file mode 100644 index 0000000..cc54504 --- /dev/null +++ b/src/tests_nextcloud/cleanup_nextcloud.py @@ -0,0 +1,22 @@ +import pytest + + +@pytest.fixture(scope="session", autouse=True) +def delete_nextcloud_user(browser: Browser): + """Delete Nextcloud Account""" + yield + context = setup_context(browser, f"{STATES}/admin_state.json") + page = context.new_page() + page.goto(CONFIG["domain"]) + with page.expect_popup() as nextcloud_info: + page.get_by_role("link", name="Nextcloud").click() + nextcloud = nextcloud_info.value + nextcloud.get_by_role("link", name="Open settings menu").click() + nextcloud.get_by_role("link", name="Users").click() + nextcloud.locator("#app-content div").filter(has_text=testuser["username"]).get_by_role( + "button", name="Toggle user actions menu" + ).click() + nextcloud.get_by_role("button", name="Delete user").click() + nextcloud.get_by_role("button", name=f"Delete authentik-{testuser['username']}'s account").click() + context.tracing.stop(path=f"{RECORDS}/nextcloud_delete_user.zip") + context.close() diff --git a/src/tests_nextcloud/conftest.py b/src/tests_nextcloud/conftest.py new file mode 100644 index 0000000..df1eac3 --- /dev/null +++ b/src/tests_nextcloud/conftest.py @@ -0,0 +1 @@ +from src.tests_authentik.fixtures_authentik import admin_context, authentik_admin_page, user_context diff --git a/src/tests_nextcloud/runner_nextcloud.py b/src/tests_nextcloud/runner_nextcloud.py new file mode 100644 index 0000000..d360dc8 --- /dev/null +++ b/src/tests_nextcloud/runner_nextcloud.py @@ -0,0 +1,16 @@ +from src.runner import Runner, SubTest +from src.tests_authentik.runner_authentik import RunnerAuthentik + + +def fake_condition(arg): + return False + + +class RunnerNextcloud(Runner): + name: str = "nextcloud" + test_dir_name: str = "tests_nextcloud" + main_setup_name = "setup_nextcloud.py" + main_test_name = "tests_nextcloud.py" + main_cleanup_name = "cleanup_nextcloud.py" + dependencies = [RunnerAuthentik] + sub_tests: list[SubTest] = [SubTest(condition=fake_condition, test_file="tests_nextcloud_onlyoffice.py")] diff --git a/src/tests_nextcloud/setup_nextcloud.py b/src/tests_nextcloud/setup_nextcloud.py new file mode 100644 index 0000000..eb53182 --- /dev/null +++ b/src/tests_nextcloud/setup_nextcloud.py @@ -0,0 +1,35 @@ +import pytest + + +@pytest.fixture(scope="session", autouse=True) +def nc_login(browser: Browser): + """Nextcloud Login""" + context = setup_context(browser, f"{STATES}/user_state.json") + page = context.new_page() + page.goto(CONFIG["domain"]) + with page.expect_popup() as nextcloud_info: + link = page.get_by_role("link", name="Nextcloud") + CONFIG["nc_domain"] = link.get_attribute("href") + link.click() + nextcloud = nextcloud_info.value + check_for(nextcloud.get_by_role("link", name="Name")) + if nextcloud.query_selector(".close-icon"): + close_button = nextcloud.get_by_role("button", name="Close modal") + close_button.click() + expect(close_button).to_be_hidden() + nextcloud.wait_for_timeout(2000) + context.storage_state(path=f"{STATES}/nc_user_state.json") + context.tracing.stop(path=f"{RECORDS}/nextcloud_login_user.zip") + context.close() + + +@pytest.fixture +def nc_session(browser: Browser): + """Reuse Nextcloud User Session""" + context = setup_context(browser, f"{STATES}/nc_user_state.json") + page = context.new_page() + page.goto(CONFIG["nc_domain"]) + if page.query_selector(".close-icon"): + page.get_by_role("button", name="Close modal").click() + yield context, page + context.close() diff --git a/src/tests_nextcloud/tests_nextcloud.py b/src/tests_nextcloud/tests_nextcloud.py new file mode 100644 index 0000000..c9bc5be --- /dev/null +++ b/src/tests_nextcloud/tests_nextcloud.py @@ -0,0 +1,13 @@ +def test_nextcloud(nc_session): + """Test Nextcloud""" + context, page = nc_session + # if page.query_selector('.close-icon'): + # page.get_by_role("button", name="Close modal").click() + if CONFIG.get("default_quota"): + quota = int( + page.get_by_role("listitem", name="Storage informations").get_by_role("link").inner_text().split()[3] + ) + assert quota == CONFIG["default_quota"] + for app in CONFIG["nc_apps"]: + check_for(page.get_by_role("link", name=app)) + context.tracing.stop(path=f"{RECORDS}/nextcloud.zip") diff --git a/src/tests_nextcloud/tests_nextcloud_onlyoffice.py b/src/tests_nextcloud/tests_nextcloud_onlyoffice.py new file mode 100644 index 0000000..0b78e5a --- /dev/null +++ b/src/tests_nextcloud/tests_nextcloud_onlyoffice.py @@ -0,0 +1,19 @@ +def test_onlyoffice(nc_session): + """Test Onlyoffice in Nextcloud""" + context, page = nc_session + # if page.query_selector('.close-icon'): + # page.get_by_role("button", name="Close modal").click() + page.get_by_role("link", name="New file/folder menu").click() + page.get_by_role("link", name="New document").click() + page.locator("#view9-input-file").fill("test.docx") + page.get_by_role("button", name="Submit").click() + outer_frame = page.frame_locator("#onlyofficeFrame") + check_for(outer_frame.locator("body")) + inner_frame = outer_frame.frame_locator("#app > iframe") + check_for(inner_frame.locator("body")) + onlyoffice = page.frame("frameEditor") + check_for(onlyoffice.locator('//*[@id="area_id"]')) + onlyoffice.locator("#btn-goback").click() + page.get_by_role("link", name="Not favorited test .docx Share Actions").get_by_role("link", name="Actions").click() + page.get_by_role("link", name="Delete file").click() + context.tracing.stop(path=f"{RECORDS}/onlyoffice.zip") -- 2.47.2 From f270f3d4a9d98275e98fef2b029f4e2e25bd4f93 Mon Sep 17 00:00:00 2001 From: Daniel Date: Thu, 30 Nov 2023 11:47:55 +0100 Subject: [PATCH 03/73] disable cleanup for now, unclear what it does --- src/tests_nextcloud/cleanup_nextcloud.py | 7 +++++-- src/tests_nextcloud/runner_nextcloud.py | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/tests_nextcloud/cleanup_nextcloud.py b/src/tests_nextcloud/cleanup_nextcloud.py index cc54504..0bd456f 100644 --- a/src/tests_nextcloud/cleanup_nextcloud.py +++ b/src/tests_nextcloud/cleanup_nextcloud.py @@ -1,9 +1,12 @@ import pytest +from playwright.sync_api import BrowserContext, expect + +# todo: what is this test for, why is it a fixture? -> ignore for now @pytest.fixture(scope="session", autouse=True) -def delete_nextcloud_user(browser: Browser): - """Delete Nextcloud Account""" +def delete_nextcloud_user(admin_context: BrowserContext): + """Delete Nextcloud User""" yield context = setup_context(browser, f"{STATES}/admin_state.json") page = context.new_page() diff --git a/src/tests_nextcloud/runner_nextcloud.py b/src/tests_nextcloud/runner_nextcloud.py index d360dc8..e5442f6 100644 --- a/src/tests_nextcloud/runner_nextcloud.py +++ b/src/tests_nextcloud/runner_nextcloud.py @@ -11,6 +11,6 @@ class RunnerNextcloud(Runner): test_dir_name: str = "tests_nextcloud" main_setup_name = "setup_nextcloud.py" main_test_name = "tests_nextcloud.py" - main_cleanup_name = "cleanup_nextcloud.py" + # main_cleanup_name = "cleanup_nextcloud.py" dependencies = [RunnerAuthentik] sub_tests: list[SubTest] = [SubTest(condition=fake_condition, test_file="tests_nextcloud_onlyoffice.py")] -- 2.47.2 From 1b3396baf42f6c9256999e6a76c60d48d516daf6 Mon Sep 17 00:00:00 2001 From: Daniel Date: Thu, 30 Nov 2023 11:49:31 +0100 Subject: [PATCH 04/73] add make_url util function --- src/utils.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/utils.py b/src/utils.py index 92f7b26..dcf66f5 100644 --- a/src/utils.py +++ b/src/utils.py @@ -18,3 +18,8 @@ def rmtree(root_dir: Path): child.unlink() root_dir.rmdir() + + +def make_url(domain: str) -> str: + """adds 'http://' at the beginning of a string""" + return "https://" + domain -- 2.47.2 From d29e8102e956308455e9f7c260ad908df1c1bab1 Mon Sep 17 00:00:00 2001 From: Daniel Date: Thu, 30 Nov 2023 11:58:23 +0100 Subject: [PATCH 05/73] add RunnerNextcloud --- src/coordinator.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/coordinator.py b/src/coordinator.py index dfe7e06..f7af1aa 100644 --- a/src/coordinator.py +++ b/src/coordinator.py @@ -8,6 +8,7 @@ from src.dirmanager import DirManager from src.html_helper import merge_html_files from src.runner import Runner from src.tests_authentik.runner_authentik import RunnerAuthentik +from src.tests_nextcloud.runner_nextcloud import RunnerNextcloud from src.tests_wordpress.runner_wordpress import RunnerWordpress from src.utils import rmtree @@ -15,6 +16,7 @@ from src.utils import rmtree RUNNER_DICT: dict[str, type[Runner]] = { "authentik": RunnerAuthentik, "wordpress": RunnerWordpress, + "nextcloud": RunnerNextcloud, } -- 2.47.2 From 2394d2fd9bda1ccc246b5c45bf33ab979d44fd05 Mon Sep 17 00:00:00 2001 From: Daniel Date: Thu, 30 Nov 2023 12:00:53 +0100 Subject: [PATCH 06/73] typo --- src/coordinator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coordinator.py b/src/coordinator.py index f7af1aa..885bb9d 100644 --- a/src/coordinator.py +++ b/src/coordinator.py @@ -12,7 +12,7 @@ from src.tests_nextcloud.runner_nextcloud import RunnerNextcloud from src.tests_wordpress.runner_wordpress import RunnerWordpress from src.utils import rmtree -# Register all runners here. Each .env file with TYPE=authentik will be ran with RunnerAuthentik +# Register all runners here. Each .env file with TYPE=authentik will be run with RunnerAuthentik RUNNER_DICT: dict[str, type[Runner]] = { "authentik": RunnerAuthentik, "wordpress": RunnerWordpress, -- 2.47.2 From 641d89438b3c237131b6a9e780120e7f5cce20c7 Mon Sep 17 00:00:00 2001 From: Daniel Date: Thu, 30 Nov 2023 12:21:27 +0100 Subject: [PATCH 07/73] add authentik_user_page --- src/tests_authentik/fixtures_authentik.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/tests_authentik/fixtures_authentik.py b/src/tests_authentik/fixtures_authentik.py index 21bddbe..e9b467d 100644 --- a/src/tests_authentik/fixtures_authentik.py +++ b/src/tests_authentik/fixtures_authentik.py @@ -21,7 +21,6 @@ def admin_context(context: BrowserContext, DIR: DirManager) -> BrowserContext: @pytest.fixture def authentik_admin_page(admin_context: BrowserContext, DIR: DirManager) -> Page: page = admin_context.new_page() - page.pause() authentik_env_file = DIR.ENV_FILES / "authentik" authentik_config: dict[str, str] = dotenv_values(authentik_env_file) # type: ignore url = "https://" + authentik_config["DOMAIN"] @@ -36,3 +35,13 @@ def user_context(context: BrowserContext, DIR: DirManager) -> BrowserContext: context.add_cookies(storage_state["cookies"]) context.set_default_timeout(TIMEOUT) return context + + +@pytest.fixture +def authentik_user_page(user_context: BrowserContext, DIR: DirManager) -> Page: + page = user_context.new_page() + authentik_env_file = DIR.ENV_FILES / "authentik" + authentik_config: dict[str, str] = dotenv_values(authentik_env_file) # type: ignore + url = "https://" + authentik_config["DOMAIN"] + page.goto(url) + return page -- 2.47.2 From 827827b962503bb552adfa74c7b21553f4681d97 Mon Sep 17 00:00:00 2001 From: Daniel Date: Thu, 30 Nov 2023 12:22:19 +0100 Subject: [PATCH 08/73] rename authentik context json files --- src/tests_authentik/fixtures_authentik.py | 4 ++-- src/tests_authentik/setup_authentik.py | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/tests_authentik/fixtures_authentik.py b/src/tests_authentik/fixtures_authentik.py index e9b467d..198e189 100644 --- a/src/tests_authentik/fixtures_authentik.py +++ b/src/tests_authentik/fixtures_authentik.py @@ -11,7 +11,7 @@ TIMEOUT = 5000 @pytest.fixture def admin_context(context: BrowserContext, DIR: DirManager) -> BrowserContext: - state_file = DIR.STATES / "admin_state.json" + state_file = DIR.STATES / "authentik_admin_state.json" storage_state = json.loads(state_file.read_bytes()) context.add_cookies(storage_state["cookies"]) context.set_default_timeout(TIMEOUT) @@ -30,7 +30,7 @@ def authentik_admin_page(admin_context: BrowserContext, DIR: DirManager) -> Page @pytest.fixture def user_context(context: BrowserContext, DIR: DirManager) -> BrowserContext: - state_file = DIR.STATES / "user_state.json" + state_file = DIR.STATES / "authentik_user_state.json" storage_state = json.loads(state_file.read_bytes()) context.add_cookies(storage_state["cookies"]) context.set_default_timeout(TIMEOUT) diff --git a/src/tests_authentik/setup_authentik.py b/src/tests_authentik/setup_authentik.py index 831a137..ee6cff4 100644 --- a/src/tests_authentik/setup_authentik.py +++ b/src/tests_authentik/setup_authentik.py @@ -34,7 +34,7 @@ def test_create_admin_login(context: BrowserContext, dotenv_config: dict[str, st expect(page.locator("ak-library")).to_be_visible() # save state - context.storage_state(path=f"{DIR.STATES}/admin_state.json") + context.storage_state(path=f"{DIR.STATES}/authentik_admin_state.json") def check_if_user_exists(admin_context: BrowserContext, dotenv_config: dict[str, str]): @@ -107,7 +107,7 @@ def test_create_user_session(context: BrowserContext, dotenv_config: dict[str, s context.set_default_timeout(TIMEOUT) # load admin cookies - state_file = DIR.STATES / "admin_state.json" + state_file = DIR.STATES / "authentik_admin_state.json" storage_state = json.loads(state_file.read_bytes()) context.add_cookies(storage_state["cookies"]) @@ -123,4 +123,4 @@ def test_create_user_session(context: BrowserContext, dotenv_config: dict[str, s context.clear_cookies() create_user(context, invite_link) - context.storage_state(path=f"{DIR.STATES}/user_state.json") + context.storage_state(path=f"{DIR.STATES}/authentik_user_state.json") -- 2.47.2 From 6cfca22168affb9e1e8a19eed50071f858799759 Mon Sep 17 00:00:00 2001 From: Daniel Date: Thu, 30 Nov 2023 12:25:35 +0100 Subject: [PATCH 09/73] simplify vars --- src/tests_authentik/fixtures_authentik.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/tests_authentik/fixtures_authentik.py b/src/tests_authentik/fixtures_authentik.py index 198e189..1d50091 100644 --- a/src/tests_authentik/fixtures_authentik.py +++ b/src/tests_authentik/fixtures_authentik.py @@ -21,9 +21,9 @@ def admin_context(context: BrowserContext, DIR: DirManager) -> BrowserContext: @pytest.fixture def authentik_admin_page(admin_context: BrowserContext, DIR: DirManager) -> Page: page = admin_context.new_page() - authentik_env_file = DIR.ENV_FILES / "authentik" - authentik_config: dict[str, str] = dotenv_values(authentik_env_file) # type: ignore - url = "https://" + authentik_config["DOMAIN"] + env_file = DIR.ENV_FILES / "authentik" + config: dict[str, str] = dotenv_values(env_file) # type: ignore + url = "https://" + config["DOMAIN"] page.goto(url) return page @@ -40,8 +40,8 @@ def user_context(context: BrowserContext, DIR: DirManager) -> BrowserContext: @pytest.fixture def authentik_user_page(user_context: BrowserContext, DIR: DirManager) -> Page: page = user_context.new_page() - authentik_env_file = DIR.ENV_FILES / "authentik" - authentik_config: dict[str, str] = dotenv_values(authentik_env_file) # type: ignore - url = "https://" + authentik_config["DOMAIN"] + env_file = DIR.ENV_FILES / "authentik" + config: dict[str, str] = dotenv_values(env_file) # type: ignore + url = "https://" + config["DOMAIN"] page.goto(url) return page -- 2.47.2 From ab295d815a99aa99e1bc8cc106e1638b02506c54 Mon Sep 17 00:00:00 2001 From: Daniel Date: Thu, 30 Nov 2023 12:26:39 +0100 Subject: [PATCH 10/73] add wordpress setup --- src/tests_wordpress/runner_wordpress.py | 1 + src/tests_wordpress/setup_wordpress.py | 37 +++++++++++++++++++++++++ src/tests_wordpress/test_wordpress.py | 13 --------- 3 files changed, 38 insertions(+), 13 deletions(-) create mode 100644 src/tests_wordpress/setup_wordpress.py diff --git a/src/tests_wordpress/runner_wordpress.py b/src/tests_wordpress/runner_wordpress.py index 4355f22..4e6e5b7 100644 --- a/src/tests_wordpress/runner_wordpress.py +++ b/src/tests_wordpress/runner_wordpress.py @@ -20,6 +20,7 @@ def condition_has_locale(dotenv_config: dict[str, str]) -> bool: class RunnerWordpress(Runner): name = "wordpress" test_dir_name = "tests_wordpress" + main_setup_name = "setup_wordpress.py" main_test_name = "test_wordpress.py" dependencies: list[type[Runner]] = [RunnerAuthentik] sub_tests = [ diff --git a/src/tests_wordpress/setup_wordpress.py b/src/tests_wordpress/setup_wordpress.py new file mode 100644 index 0000000..5ddbb7c --- /dev/null +++ b/src/tests_wordpress/setup_wordpress.py @@ -0,0 +1,37 @@ +import pytest +from playwright.sync_api import BrowserContext, Page, expect + +from src.dirmanager import DirManager + + +@pytest.mark.xfail(reason="wordpress sso login has not been generated") +def test_visit_from_domain(admin_context: BrowserContext, dotenv_config: dict[str, str]): + # with authentik_admin_page.expect_popup() as event_context: + # authentik_admin_page.get_by_role("link", name="Wordpress").click() + # page_wordpress = event_context.value + + page = admin_context.new_page() + url = "https://" + dotenv_config["DOMAIN"] + page.goto(url) + + # look for content wrapper + expect(page.locator("#wpcontent")).to_be_visible() + + # look for admin bar + expect(page.locator("#wpadminbar")).to_be_visible() + + +def setup_visit_from_authentik(authentik_admin_page: Page, DIR: DirManager): + with authentik_admin_page.expect_popup() as event_context: + authentik_admin_page.get_by_role("link", name="Wordpress").click() + page_wordpress = event_context.value + + # look for content wrapper + expect(page_wordpress.locator("#wpcontent")).to_be_visible() + + # look for admin bar + expect(page_wordpress.locator("#wpadminbar")).to_be_visible() + + context = page_wordpress.context + + context.storage_state(path=f"{DIR.STATES}/wordpress_admin_state.json") diff --git a/src/tests_wordpress/test_wordpress.py b/src/tests_wordpress/test_wordpress.py index bb9275d..e69de29 100644 --- a/src/tests_wordpress/test_wordpress.py +++ b/src/tests_wordpress/test_wordpress.py @@ -1,13 +0,0 @@ -from playwright.sync_api import Page, expect - - -def test_visit_from_authentik(authentik_admin_page: Page): - with authentik_admin_page.expect_popup() as event_context: - authentik_admin_page.get_by_role("link", name="Wordpress").click() - page_wordpress = event_context.value - - # look for content wrapper - expect(page_wordpress.locator("#wpcontent")).to_be_visible() - - # look for admin bar - expect(page_wordpress.locator("#wpadminbar")).to_be_visible() -- 2.47.2 From 60f281db95e8a58a5f7beafad93225902d778127 Mon Sep 17 00:00:00 2001 From: Daniel Date: Thu, 30 Nov 2023 12:26:59 +0100 Subject: [PATCH 11/73] add fixtures to wordpress conftest --- src/tests_wordpress/conftest.py | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/src/tests_wordpress/conftest.py b/src/tests_wordpress/conftest.py index df1eac3..0380c77 100644 --- a/src/tests_wordpress/conftest.py +++ b/src/tests_wordpress/conftest.py @@ -1 +1,28 @@ -from src.tests_authentik.fixtures_authentik import admin_context, authentik_admin_page, user_context +import json + +import pytest +from dotenv import dotenv_values +from playwright.sync_api import BrowserContext, Page + +from src.dirmanager import DirManager + +TIMEOUT = 5000 + + +@pytest.fixture +def wordpress_admin_context(context: BrowserContext, DIR: DirManager) -> BrowserContext: + state_file = DIR.STATES / "wordpress_admin_state.json" + storage_state = json.loads(state_file.read_bytes()) + context.add_cookies(storage_state["cookies"]) + context.set_default_timeout(TIMEOUT) + return context + + +@pytest.fixture +def wordpress_admin_page(wordpress_admin_context: BrowserContext, DIR: DirManager) -> Page: + page = wordpress_admin_context.new_page() + env_file = DIR.ENV_FILES / "wordpress" + config: dict[str, str] = dotenv_values(env_file) # type: ignore + url = "https://" + config["DOMAIN"] + page.goto(url) + return page -- 2.47.2 From 466acbd7606df01ad6efa44a3c633ebaf8269781 Mon Sep 17 00:00:00 2001 From: Daniel Date: Thu, 30 Nov 2023 13:13:30 +0100 Subject: [PATCH 12/73] disable localization test --- src/tests_wordpress/runner_wordpress.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tests_wordpress/runner_wordpress.py b/src/tests_wordpress/runner_wordpress.py index 4e6e5b7..1f9f4cd 100644 --- a/src/tests_wordpress/runner_wordpress.py +++ b/src/tests_wordpress/runner_wordpress.py @@ -24,6 +24,6 @@ class RunnerWordpress(Runner): main_test_name = "test_wordpress.py" dependencies: list[type[Runner]] = [RunnerAuthentik] sub_tests = [ - SubTest(condition=condition_has_locale, test_file="test_wordpress_localization.py"), + # SubTest(condition=condition_has_locale, test_file="test_wordpress_localization.py"), ] prevent_skip = True -- 2.47.2 From dbbbd3a43dd0ec98e058bb1aea5f38edb6aea059 Mon Sep 17 00:00:00 2001 From: Daniel Date: Thu, 30 Nov 2023 16:34:31 +0100 Subject: [PATCH 13/73] improve readme --- README.md | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a4ffa00..eb464f1 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Clone +# GIT Clone To clone with submodules, use these git commands: @@ -8,7 +8,23 @@ git submodule update --init // add submodule after normal cloning git submodule update --remote // update submodules ``` -# Run +# Run without Docker + +### Installation + +Create a python environment and install all dependencies via + +```bash +pip install -r requirements.txt +``` + +Run the script with + +```bash +python main.py +``` + +# Run with Docker ```bash docker compose build -- 2.47.2 From 8c5b1dab9459e72f4757c2baea02fb70e8c1a6ec Mon Sep 17 00:00:00 2001 From: Daniel Date: Thu, 30 Nov 2023 16:49:18 +0100 Subject: [PATCH 14/73] add playwright install --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index eb464f1..9d61b40 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,7 @@ Create a python environment and install all dependencies via ```bash pip install -r requirements.txt +playwright install ``` Run the script with -- 2.47.2 From ab202afc87407f0ac468945a9457320adc4b50e1 Mon Sep 17 00:00:00 2001 From: Daniel Date: Thu, 30 Nov 2023 17:41:47 +0100 Subject: [PATCH 15/73] add pytest-html --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index f1577e2..a17d5a2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,5 @@ pytest +pytest-html pytest-playwright python-dotenv icecream -- 2.47.2 From e44610291257944c856e9494e50735d64e1df387 Mon Sep 17 00:00:00 2001 From: Daniel Date: Thu, 30 Nov 2023 17:44:05 +0100 Subject: [PATCH 16/73] increase timeout --- src/tests_authentik/setup_authentik.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tests_authentik/setup_authentik.py b/src/tests_authentik/setup_authentik.py index ee6cff4..ab52270 100644 --- a/src/tests_authentik/setup_authentik.py +++ b/src/tests_authentik/setup_authentik.py @@ -11,7 +11,7 @@ ADMIN_PASS = os.environ["ADMIN_PASS"] LOCALE = {"Accept-Language": "de_DE"} TESTUSER = {"username": "testuser", "name": "Test User", "password": "test123", "email": "test@example.com"} -TIMEOUT = 6000 +TIMEOUT = 10000 def test_create_admin_login(context: BrowserContext, dotenv_config: dict[str, str], DIR: DirManager): -- 2.47.2 From 19f44f3028766f4406896f7774dbc78a0923ef62 Mon Sep 17 00:00:00 2001 From: Daniel Date: Thu, 30 Nov 2023 17:50:02 +0100 Subject: [PATCH 17/73] remove assert in _dependencies_passed, because it crashes the coordinator. instead, just skip the test with a warning --- src/runner.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/runner.py b/src/runner.py index 27bd026..3c48c4b 100644 --- a/src/runner.py +++ b/src/runner.py @@ -36,7 +36,9 @@ class Runner: def run_tests(self): # check if required dependencies have passed - self._assert_dependencies_passed() + if not self._dependencies_passed(): + logger.warning("skipping run_tests() of {self.name}, because some dependencies have not passed") + return # run main setup if available if isinstance(self.main_setup_name, str): @@ -64,6 +66,9 @@ class Runner: self._create_result_file(result=-1, identifier_string=identifier_string) def run_cleanup(self): + if not self._dependencies_passed(): + logger.warning("skipping run_cleanup() of {self.name}, because some dependencies have not passed") + return if isinstance(self.main_cleanup_name, str): identifier_string = self.combine_names(self.name, self.main_cleanup_name) test_path = self.root_dir / self.test_dir_name / self.main_cleanup_name @@ -153,15 +158,13 @@ class Runner: with open(file_path, "w") as _: pass # create empty file - def _assert_dependencies_passed(self): + def _dependencies_passed(self): """assert that all dependencie setups passed before""" passed_tests = [r.name for r in self.DIRS.RESULTS.glob("*") if "passed" in r.name] for dependencie in self.dependencies: dependencie_identifier = self.combine_names(dependencie.name, dependencie.main_setup_name) - assert any( - dependencie_identifier in f for f in passed_tests - ), f"could not run {self.name} because {dependencie} did not run before" + return any(dependencie_identifier in f for f in passed_tests) @staticmethod def result_int_to_str(result_int: int) -> str: -- 2.47.2 From 58303c55ca83e39497f71b8799e9dfb9f176e42a Mon Sep 17 00:00:00 2001 From: Daniel Date: Thu, 30 Nov 2023 18:10:04 +0100 Subject: [PATCH 18/73] turn warning into f-string --- src/runner.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/runner.py b/src/runner.py index 3c48c4b..df46ff4 100644 --- a/src/runner.py +++ b/src/runner.py @@ -37,7 +37,7 @@ class Runner: def run_tests(self): # check if required dependencies have passed if not self._dependencies_passed(): - logger.warning("skipping run_tests() of {self.name}, because some dependencies have not passed") + logger.warning(f"skipping run_tests() of {self.name}, because some dependencies have not passed") return # run main setup if available -- 2.47.2 From 565903f3049f3e65e4aac190d11437c52463337f Mon Sep 17 00:00:00 2001 From: Daniel Date: Thu, 30 Nov 2023 18:22:30 +0100 Subject: [PATCH 19/73] fix _dependencies_passed --- src/runner.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/runner.py b/src/runner.py index df46ff4..1e2cc6f 100644 --- a/src/runner.py +++ b/src/runner.py @@ -159,12 +159,14 @@ class Runner: pass # create empty file def _dependencies_passed(self): - """assert that all dependencie setups passed before""" + """returns true if the setup of each dependency has passed""" passed_tests = [r.name for r in self.DIRS.RESULTS.glob("*") if "passed" in r.name] + results = [] for dependencie in self.dependencies: dependencie_identifier = self.combine_names(dependencie.name, dependencie.main_setup_name) - return any(dependencie_identifier in f for f in passed_tests) + results.append(any(dependencie_identifier in f for f in passed_tests)) + return all(results) @staticmethod def result_int_to_str(result_int: int) -> str: -- 2.47.2 From 636c39d55745abf23e3d39784f41bb616d4590d0 Mon Sep 17 00:00:00 2001 From: Daniel Date: Thu, 30 Nov 2023 18:25:41 +0100 Subject: [PATCH 20/73] remove timeout and locale to replace with global timeout --- src/tests_authentik/fixtures_authentik.py | 4 ---- src/tests_authentik/setup_authentik.py | 11 ++--------- src/tests_wordpress/conftest.py | 3 --- 3 files changed, 2 insertions(+), 16 deletions(-) diff --git a/src/tests_authentik/fixtures_authentik.py b/src/tests_authentik/fixtures_authentik.py index 1d50091..836f3ca 100644 --- a/src/tests_authentik/fixtures_authentik.py +++ b/src/tests_authentik/fixtures_authentik.py @@ -6,15 +6,12 @@ from playwright.sync_api import BrowserContext, Page from src.dirmanager import DirManager -TIMEOUT = 5000 - @pytest.fixture def admin_context(context: BrowserContext, DIR: DirManager) -> BrowserContext: state_file = DIR.STATES / "authentik_admin_state.json" storage_state = json.loads(state_file.read_bytes()) context.add_cookies(storage_state["cookies"]) - context.set_default_timeout(TIMEOUT) return context @@ -33,7 +30,6 @@ def user_context(context: BrowserContext, DIR: DirManager) -> BrowserContext: state_file = DIR.STATES / "authentik_user_state.json" storage_state = json.loads(state_file.read_bytes()) context.add_cookies(storage_state["cookies"]) - context.set_default_timeout(TIMEOUT) return context diff --git a/src/tests_authentik/setup_authentik.py b/src/tests_authentik/setup_authentik.py index ab52270..627dbbe 100644 --- a/src/tests_authentik/setup_authentik.py +++ b/src/tests_authentik/setup_authentik.py @@ -9,15 +9,12 @@ from src.dirmanager import DirManager ADMIN_USER = os.environ["ADMIN_USER"] ADMIN_PASS = os.environ["ADMIN_PASS"] -LOCALE = {"Accept-Language": "de_DE"} + TESTUSER = {"username": "testuser", "name": "Test User", "password": "test123", "email": "test@example.com"} -TIMEOUT = 10000 def test_create_admin_login(context: BrowserContext, dotenv_config: dict[str, str], DIR: DirManager): # go to page - context.set_extra_http_headers(LOCALE) - context.set_default_timeout(TIMEOUT) page = context.new_page() url = "https://" + dotenv_config["DOMAIN"] page.goto(url) @@ -103,9 +100,6 @@ def create_user(user_context: BrowserContext, invitelink): def test_create_user_session(context: BrowserContext, dotenv_config: dict[str, str], DIR: DirManager): - context.set_extra_http_headers(LOCALE) - context.set_default_timeout(TIMEOUT) - # load admin cookies state_file = DIR.STATES / "authentik_admin_state.json" storage_state = json.loads(state_file.read_bytes()) @@ -116,8 +110,7 @@ def test_create_user_session(context: BrowserContext, dotenv_config: dict[str, s pass context.clear_cookies() else: - ## create user - # create invite_link + # get invite_link invite_link = create_invite_link(context, dotenv_config) # create user context.clear_cookies() diff --git a/src/tests_wordpress/conftest.py b/src/tests_wordpress/conftest.py index 0380c77..82156e3 100644 --- a/src/tests_wordpress/conftest.py +++ b/src/tests_wordpress/conftest.py @@ -6,15 +6,12 @@ from playwright.sync_api import BrowserContext, Page from src.dirmanager import DirManager -TIMEOUT = 5000 - @pytest.fixture def wordpress_admin_context(context: BrowserContext, DIR: DirManager) -> BrowserContext: state_file = DIR.STATES / "wordpress_admin_state.json" storage_state = json.loads(state_file.read_bytes()) context.add_cookies(storage_state["cookies"]) - context.set_default_timeout(TIMEOUT) return context -- 2.47.2 From 5d3743962f5225a2387480c7133905f440af4b42 Mon Sep 17 00:00:00 2001 From: Daniel Date: Thu, 30 Nov 2023 18:25:58 +0100 Subject: [PATCH 21/73] add global timeout settings --- src/conftest.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/conftest.py b/src/conftest.py index 3eb168c..770e666 100644 --- a/src/conftest.py +++ b/src/conftest.py @@ -9,11 +9,22 @@ from pathlib import Path import pytest from dotenv import dotenv_values +from playwright.sync_api import BrowserContext, expect from pytest import Parser from src.dirmanager import DirManager -TIMEOUT = 5000 +# global timeout and LOCALE +LOCALE = {"Accept-Language": "de_DE"} +TIMEOUT = 10_000 +expect.set_options(timeout=TIMEOUT) + + +@pytest.fixture +def context(context: BrowserContext) -> BrowserContext: + context.set_default_timeout(TIMEOUT) + context.set_extra_http_headers(LOCALE) + return context def pytest_addoption(parser: Parser): -- 2.47.2 From add21a694e5c380869ab14b535f78358bb7bf7c8 Mon Sep 17 00:00:00 2001 From: Daniel Date: Thu, 30 Nov 2023 18:42:15 +0100 Subject: [PATCH 22/73] rename more authentik fixtures --- src/tests_authentik/fixtures_authentik.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tests_authentik/fixtures_authentik.py b/src/tests_authentik/fixtures_authentik.py index 836f3ca..94af38f 100644 --- a/src/tests_authentik/fixtures_authentik.py +++ b/src/tests_authentik/fixtures_authentik.py @@ -8,7 +8,7 @@ from src.dirmanager import DirManager @pytest.fixture -def admin_context(context: BrowserContext, DIR: DirManager) -> BrowserContext: +def authentik_admin_context(context: BrowserContext, DIR: DirManager) -> BrowserContext: state_file = DIR.STATES / "authentik_admin_state.json" storage_state = json.loads(state_file.read_bytes()) context.add_cookies(storage_state["cookies"]) @@ -26,7 +26,7 @@ def authentik_admin_page(admin_context: BrowserContext, DIR: DirManager) -> Page @pytest.fixture -def user_context(context: BrowserContext, DIR: DirManager) -> BrowserContext: +def authentik_user_context(context: BrowserContext, DIR: DirManager) -> BrowserContext: state_file = DIR.STATES / "authentik_user_state.json" storage_state = json.loads(state_file.read_bytes()) context.add_cookies(storage_state["cookies"]) -- 2.47.2 From 3e142a7593ee9fdae71b46d3cde01c305cdb6548 Mon Sep 17 00:00:00 2001 From: Daniel Date: Thu, 30 Nov 2023 18:42:27 +0100 Subject: [PATCH 23/73] import all authentik fixtures --- src/tests_wordpress/conftest.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/tests_wordpress/conftest.py b/src/tests_wordpress/conftest.py index 82156e3..e57e575 100644 --- a/src/tests_wordpress/conftest.py +++ b/src/tests_wordpress/conftest.py @@ -5,6 +5,12 @@ from dotenv import dotenv_values from playwright.sync_api import BrowserContext, Page from src.dirmanager import DirManager +from src.tests_authentik.fixtures_authentik import ( + authentik_admin_context, + authentik_admin_page, + authentik_user_context, + authentik_user_page, +) @pytest.fixture -- 2.47.2 From 21dd190fb36411e885adcab1a8bd891840763d8d Mon Sep 17 00:00:00 2001 From: Daniel Date: Thu, 30 Nov 2023 18:43:53 +0100 Subject: [PATCH 24/73] use new authentik fixture name --- src/tests_wordpress/setup_wordpress.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/tests_wordpress/setup_wordpress.py b/src/tests_wordpress/setup_wordpress.py index 5ddbb7c..b6ed2dc 100644 --- a/src/tests_wordpress/setup_wordpress.py +++ b/src/tests_wordpress/setup_wordpress.py @@ -5,12 +5,8 @@ from src.dirmanager import DirManager @pytest.mark.xfail(reason="wordpress sso login has not been generated") -def test_visit_from_domain(admin_context: BrowserContext, dotenv_config: dict[str, str]): - # with authentik_admin_page.expect_popup() as event_context: - # authentik_admin_page.get_by_role("link", name="Wordpress").click() - # page_wordpress = event_context.value - - page = admin_context.new_page() +def test_visit_from_domain(authentik_admin_context: BrowserContext, dotenv_config: dict[str, str]): + page = authentik_admin_context.new_page() url = "https://" + dotenv_config["DOMAIN"] page.goto(url) @@ -21,7 +17,7 @@ def test_visit_from_domain(admin_context: BrowserContext, dotenv_config: dict[st expect(page.locator("#wpadminbar")).to_be_visible() -def setup_visit_from_authentik(authentik_admin_page: Page, DIR: DirManager): +def test_visit_from_authentik(authentik_admin_page: Page, DIR: DirManager): with authentik_admin_page.expect_popup() as event_context: authentik_admin_page.get_by_role("link", name="Wordpress").click() page_wordpress = event_context.value @@ -35,3 +31,7 @@ def setup_visit_from_authentik(authentik_admin_page: Page, DIR: DirManager): context = page_wordpress.context context.storage_state(path=f"{DIR.STATES}/wordpress_admin_state.json") + + +def setup_something(): + assert False -- 2.47.2 From d96927ae02d4632b769ffc8ae4edbb767e78b30c Mon Sep 17 00:00:00 2001 From: Daniel Date: Thu, 30 Nov 2023 18:48:22 +0100 Subject: [PATCH 25/73] fix references --- src/tests_authentik/fixtures_authentik.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/tests_authentik/fixtures_authentik.py b/src/tests_authentik/fixtures_authentik.py index 94af38f..d4c21c8 100644 --- a/src/tests_authentik/fixtures_authentik.py +++ b/src/tests_authentik/fixtures_authentik.py @@ -16,8 +16,8 @@ def authentik_admin_context(context: BrowserContext, DIR: DirManager) -> Browser @pytest.fixture -def authentik_admin_page(admin_context: BrowserContext, DIR: DirManager) -> Page: - page = admin_context.new_page() +def authentik_admin_page(authentik_admin_context: BrowserContext, DIR: DirManager) -> Page: + page = authentik_admin_context.new_page() env_file = DIR.ENV_FILES / "authentik" config: dict[str, str] = dotenv_values(env_file) # type: ignore url = "https://" + config["DOMAIN"] @@ -34,8 +34,8 @@ def authentik_user_context(context: BrowserContext, DIR: DirManager) -> BrowserC @pytest.fixture -def authentik_user_page(user_context: BrowserContext, DIR: DirManager) -> Page: - page = user_context.new_page() +def authentik_user_page(authentik_user_context: BrowserContext, DIR: DirManager) -> Page: + page = authentik_user_context.new_page() env_file = DIR.ENV_FILES / "authentik" config: dict[str, str] = dotenv_values(env_file) # type: ignore url = "https://" + config["DOMAIN"] -- 2.47.2 From aa442a7f0cea865da25dafd525b922c1dec58978 Mon Sep 17 00:00:00 2001 From: Daniel Date: Thu, 30 Nov 2023 18:50:26 +0100 Subject: [PATCH 26/73] remove unused plugin --- src/tests_authentik/plugin_authentik.py | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 src/tests_authentik/plugin_authentik.py diff --git a/src/tests_authentik/plugin_authentik.py b/src/tests_authentik/plugin_authentik.py deleted file mode 100644 index 14f31c9..0000000 --- a/src/tests_authentik/plugin_authentik.py +++ /dev/null @@ -1,3 +0,0 @@ -# will be loaded in conftest.py -# will provide context for other tests (wordpress etc.) -# that depend on authentik (which is all of them) -- 2.47.2 From 0d76754ece48f5bc58bd13028dac45408f5831cd Mon Sep 17 00:00:00 2001 From: Daniel Date: Thu, 30 Nov 2023 18:58:56 +0100 Subject: [PATCH 27/73] change global timeout --- src/conftest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/conftest.py b/src/conftest.py index 770e666..5cedd24 100644 --- a/src/conftest.py +++ b/src/conftest.py @@ -16,7 +16,7 @@ from src.dirmanager import DirManager # global timeout and LOCALE LOCALE = {"Accept-Language": "de_DE"} -TIMEOUT = 10_000 +TIMEOUT = 7_000 expect.set_options(timeout=TIMEOUT) -- 2.47.2 From 4af494e047ec82d15a4344483deb84ad7c3fafe2 Mon Sep 17 00:00:00 2001 From: Daniel Date: Thu, 30 Nov 2023 19:02:36 +0100 Subject: [PATCH 28/73] custom timeout for expect in failing test --- src/tests_wordpress/setup_wordpress.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tests_wordpress/setup_wordpress.py b/src/tests_wordpress/setup_wordpress.py index b6ed2dc..ee7d9d1 100644 --- a/src/tests_wordpress/setup_wordpress.py +++ b/src/tests_wordpress/setup_wordpress.py @@ -11,10 +11,10 @@ def test_visit_from_domain(authentik_admin_context: BrowserContext, dotenv_confi page.goto(url) # look for content wrapper - expect(page.locator("#wpcontent")).to_be_visible() + expect(page.locator("#wpcontent")).to_be_visible(timeout=3_000) # look for admin bar - expect(page.locator("#wpadminbar")).to_be_visible() + expect(page.locator("#wpadminbar")).to_be_visible(timeout=3_000) def test_visit_from_authentik(authentik_admin_page: Page, DIR: DirManager): -- 2.47.2 From f5d31ebc757e4eb510929ebbf81731bc299a7c74 Mon Sep 17 00:00:00 2001 From: Daniel Date: Thu, 30 Nov 2023 19:04:37 +0100 Subject: [PATCH 29/73] remove old setup --- .../deprecated-setup_authentik-deprecated.py | 100 ------------------ 1 file changed, 100 deletions(-) delete mode 100644 src/setup-deprecated/deprecated-setup_authentik-deprecated.py diff --git a/src/setup-deprecated/deprecated-setup_authentik-deprecated.py b/src/setup-deprecated/deprecated-setup_authentik-deprecated.py deleted file mode 100644 index 4ac4fd5..0000000 --- a/src/setup-deprecated/deprecated-setup_authentik-deprecated.py +++ /dev/null @@ -1,100 +0,0 @@ -import json -from pathlib import Path - -import pytest -from icecream import ic -from playwright.sync_api import Browser, Locator, expect - -cred_file = Path("credentials.json") -with open(cred_file, "r") as f: - CREDENTIALS = json.load(f) - -print(CREDENTIALS) - -TESTUSER = {"username": "testuser", "name": "Test User", "password": "test123", "email": "test@example.com"} -TIMEOUT = 5000 - - -def check_for(locator: Locator): - expect(locator).to_be_visible(timeout=TIMEOUT) - - -# todo: why is this a fixture? to get dotenv_config -@pytest.fixture(scope="session", autouse=True) -def create_admin_login(browser: Browser, dotenv_config, STATES): - # ic(dotenv_config) - - # go to page - context = browser.new_context() - context.set_default_timeout(TIMEOUT) - page = context.new_page() - url = "https://" + dotenv_config["DOMAIN"] - page.goto(url) - - # check welcome message - welcome_message = dotenv_config.get("welcome_message") - if welcome_message: - check_for(page.get_by_text(welcome_message)) - - # login - page.locator('input[name="uidField"]').fill(CREDENTIALS["admin"]) - page.locator('ak-stage-identification input[name="password"]').fill(CREDENTIALS["admin_pw"]) - page.get_by_role("button", name="Log In").click() - check_for(page.locator("ak-library")) - - # save state - context.storage_state(path=f"{STATES}/admin_state.json") - page.close() - context.close() - - -def create_invite_link(page): - url = "https://" + dotenv_config["DOMAIN"] - page.goto(url) - page.get_by_role("link", name="Admin Interface").click() - page.get_by_text("Verzeichnis").click() - page.get_by_text("Benutzer").nth(2).click() - page.get_by_text("Einladungen").click() - page.get_by_role("button", name="Erstellen").first.click() - page.locator('input[name="name"]').click() - linkname = "testlink9433" - page.locator('input[name="name"]').fill(linkname) - page.get_by_placeholder("Wählen Sie ein Objekt aus.").click() - page.get_by_role("option", name="invitation-enrollment-flow invitation-enrollment-flow").click() - page.get_by_text("Erstellen", exact=True).first.click() - linklocator = page.get_by_role("rowgroup").filter(has=page.get_by_text(linkname)) - linklocator.locator(".fa-angle-down").click() - invitelink = linklocator.get_by_role("textbox").get_attribute(name="value") - return invitelink - - -def create_user(context, invitelink): - page = context.new_page() - page.goto(invitelink) - page.get_by_placeholder("Benutzername").click() - page.get_by_placeholder("Benutzername").fill(testuser["username"]) - page.locator('input[name="name"]').click() - page.locator('input[name="name"]').fill(testuser["name"]) - page.locator('input[name="email"]').click() - page.locator('input[name="email"]').fill(testuser["email"]) - page.get_by_placeholder("Passwort", exact=True).click() - page.get_by_placeholder("Passwort", exact=True).fill(testuser["password"]) - page.get_by_placeholder("Passwort (wiederholen)").click() - page.get_by_placeholder("Passwort (wiederholen)").fill(testuser["password"]) - page.get_by_role("button", name="Weiter").click() - check_for(page.locator("ak-library")) - context.storage_state(path=f"{STATES}/user_state.json") - - -@pytest.fixture(scope="session", autouse=True) -def create_user_session(browser: Browser, admin_login): - admin_context = browser.new_context(storage_state=f"{STATES}/admin_state.json") - # admin_context = setup_context(browser, f"{STATES}/admin_state.json") - admin_page = admin_context.new_page() - invitelink = create_invite_link(admin_page) - admin_context.tracing.stop(path=f"{RECORDS}/create_invite_link.zip") - admin_context.close() - user_context = setup_context(browser) - create_user(user_context, invitelink) - user_context.tracing.stop(path=f"{RECORDS}/create_user.zip") - user_context.close() -- 2.47.2 From a8b4302805a0ae32761f5e389cca635d08c9fdba Mon Sep 17 00:00:00 2001 From: Daniel Date: Thu, 30 Nov 2023 19:16:03 +0100 Subject: [PATCH 30/73] add prototype --- prototyping/test_urllib.py | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 prototyping/test_urllib.py diff --git a/prototyping/test_urllib.py b/prototyping/test_urllib.py new file mode 100644 index 0000000..074b0a5 --- /dev/null +++ b/prototyping/test_urllib.py @@ -0,0 +1,10 @@ +# %% +from urllib.parse import parse_qs, urlencode, urlparse, urlunparse + +string = "blog.dev.local-it.cloud" + + +parsed_url = urlparse(string, scheme="https") +print(parsed_url) + +print(urlunparse(parsed_url)) -- 2.47.2 From cd824c2bf274931a3b46b1f49b480130a3a99efa Mon Sep 17 00:00:00 2001 From: Daniel Date: Thu, 30 Nov 2023 19:21:46 +0100 Subject: [PATCH 31/73] small dir improvements --- src/dirmanager.py | 4 ++-- src/runner.py | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/dirmanager.py b/src/dirmanager.py index c210d25..b5228bf 100644 --- a/src/dirmanager.py +++ b/src/dirmanager.py @@ -30,12 +30,12 @@ class DirManager: ) @property - def OUTPUT(self): + def OUTPUT_DIR(self): return self._output_dir @property def SESSION(self): - return self._output_dir / f"test-{self.session_id}" + return self.OUTPUT_DIR / f"test-{self.session_id}" @property def RECORDS(self): diff --git a/src/runner.py b/src/runner.py index 1e2cc6f..f069eda 100644 --- a/src/runner.py +++ b/src/runner.py @@ -117,13 +117,14 @@ class Runner: command_arguments.append("--env_file") command_arguments.append(str(self.dotenv_path)) + # set root dir for tests output (used in DirManager). this is our custom argument command_arguments.append("--output_dir") - command_arguments.append(str(self.DIRS.OUTPUT)) + command_arguments.append(str(self.DIRS.OUTPUT_DIR)) command_arguments.append("--session_id") command_arguments.append(self.session_id) - # artifacts dir + # artifacts dir from pytest # warning: https://github.com/microsoft/playwright-pytest/issues/111 # --output only works with the given context and page fixture # folder needs to be unique! traces will not appear, if every pytest run has same output dir -- 2.47.2 From ec748398fbf9dc45bd0815fdaa54378dd9f6a44b Mon Sep 17 00:00:00 2001 From: Daniel Date: Thu, 30 Nov 2023 19:30:24 +0100 Subject: [PATCH 32/73] also discover functions as tests that have setup_ naming --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index d6f9e14..057345b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,4 +19,4 @@ line-length = 120 target-version = "py311" [tool.pytest.ini_options] -python_files = "test_*.py setup*.py" \ No newline at end of file +python_functions = "test_* setup_*" \ No newline at end of file -- 2.47.2 From c9cb39d375ae8d2c22657e66129c1ca2ecacad31 Mon Sep 17 00:00:00 2001 From: Daniel Date: Thu, 30 Nov 2023 19:30:59 +0100 Subject: [PATCH 33/73] cleanup --- src/tests_wordpress/setup_wordpress.py | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/src/tests_wordpress/setup_wordpress.py b/src/tests_wordpress/setup_wordpress.py index ee7d9d1..6543c9d 100644 --- a/src/tests_wordpress/setup_wordpress.py +++ b/src/tests_wordpress/setup_wordpress.py @@ -6,32 +6,25 @@ from src.dirmanager import DirManager @pytest.mark.xfail(reason="wordpress sso login has not been generated") def test_visit_from_domain(authentik_admin_context: BrowserContext, dotenv_config: dict[str, str]): + """visit wordpress directly with admin_session, expect not to be logged in""" page = authentik_admin_context.new_page() url = "https://" + dotenv_config["DOMAIN"] page.goto(url) - # look for content wrapper expect(page.locator("#wpcontent")).to_be_visible(timeout=3_000) - # look for admin bar expect(page.locator("#wpadminbar")).to_be_visible(timeout=3_000) -def test_visit_from_authentik(authentik_admin_page: Page, DIR: DirManager): +def setup_wordpress_admin_session(authentik_admin_page: Page, DIR: DirManager): + """visit wordpress from authentik with admin_session to create wordpress_admin_session""" with authentik_admin_page.expect_popup() as event_context: authentik_admin_page.get_by_role("link", name="Wordpress").click() page_wordpress = event_context.value - # look for content wrapper expect(page_wordpress.locator("#wpcontent")).to_be_visible() - # look for admin bar expect(page_wordpress.locator("#wpadminbar")).to_be_visible() - + # save session context = page_wordpress.context - context.storage_state(path=f"{DIR.STATES}/wordpress_admin_state.json") - - -def setup_something(): - assert False -- 2.47.2 From 99e7800792afd56961354bba1411e546e10194b0 Mon Sep 17 00:00:00 2001 From: Daniel Date: Thu, 30 Nov 2023 19:31:46 +0100 Subject: [PATCH 34/73] rename test functions to setup_ --- src/tests_authentik/setup_authentik.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tests_authentik/setup_authentik.py b/src/tests_authentik/setup_authentik.py index 627dbbe..7927895 100644 --- a/src/tests_authentik/setup_authentik.py +++ b/src/tests_authentik/setup_authentik.py @@ -13,7 +13,7 @@ ADMIN_PASS = os.environ["ADMIN_PASS"] TESTUSER = {"username": "testuser", "name": "Test User", "password": "test123", "email": "test@example.com"} -def test_create_admin_login(context: BrowserContext, dotenv_config: dict[str, str], DIR: DirManager): +def setup_admin_state(context: BrowserContext, dotenv_config: dict[str, str], DIR: DirManager): # go to page page = context.new_page() url = "https://" + dotenv_config["DOMAIN"] @@ -99,7 +99,7 @@ def create_user(user_context: BrowserContext, invitelink): expect(page.locator("ak-library")).to_be_visible() -def test_create_user_session(context: BrowserContext, dotenv_config: dict[str, str], DIR: DirManager): +def setup_user_state(context: BrowserContext, dotenv_config: dict[str, str], DIR: DirManager): # load admin cookies state_file = DIR.STATES / "authentik_admin_state.json" storage_state = json.loads(state_file.read_bytes()) -- 2.47.2 From 010b1187c00de89657063452e4789425b1c5ddba Mon Sep 17 00:00:00 2001 From: Daniel Date: Thu, 30 Nov 2023 23:08:00 +0100 Subject: [PATCH 35/73] add codegen --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index 9d61b40..a046244 100644 --- a/README.md +++ b/README.md @@ -45,3 +45,11 @@ Force rebuild wtihtout cache ```bash docker-compose build --no-cache ``` + +# Codegen + +Use playwright codegen to create code for new testes easily https://playwright.dev/python/docs/codegen + +```bash +playwright codegen demo.playwright.dev/todomvc +``` \ No newline at end of file -- 2.47.2 From b62adc0c2a7fbbb77120cc08f2e43cb2e63c7b55 Mon Sep 17 00:00:00 2001 From: Daniel Date: Fri, 1 Dec 2023 15:09:45 +0100 Subject: [PATCH 36/73] add comment --- main.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/main.py b/main.py index f71cc25..e0451d9 100644 --- a/main.py +++ b/main.py @@ -17,6 +17,8 @@ from src.utils import get_session_id # requires that authentik ran first to create the admin session and the user # session. At the moment, wrong ordering results in unsuccessful test # (wrong ordering would be wordpress env file is before authentik env file). +# At the moment, functionailty is only guaranteed if each env file use +# a unique TYPE var. ENV_FILES = [ Path("envfiles/login.test.dev.local-it.cloud.env"), # authentik -- 2.47.2 From d6941824cd39943150f1db747b473d3c8bdd5806 Mon Sep 17 00:00:00 2001 From: Daniel Date: Fri, 1 Dec 2023 15:13:06 +0100 Subject: [PATCH 37/73] put logging file into /records --- main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.py b/main.py index e0451d9..447c168 100644 --- a/main.py +++ b/main.py @@ -58,7 +58,7 @@ session_id = get_session_id() # ------------------------------- setup logging ------------------------------ # DIR = DirManager(output_dir=OUTPUT_DIR, session_id=session_id) -log_file = DIR.RESULTS / "full.log" +log_file = DIR.RECORDS / "full.log" logger.add(log_file) -- 2.47.2 From 3a69366cc79c60eba462e590b344dde71d60b5e5 Mon Sep 17 00:00:00 2001 From: Daniel Date: Fri, 1 Dec 2023 15:13:37 +0100 Subject: [PATCH 38/73] rename log file --- main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.py b/main.py index 447c168..8cad255 100644 --- a/main.py +++ b/main.py @@ -58,7 +58,7 @@ session_id = get_session_id() # ------------------------------- setup logging ------------------------------ # DIR = DirManager(output_dir=OUTPUT_DIR, session_id=session_id) -log_file = DIR.RECORDS / "full.log" +log_file = DIR.RECORDS / "coordinator.log" logger.add(log_file) -- 2.47.2 From 23184d82bcb75e230763255b36b8253d5cc23cc8 Mon Sep 17 00:00:00 2001 From: Daniel Date: Fri, 1 Dec 2023 17:25:21 +0100 Subject: [PATCH 39/73] enable -v --- src/runner.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/runner.py b/src/runner.py index f069eda..4b97275 100644 --- a/src/runner.py +++ b/src/runner.py @@ -110,7 +110,7 @@ class Runner: command_arguments = [] - # command_arguments.append("-v") + command_arguments.append("-v") # command_arguments.append("-rx") command_arguments.append(str(full_test_path)) -- 2.47.2 From dad9d297ab975ba5d37f4ed9820437ee43a1540f Mon Sep 17 00:00:00 2001 From: Daniel Date: Fri, 1 Dec 2023 20:24:38 +0100 Subject: [PATCH 40/73] add sorting algorithm --- prototyping/sorting_algo.py | 69 +++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 prototyping/sorting_algo.py diff --git a/prototyping/sorting_algo.py b/prototyping/sorting_algo.py new file mode 100644 index 0000000..d9f28d8 --- /dev/null +++ b/prototyping/sorting_algo.py @@ -0,0 +1,69 @@ +# %% + +from typing import NamedTuple + + +class Rule(NamedTuple): + child: str + parent: str + + +def sort_by_rules(in_list: list, rules: list[Rule]) -> bool: + def is_rule_satisfied(in_list: list, rule: Rule) -> bool: + child_val, parent_val = rule + child_index = in_list.index(child_val) + parent_index = in_list.index(parent_val) + return parent_index < child_index + + def move_item_down(in_list: list, index: int): + """moves item""" + # assert index > + in_list[index], in_list[index - 1] = in_list[index - 1], in_list[index] + + for _ in range(10000): + rule_satisfied: list[bool] = [] + for rule in rules: + if not is_rule_satisfied(in_list, rule): + rule_satisfied.append(False) + parent_index = in_list.index(rule.parent) + move_item_down(in_list, parent_index) + else: + rule_satisfied.append(True) + if all(rule_satisfied): + print("success") + return True + print("failed") + return False + + +in_list = ["a", "b", "c", "d", "e", "f", "g"] +rules = [ # X depends on Y + Rule("a", "e"), + Rule("b", "e"), + Rule("b", "f"), + Rule("c", "e"), + Rule("d", "e"), + Rule("f", "e"), +] + +print("before", in_list) +sort_by_rules(in_list, rules) +print("before", in_list) + + +def test_uniqueness(in_list: list): + assert len(set(in_list)) == len(in_list) + + +def test_rules(in_list, rules): + rule_satisfied: list[bool] = [] + for rule in rules: + if is_rule_satisfied(in_list, rule): + rule_satisfied.append(True) + else: + rule_satisfied.append(False) + assert all(rule_satisfied) + + +test_uniqueness(in_list) +test_rules(in_list, rules) -- 2.47.2 From 0fe5fa5536c3467440237798d0aaa22c17bc1673 Mon Sep 17 00:00:00 2001 From: Daniel Date: Fri, 1 Dec 2023 20:32:00 +0100 Subject: [PATCH 41/73] move function --- prototyping/sorting_algo.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/prototyping/sorting_algo.py b/prototyping/sorting_algo.py index d9f28d8..00a454e 100644 --- a/prototyping/sorting_algo.py +++ b/prototyping/sorting_algo.py @@ -8,13 +8,14 @@ class Rule(NamedTuple): parent: str -def sort_by_rules(in_list: list, rules: list[Rule]) -> bool: - def is_rule_satisfied(in_list: list, rule: Rule) -> bool: - child_val, parent_val = rule - child_index = in_list.index(child_val) - parent_index = in_list.index(parent_val) - return parent_index < child_index +def is_rule_satisfied(in_list: list, rule: Rule) -> bool: + child_val, parent_val = rule + child_index = in_list.index(child_val) + parent_index = in_list.index(parent_val) + return parent_index < child_index + +def sort_by_rules(in_list: list, rules: list[Rule]) -> bool: def move_item_down(in_list: list, index: int): """moves item""" # assert index > -- 2.47.2 From 361c92a3df49aee94e5ef4c9886b04802303d0ba Mon Sep 17 00:00:00 2001 From: Daniel Date: Sat, 2 Dec 2023 13:43:54 +0100 Subject: [PATCH 42/73] remove prints --- src/html_helper.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/html_helper.py b/src/html_helper.py index 8f94b67..6e81c7e 100644 --- a/src/html_helper.py +++ b/src/html_helper.py @@ -174,10 +174,8 @@ def get_html_files(path, output_file_path): output_file_path = os.path.abspath(output_file_path) for p in pathlib.Path(path).rglob("*.html"): - print(p) res = str(p.absolute()) if output_file_path in res: - print("damn") continue tmp = BeautifulSoup("".join(open(res)), features="html.parser") -- 2.47.2 From 2e7e2c0fb22e0f4ea649fcc326d83ce826ed432c Mon Sep 17 00:00:00 2001 From: Daniel Date: Sat, 2 Dec 2023 13:44:04 +0100 Subject: [PATCH 43/73] make it a package --- prototyping/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 prototyping/__init__.py diff --git a/prototyping/__init__.py b/prototyping/__init__.py new file mode 100644 index 0000000..e69de29 -- 2.47.2 From 37062ad5026c3574e1ee4ce08ca564436058b17b Mon Sep 17 00:00:00 2001 From: Daniel Date: Sat, 2 Dec 2023 13:44:43 +0100 Subject: [PATCH 44/73] add pytest arguments to make pytest usable for testing the actual code base --- pyproject.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 057345b..6912f0b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,4 +19,5 @@ line-length = 120 target-version = "py311" [tool.pytest.ini_options] -python_functions = "test_* setup_*" \ No newline at end of file +python_functions = "test_* setup_*" +norecursedirs = "previous-work src" \ No newline at end of file -- 2.47.2 From 1eabe31e9e13b554032f7ca8a427849449953f9b Mon Sep 17 00:00:00 2001 From: Daniel Date: Sat, 2 Dec 2023 13:45:04 +0100 Subject: [PATCH 45/73] other way of importing the fixtures, not sure if better --- src/tests_wordpress/conftest.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/tests_wordpress/conftest.py b/src/tests_wordpress/conftest.py index e57e575..f2d4e2c 100644 --- a/src/tests_wordpress/conftest.py +++ b/src/tests_wordpress/conftest.py @@ -5,12 +5,15 @@ from dotenv import dotenv_values from playwright.sync_api import BrowserContext, Page from src.dirmanager import DirManager -from src.tests_authentik.fixtures_authentik import ( - authentik_admin_context, - authentik_admin_page, - authentik_user_context, - authentik_user_page, -) + +# from src.tests_authentik.fixtures_authentik import ( +# authentik_admin_context, +# authentik_admin_page, +# authentik_user_context, +# authentik_user_page, +# ) + +pytest_plugins = "src.tests_authentik.fixtures_authentik" @pytest.fixture -- 2.47.2 From 5541e7a88a797a28d12da9472b969e1d4d6dd60e Mon Sep 17 00:00:00 2001 From: Daniel Date: Sat, 2 Dec 2023 13:45:25 +0100 Subject: [PATCH 46/73] add test script for dependency resolving algorithm --- tests/test_utils.py | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 tests/test_utils.py diff --git a/tests/test_utils.py b/tests/test_utils.py new file mode 100644 index 0000000..e462fc6 --- /dev/null +++ b/tests/test_utils.py @@ -0,0 +1,40 @@ +import pytest + +from prototyping.sorting_algo import Rule, is_rule_satisfied, sort_by_rules + + +@pytest.fixture +def in_list(): + return ["a", "b", "c", "d", "e", "f", "g"] + + +@pytest.fixture +def rules() -> list[Rule]: + return [ # X depends on Y + Rule("a", "e"), + Rule("b", "e"), + Rule("b", "f"), + Rule("c", "e"), + Rule("d", "e"), + Rule("f", "e"), + ] + + +def has_unique_elements(in_list): + return len(set(in_list)) == len(in_list) + + +def has_rules_satisfied(in_list, rules): + rule_satisfied: list[bool] = [] + for rule in rules: + if is_rule_satisfied(in_list, rule): + rule_satisfied.append(True) + else: + rule_satisfied.append(False) + return all(rule_satisfied) + + +def test_stuff(in_list, rules): + sort_by_rules(in_list, rules) + assert has_unique_elements(in_list) + assert has_rules_satisfied(in_list, rules) -- 2.47.2 From 06ff6449c3c14578ed573c953cb24d2e960c2dd9 Mon Sep 17 00:00:00 2001 From: Daniel Date: Sat, 2 Dec 2023 14:31:18 +0100 Subject: [PATCH 47/73] shorter code --- prototyping/sorting_algo.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/prototyping/sorting_algo.py b/prototyping/sorting_algo.py index 00a454e..7deee60 100644 --- a/prototyping/sorting_algo.py +++ b/prototyping/sorting_algo.py @@ -9,9 +9,8 @@ class Rule(NamedTuple): def is_rule_satisfied(in_list: list, rule: Rule) -> bool: - child_val, parent_val = rule - child_index = in_list.index(child_val) - parent_index = in_list.index(parent_val) + child_index = in_list.index(rule.child) + parent_index = in_list.index(rule.parent) return parent_index < child_index -- 2.47.2 From a039be4581784a607aa65f58f69bc0fc9ddfdfd0 Mon Sep 17 00:00:00 2001 From: Daniel Date: Sat, 2 Dec 2023 14:34:39 +0100 Subject: [PATCH 48/73] move sorting stuff --- .../sorting_algo.py => src/env_file_helper.py | 35 ------------------- 1 file changed, 35 deletions(-) rename prototyping/sorting_algo.py => src/env_file_helper.py (59%) diff --git a/prototyping/sorting_algo.py b/src/env_file_helper.py similarity index 59% rename from prototyping/sorting_algo.py rename to src/env_file_helper.py index 7deee60..2f52f28 100644 --- a/prototyping/sorting_algo.py +++ b/src/env_file_helper.py @@ -1,5 +1,3 @@ -# %% - from typing import NamedTuple @@ -34,36 +32,3 @@ def sort_by_rules(in_list: list, rules: list[Rule]) -> bool: return True print("failed") return False - - -in_list = ["a", "b", "c", "d", "e", "f", "g"] -rules = [ # X depends on Y - Rule("a", "e"), - Rule("b", "e"), - Rule("b", "f"), - Rule("c", "e"), - Rule("d", "e"), - Rule("f", "e"), -] - -print("before", in_list) -sort_by_rules(in_list, rules) -print("before", in_list) - - -def test_uniqueness(in_list: list): - assert len(set(in_list)) == len(in_list) - - -def test_rules(in_list, rules): - rule_satisfied: list[bool] = [] - for rule in rules: - if is_rule_satisfied(in_list, rule): - rule_satisfied.append(True) - else: - rule_satisfied.append(False) - assert all(rule_satisfied) - - -test_uniqueness(in_list) -test_rules(in_list, rules) -- 2.47.2 From 51db74f0b513ed757ac2eb0ce499617b8c935a1f Mon Sep 17 00:00:00 2001 From: Daniel Date: Sat, 2 Dec 2023 14:41:37 +0100 Subject: [PATCH 49/73] cleanup --- src/env_file_helper.py | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/src/env_file_helper.py b/src/env_file_helper.py index 2f52f28..20ca508 100644 --- a/src/env_file_helper.py +++ b/src/env_file_helper.py @@ -1,34 +1,37 @@ from typing import NamedTuple +from loguru import logger + class Rule(NamedTuple): child: str parent: str -def is_rule_satisfied(in_list: list, rule: Rule) -> bool: +def _is_rule_satisfied(in_list: list, rule: Rule) -> bool: child_index = in_list.index(rule.child) parent_index = in_list.index(rule.parent) return parent_index < child_index -def sort_by_rules(in_list: list, rules: list[Rule]) -> bool: - def move_item_down(in_list: list, index: int): - """moves item""" - # assert index > +def sort_env_files_by_rule(env_list: list, rules: list[Rule]) -> list: + in_list = env_list.copy() + + def swap_item_with_previous(in_list: list, index: int): + """swaps item at index N with item at index N-1""" + assert index > 0, "cannot swap with negative index" in_list[index], in_list[index - 1] = in_list[index - 1], in_list[index] - for _ in range(10000): + for _ in range(10_000): rule_satisfied: list[bool] = [] for rule in rules: - if not is_rule_satisfied(in_list, rule): + if _is_rule_satisfied(in_list, rule): + rule_satisfied.append(True) + else: rule_satisfied.append(False) parent_index = in_list.index(rule.parent) - move_item_down(in_list, parent_index) - else: - rule_satisfied.append(True) + swap_item_with_previous(in_list, parent_index) if all(rule_satisfied): - print("success") - return True - print("failed") - return False + return in_list + logger.error("could not find order that satisfys all rules") + raise ValueError -- 2.47.2 From df75e0afd36a6f95cf6e50ccc8052e037f001b00 Mon Sep 17 00:00:00 2001 From: Daniel Date: Sat, 2 Dec 2023 15:21:03 +0100 Subject: [PATCH 50/73] rework env file parsing logic with EnvFile class, preparing for dependency resolution --- src/coordinator.py | 28 +++++++++++++++------------- src/env_file_helper.py | 13 ++++++++++--- 2 files changed, 25 insertions(+), 16 deletions(-) diff --git a/src/coordinator.py b/src/coordinator.py index 885bb9d..b0721c9 100644 --- a/src/coordinator.py +++ b/src/coordinator.py @@ -5,6 +5,7 @@ from dotenv import dotenv_values from loguru import logger from src.dirmanager import DirManager +from src.env_file_helper import DependencyRule, EnvFile, sort_env_files_by_rule from src.html_helper import merge_html_files from src.runner import Runner from src.tests_authentik.runner_authentik import RunnerAuthentik @@ -31,18 +32,18 @@ class Coordinator: self.output_dir = output_dir self.session_id = session_id - self.env_paths: dict[str, Path] = dict() - self.env_configs: dict[str, dict[str, str]] = dict() # todo: needed? - self._parse_env_files(env_paths_list) + self.env_files: list[EnvFile] = self._parse_env_files(env_paths_list) - def _parse_env_files(self, env_paths: list[Path]): + def _parse_env_files(self, env_paths: list[Path]) -> list[EnvFile]: + """Returns a list of EnvFile objects created from the given env files""" + env_files: list[EnvFile] = [] for env_path in env_paths: assert env_path.is_file(), f"the env file {env_path} does not exist" config: dict[str, str] = dotenv_values(env_path) # type: ignore assert "TYPE" in config, f"the env file {env_path} does not specify the required TYPE key." env_type = config["TYPE"] - self.env_paths[env_type] = env_path - self.env_configs[env_type] = config # todo: needed? + env_files.append(EnvFile(env_path=env_path, config=config, env_type=env_type)) + return env_files def setup_test(self): logger.info("calling setup_test()") @@ -53,24 +54,25 @@ class Coordinator: """Copies all env filesto STATES/env_files. Files will be renamed to their own TYPE value.""" env_files_dir = self.DIR.STATES / "env_files" env_files_dir.mkdir(exist_ok=True) - for type_key, env_path in self.env_paths.items(): - shutil.copy(env_path, env_files_dir / type_key) + for env_file in self.env_files: + shutil.copy(env_file.env_path, env_files_dir / env_file.env_type) def run_test(self): logger.info("calling run_test()") - self.runners: list[Runner] = self._load_runners(self.env_paths.values()) + self.runners: list[Runner] = self._load_runners(self.env_files) for runner in self.runners: runner.run_tests() for runner in self.runners: runner.run_cleanup() logger.info("run_test() finished") - def _load_runners(self, env_files: list[Path]) -> list[Runner]: + def _load_runners(self, env_files: list[EnvFile]) -> list[Runner]: runners = [] for env_file in env_files: - config: dict[str, str] = dotenv_values(env_file) # type: ignore - RunnerClass = RUNNER_DICT[config["TYPE"]] - runners.append(RunnerClass(dotenv_path=env_file, output_dir=self.output_dir, session_id=self.session_id)) + RunnerClass = RUNNER_DICT[env_file.config["TYPE"]] + runners.append( + RunnerClass(dotenv_path=env_file.env_path, output_dir=self.output_dir, session_id=self.session_id) + ) return runners def combine_html(self): diff --git a/src/env_file_helper.py b/src/env_file_helper.py index 20ca508..361dca0 100644 --- a/src/env_file_helper.py +++ b/src/env_file_helper.py @@ -1,20 +1,27 @@ +from pathlib import Path from typing import NamedTuple from loguru import logger -class Rule(NamedTuple): +class EnvFile(NamedTuple): + env_path: Path + config: dict[str, str] + env_type: str + + +class DependencyRule(NamedTuple): child: str parent: str -def _is_rule_satisfied(in_list: list, rule: Rule) -> bool: +def _is_rule_satisfied(in_list: list, rule: DependencyRule) -> bool: child_index = in_list.index(rule.child) parent_index = in_list.index(rule.parent) return parent_index < child_index -def sort_env_files_by_rule(env_list: list, rules: list[Rule]) -> list: +def sort_env_files_by_rule(env_list: list, rules: list[DependencyRule]) -> list: in_list = env_list.copy() def swap_item_with_previous(in_list: list, index: int): -- 2.47.2 From 026a2936580bb2609102031b75e600c1d84624e7 Mon Sep 17 00:00:00 2001 From: Daniel Date: Sat, 2 Dec 2023 15:24:58 +0100 Subject: [PATCH 51/73] wip _get_dependency_rules --- src/coordinator.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/coordinator.py b/src/coordinator.py index b0721c9..4e20a69 100644 --- a/src/coordinator.py +++ b/src/coordinator.py @@ -33,8 +33,10 @@ class Coordinator: self.session_id = session_id self.env_files: list[EnvFile] = self._parse_env_files(env_paths_list) + self._get_dependency_rules(self.env_files) - def _parse_env_files(self, env_paths: list[Path]) -> list[EnvFile]: + @staticmethod + def _parse_env_files(env_paths: list[Path]) -> list[EnvFile]: """Returns a list of EnvFile objects created from the given env files""" env_files: list[EnvFile] = [] for env_path in env_paths: @@ -45,6 +47,14 @@ class Coordinator: env_files.append(EnvFile(env_path=env_path, config=config, env_type=env_type)) return env_files + def _get_dependency_rules(self, env_files: list[EnvFile]): + for env_file in env_files: + child_runner_class = RUNNER_DICT[env_file.env_type] + print(child_runner_class.name) + for dependency in child_runner_class.dependencies: + print("dependency", dependency.name) + exit() + def setup_test(self): logger.info("calling setup_test()") self.DIR.create_all_dirs() -- 2.47.2 From fba86f647f554d6183132a97897b7a0d4662cd9b Mon Sep 17 00:00:00 2001 From: Daniel Date: Sat, 2 Dec 2023 15:25:22 +0100 Subject: [PATCH 52/73] typo --- src/coordinator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coordinator.py b/src/coordinator.py index 4e20a69..c1bb8cb 100644 --- a/src/coordinator.py +++ b/src/coordinator.py @@ -61,7 +61,7 @@ class Coordinator: self._copy_env_files() def _copy_env_files(self): - """Copies all env filesto STATES/env_files. Files will be renamed to their own TYPE value.""" + """Copies all env files to STATES/env_files. Files will be renamed to their own TYPE value.""" env_files_dir = self.DIR.STATES / "env_files" env_files_dir.mkdir(exist_ok=True) for env_file in self.env_files: -- 2.47.2 From a86a830c081b1d3cb09e2b75cdb0dde7b1e57cbc Mon Sep 17 00:00:00 2001 From: Daniel Date: Sat, 2 Dec 2023 15:35:03 +0100 Subject: [PATCH 53/73] move setup into its own runner function --- src/coordinator.py | 2 ++ src/runner.py | 15 +++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/src/coordinator.py b/src/coordinator.py index c1bb8cb..24373f3 100644 --- a/src/coordinator.py +++ b/src/coordinator.py @@ -70,6 +70,8 @@ class Coordinator: def run_test(self): logger.info("calling run_test()") self.runners: list[Runner] = self._load_runners(self.env_files) + for runner in self.runners: + runner.run_setup() for runner in self.runners: runner.run_tests() for runner in self.runners: diff --git a/src/runner.py b/src/runner.py index 4b97275..ce8db3b 100644 --- a/src/runner.py +++ b/src/runner.py @@ -34,7 +34,21 @@ class Runner: assert self.test_dir_name self.root_dir = Path(__file__).parent + def run_setup(self): + """runs the setup script if available""" + if not self._dependencies_passed(): + logger.warning(f"skipping run_tests() of {self.name}, because some dependencies have not passed") + return + + # run main setup if available + if isinstance(self.main_setup_name, str): + self._run_or_skip_test( + identifier_string=self.combine_names(self.name, self.main_setup_name), + test_path=self.root_dir / self.test_dir_name / self.main_setup_name, + ) + def run_tests(self): + """runs the main test script and if available and sub test scripts if their running condition is met""" # check if required dependencies have passed if not self._dependencies_passed(): logger.warning(f"skipping run_tests() of {self.name}, because some dependencies have not passed") @@ -66,6 +80,7 @@ class Runner: self._create_result_file(result=-1, identifier_string=identifier_string) def run_cleanup(self): + """runs the cleanup script if available""" if not self._dependencies_passed(): logger.warning("skipping run_cleanup() of {self.name}, because some dependencies have not passed") return -- 2.47.2 From 65f12389163d54c50d4c75971d70b39aa9909537 Mon Sep 17 00:00:00 2001 From: Daniel Date: Sat, 2 Dec 2023 15:39:36 +0100 Subject: [PATCH 54/73] typing and docs --- src/coordinator.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/coordinator.py b/src/coordinator.py index 24373f3..cb011f1 100644 --- a/src/coordinator.py +++ b/src/coordinator.py @@ -22,7 +22,7 @@ RUNNER_DICT: dict[str, type[Runner]] = { class Coordinator: - def __init__(self, env_paths_list: list[Path], output_dir: Path, session_id: str): + def __init__(self, env_paths_list: list[Path], output_dir: Path, session_id: str) -> None: out_string = "".join([e.name + "\n" for e in env_paths_list]) out_string += f"output_dir = {output_dir}\n" out_string += f"session_id = {session_id}" @@ -55,19 +55,19 @@ class Coordinator: print("dependency", dependency.name) exit() - def setup_test(self): + def setup_test(self) -> None: logger.info("calling setup_test()") self.DIR.create_all_dirs() self._copy_env_files() - def _copy_env_files(self): + def _copy_env_files(self) -> None: """Copies all env files to STATES/env_files. Files will be renamed to their own TYPE value.""" env_files_dir = self.DIR.STATES / "env_files" env_files_dir.mkdir(exist_ok=True) for env_file in self.env_files: shutil.copy(env_file.env_path, env_files_dir / env_file.env_type) - def run_test(self): + def run_test(self) -> None: logger.info("calling run_test()") self.runners: list[Runner] = self._load_runners(self.env_files) for runner in self.runners: @@ -79,6 +79,7 @@ class Coordinator: logger.info("run_test() finished") def _load_runners(self, env_files: list[EnvFile]) -> list[Runner]: + """Creates an instance of the correct Runner class for each given env file""" runners = [] for env_file in env_files: RunnerClass = RUNNER_DICT[env_file.config["TYPE"]] @@ -87,7 +88,8 @@ class Coordinator: ) return runners - def combine_html(self): + def combine_html(self) -> None: + """combines all generated pytest html reports into one""" in_path = str(self.DIR.RECORDS / "html") out_path = str(self.DIR.RECORDS / "full-report.html") title = "combined.html" -- 2.47.2 From 978a37ca30e57b51391e451ce337b04d6f32aa53 Mon Sep 17 00:00:00 2001 From: Daniel Date: Sat, 2 Dec 2023 15:42:28 +0100 Subject: [PATCH 55/73] create dependency_rules --- src/coordinator.py | 7 ++++++- src/env_file_helper.py | 6 +++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/coordinator.py b/src/coordinator.py index cb011f1..948a03a 100644 --- a/src/coordinator.py +++ b/src/coordinator.py @@ -47,13 +47,18 @@ class Coordinator: env_files.append(EnvFile(env_path=env_path, config=config, env_type=env_type)) return env_files - def _get_dependency_rules(self, env_files: list[EnvFile]): + def _get_dependency_rules(self, env_files: list[EnvFile]) -> list[DependencyRule]: + dependency_rules: list[DependencyRule] = [] for env_file in env_files: child_runner_class = RUNNER_DICT[env_file.env_type] print(child_runner_class.name) for dependency in child_runner_class.dependencies: print("dependency", dependency.name) + dependency_rule = DependencyRule(child=child_runner_class.name, dependency=dependency.name) + dependency_rules.append(dependency_rule) + print(dependency_rules) exit() + return dependency_rules def setup_test(self) -> None: logger.info("calling setup_test()") diff --git a/src/env_file_helper.py b/src/env_file_helper.py index 361dca0..944651e 100644 --- a/src/env_file_helper.py +++ b/src/env_file_helper.py @@ -12,12 +12,12 @@ class EnvFile(NamedTuple): class DependencyRule(NamedTuple): child: str - parent: str + dependency: str def _is_rule_satisfied(in_list: list, rule: DependencyRule) -> bool: child_index = in_list.index(rule.child) - parent_index = in_list.index(rule.parent) + parent_index = in_list.index(rule.dependency) return parent_index < child_index @@ -36,7 +36,7 @@ def sort_env_files_by_rule(env_list: list, rules: list[DependencyRule]) -> list: rule_satisfied.append(True) else: rule_satisfied.append(False) - parent_index = in_list.index(rule.parent) + parent_index = in_list.index(rule.dependency) swap_item_with_previous(in_list, parent_index) if all(rule_satisfied): return in_list -- 2.47.2 From 987d1a12102fc4771b21a849177ad4625b55fda5 Mon Sep 17 00:00:00 2001 From: Daniel Date: Sat, 2 Dec 2023 16:25:22 +0100 Subject: [PATCH 56/73] add docstring --- src/runner.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/runner.py b/src/runner.py index ce8db3b..46819ed 100644 --- a/src/runner.py +++ b/src/runner.py @@ -186,6 +186,7 @@ class Runner: @staticmethod def result_int_to_str(result_int: int) -> str: + """converts the pytest exit code (int) into a meaningful string""" match result_int: case -1: return "skipped" -- 2.47.2 From 08da8787c87206055d315b9a24f39958e3f056ab Mon Sep 17 00:00:00 2001 From: Daniel Date: Sat, 2 Dec 2023 16:26:03 +0100 Subject: [PATCH 57/73] use dependency_rules in coordinator --- src/coordinator.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/coordinator.py b/src/coordinator.py index 948a03a..c72f61b 100644 --- a/src/coordinator.py +++ b/src/coordinator.py @@ -32,11 +32,12 @@ class Coordinator: self.output_dir = output_dir self.session_id = session_id - self.env_files: list[EnvFile] = self._parse_env_files(env_paths_list) - self._get_dependency_rules(self.env_files) + # parse env files + self.env_files: list[EnvFile] = self._getn_env_files_list(env_paths_list) + self.dependency_rules: list[DependencyRule] = self._get_dependency_rules(self.env_files) @staticmethod - def _parse_env_files(env_paths: list[Path]) -> list[EnvFile]: + def _getn_env_files_list(env_paths: list[Path]) -> list[EnvFile]: """Returns a list of EnvFile objects created from the given env files""" env_files: list[EnvFile] = [] for env_path in env_paths: @@ -47,7 +48,8 @@ class Coordinator: env_files.append(EnvFile(env_path=env_path, config=config, env_type=env_type)) return env_files - def _get_dependency_rules(self, env_files: list[EnvFile]) -> list[DependencyRule]: + @staticmethod + def _get_dependency_rules(env_files: list[EnvFile]) -> list[DependencyRule]: dependency_rules: list[DependencyRule] = [] for env_file in env_files: child_runner_class = RUNNER_DICT[env_file.env_type] -- 2.47.2 From 6ff5b639a6d9ae82a259281a677be156b7898a27 Mon Sep 17 00:00:00 2001 From: Daniel Date: Sat, 2 Dec 2023 18:28:25 +0100 Subject: [PATCH 58/73] rename SubTest to ConditionalTest --- src/runner.py | 18 +++++++++--------- src/tests_authentik/runner_authentik.py | 2 +- src/tests_demo/runner_demo.py | 8 ++++---- src/tests_nextcloud/runner_nextcloud.py | 6 ++++-- src/tests_wordpress/runner_wordpress.py | 6 +++--- 5 files changed, 21 insertions(+), 19 deletions(-) diff --git a/src/runner.py b/src/runner.py index 46819ed..344bd10 100644 --- a/src/runner.py +++ b/src/runner.py @@ -8,7 +8,7 @@ from loguru import logger from src.dirmanager import DirManager -class SubTest(TypedDict): +class ConditionalTest(TypedDict): condition: Callable[[dict[str, str]], bool] test_file: str @@ -20,7 +20,7 @@ class Runner: main_test_name: Optional[str] = None main_cleanup_name: Optional[str] = None dependencies: list[type["Runner"]] = [] - sub_tests: list[SubTest] = [] + conditional_tests: list[ConditionalTest] = [] prevent_skip = False def __init__(self, dotenv_path: Path, output_dir: Path, session_id: str): @@ -69,12 +69,12 @@ class Runner: ) # run sub tests if conditions are met - for sub_test in self.sub_tests: - condition_function = sub_test["condition"] - sub_test_name = sub_test["test_file"] - identifier_string = self.combine_names(self.name, sub_test_name) + for cond_test in self.conditional_tests: + condition_function = cond_test["condition"] + cond_test_name = cond_test["test_file"] + identifier_string = self.combine_names(self.name, cond_test_name) if condition_function(self.config): - test_path = self.root_dir / self.test_dir_name / sub_test_name + test_path = self.root_dir / self.test_dir_name / cond_test_name self._run_or_skip_test(identifier_string=identifier_string, test_path=test_path) else: self._create_result_file(result=-1, identifier_string=identifier_string) @@ -100,12 +100,12 @@ class Runner: self._create_result_file(result=result, identifier_string=identifier_string) def _is_test_passed(self, identifier_string: str, remove_existing: bool = False) -> bool: - """returns True if the selected test (matching test_name + sub_test_name) already passed + """returns True if the selected test matching identifier_string already passed This is determined by the presence of a specific output file in the RESULTS folder that matches identifier_string - remove_existing: If True, result files matching test_name + sub_test_name with a status + remove_existing: If True, result files matching identifier_string with a status other than 'passed' will be deleted""" already_passed = False diff --git a/src/tests_authentik/runner_authentik.py b/src/tests_authentik/runner_authentik.py index 59c486e..9b7218f 100644 --- a/src/tests_authentik/runner_authentik.py +++ b/src/tests_authentik/runner_authentik.py @@ -1,4 +1,4 @@ -from src.runner import Runner, SubTest +from src.runner import Runner def condition_always_true(dotenv_config: dict[str, str]) -> bool: diff --git a/src/tests_demo/runner_demo.py b/src/tests_demo/runner_demo.py index 8f36e9c..310cf93 100644 --- a/src/tests_demo/runner_demo.py +++ b/src/tests_demo/runner_demo.py @@ -1,6 +1,6 @@ from typing import Optional -from src.runner import Runner, SubTest +from src.runner import ConditionalTest, Runner from src.tests_authentik.runner_authentik import RunnerAuthentik @@ -24,6 +24,6 @@ class RunnerDemo(Runner): dependencies: list[type["Runner"]] = [RunnerAuthentik] # this list can hold many more tests from RunnerDemo that run conditional. The condition - # and the test file can be defined by creating a SubTest instance: - # SubTest(condition: Callable, test_file: str) - sub_tests: list[SubTest] = [] + # and the test file can be defined by creating a ConditionalTest instance: + # ConditionalTest(condition: Callable, test_file: str) + conditional_tests: list[ConditionalTest] = [] diff --git a/src/tests_nextcloud/runner_nextcloud.py b/src/tests_nextcloud/runner_nextcloud.py index e5442f6..36c7bd9 100644 --- a/src/tests_nextcloud/runner_nextcloud.py +++ b/src/tests_nextcloud/runner_nextcloud.py @@ -1,4 +1,4 @@ -from src.runner import Runner, SubTest +from src.runner import ConditionalTest, Runner from src.tests_authentik.runner_authentik import RunnerAuthentik @@ -13,4 +13,6 @@ class RunnerNextcloud(Runner): main_test_name = "tests_nextcloud.py" # main_cleanup_name = "cleanup_nextcloud.py" dependencies = [RunnerAuthentik] - sub_tests: list[SubTest] = [SubTest(condition=fake_condition, test_file="tests_nextcloud_onlyoffice.py")] + conditional_tests: list[ConditionalTest] = [ + ConditionalTest(condition=fake_condition, test_file="tests_nextcloud_onlyoffice.py") + ] diff --git a/src/tests_wordpress/runner_wordpress.py b/src/tests_wordpress/runner_wordpress.py index 1f9f4cd..186a554 100644 --- a/src/tests_wordpress/runner_wordpress.py +++ b/src/tests_wordpress/runner_wordpress.py @@ -1,4 +1,4 @@ -from src.runner import Runner, SubTest +from src.runner import ConditionalTest, Runner from src.tests_authentik.runner_authentik import RunnerAuthentik @@ -23,7 +23,7 @@ class RunnerWordpress(Runner): main_setup_name = "setup_wordpress.py" main_test_name = "test_wordpress.py" dependencies: list[type[Runner]] = [RunnerAuthentik] - sub_tests = [ - # SubTest(condition=condition_has_locale, test_file="test_wordpress_localization.py"), + conditional_tests = [ + # ConditionalTest(condition=condition_has_locale, test_file="test_wordpress_localization.py"), ] prevent_skip = True -- 2.47.2 From a6e9fd13cc046d5141e9d6b64cf445912f579307 Mon Sep 17 00:00:00 2001 From: Daniel Date: Sun, 3 Dec 2023 19:47:03 +0100 Subject: [PATCH 59/73] rename and wip --- tests/test_env_resolution.py | 55 ++++++++++++++++++++++++++++++++++++ tests/test_utils.py | 40 -------------------------- 2 files changed, 55 insertions(+), 40 deletions(-) create mode 100644 tests/test_env_resolution.py delete mode 100644 tests/test_utils.py diff --git a/tests/test_env_resolution.py b/tests/test_env_resolution.py new file mode 100644 index 0000000..2e98efb --- /dev/null +++ b/tests/test_env_resolution.py @@ -0,0 +1,55 @@ +import sys +from pathlib import Path + +sys.path.append(Path(__file__).parent.parent.resolve().__str__()) + +# import pytest + +# from prototyping.sorting_algo import Rule, is_rule_satisfied, sort_by_rules +from src.coordinator import Coordinator +from src.env_file_helper import DependencyRule, EnvFile, sort_env_files_by_rule + +# @pytest.fixture +# def in_list(): +# return ["a", "b", "c", "d", "e", "f", "g"] + + +# @pytest.fixture +# def rules() -> list[Rule]: +# return [ # X depends on Y +# Rule("a", "e"), +# Rule("b", "e"), +# Rule("b", "f"), +# Rule("c", "e"), +# Rule("d", "e"), +# Rule("f", "e"), +# ] + + +# def has_rules_satisfied(in_list, rules): +# rule_satisfied: list[bool] = [] +# for rule in rules: +# if is_rule_satisfied(in_list, rule): +# rule_satisfied.append(True) +# else: +# rule_satisfied.append(False) +# return all(rule_satisfied) + + +# def test_stuff(in_list, rules): +# sort_by_rules(in_list, rules) +# assert has_unique_elements(in_list) +# assert has_rules_satisfied(in_list, rules) + + +ENV_FILES = [ + Path("envfiles/login.test.dev.local-it.cloud.env"), # authentik + Path("envfiles/blog.test.dev.local-it.cloud.env"), # wordpress +] + + +env_files: list[EnvFile] = Coordinator._getn_env_files_list(ENV_FILES) +dependency_rules: list[DependencyRule] = Coordinator._get_dependency_rules(env_files) + + +print(dependency_rules) diff --git a/tests/test_utils.py b/tests/test_utils.py deleted file mode 100644 index e462fc6..0000000 --- a/tests/test_utils.py +++ /dev/null @@ -1,40 +0,0 @@ -import pytest - -from prototyping.sorting_algo import Rule, is_rule_satisfied, sort_by_rules - - -@pytest.fixture -def in_list(): - return ["a", "b", "c", "d", "e", "f", "g"] - - -@pytest.fixture -def rules() -> list[Rule]: - return [ # X depends on Y - Rule("a", "e"), - Rule("b", "e"), - Rule("b", "f"), - Rule("c", "e"), - Rule("d", "e"), - Rule("f", "e"), - ] - - -def has_unique_elements(in_list): - return len(set(in_list)) == len(in_list) - - -def has_rules_satisfied(in_list, rules): - rule_satisfied: list[bool] = [] - for rule in rules: - if is_rule_satisfied(in_list, rule): - rule_satisfied.append(True) - else: - rule_satisfied.append(False) - return all(rule_satisfied) - - -def test_stuff(in_list, rules): - sort_by_rules(in_list, rules) - assert has_unique_elements(in_list) - assert has_rules_satisfied(in_list, rules) -- 2.47.2 From ac6976ae870fcbef53686755faa2a82d063bc78b Mon Sep 17 00:00:00 2001 From: Daniel Date: Sun, 3 Dec 2023 19:48:35 +0100 Subject: [PATCH 60/73] remove exit and print --- src/coordinator.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/coordinator.py b/src/coordinator.py index c72f61b..77c4fa5 100644 --- a/src/coordinator.py +++ b/src/coordinator.py @@ -58,8 +58,6 @@ class Coordinator: print("dependency", dependency.name) dependency_rule = DependencyRule(child=child_runner_class.name, dependency=dependency.name) dependency_rules.append(dependency_rule) - print(dependency_rules) - exit() return dependency_rules def setup_test(self) -> None: -- 2.47.2 From ddfec4ccdc1547dc285a22e2316fdc8ef8e89ec2 Mon Sep 17 00:00:00 2001 From: Daniel Date: Sun, 3 Dec 2023 19:50:55 +0100 Subject: [PATCH 61/73] remove print --- src/coordinator.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/coordinator.py b/src/coordinator.py index 77c4fa5..f99e69d 100644 --- a/src/coordinator.py +++ b/src/coordinator.py @@ -53,9 +53,7 @@ class Coordinator: dependency_rules: list[DependencyRule] = [] for env_file in env_files: child_runner_class = RUNNER_DICT[env_file.env_type] - print(child_runner_class.name) for dependency in child_runner_class.dependencies: - print("dependency", dependency.name) dependency_rule = DependencyRule(child=child_runner_class.name, dependency=dependency.name) dependency_rules.append(dependency_rule) return dependency_rules -- 2.47.2 From 83d082f9cb30db7a1da76807d6187b07855feefd Mon Sep 17 00:00:00 2001 From: Daniel Date: Sun, 3 Dec 2023 20:00:20 +0100 Subject: [PATCH 62/73] sort_env_files_by_rule working with EnvFile list --- src/env_file_helper.py | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/src/env_file_helper.py b/src/env_file_helper.py index 944651e..29fb914 100644 --- a/src/env_file_helper.py +++ b/src/env_file_helper.py @@ -9,22 +9,29 @@ class EnvFile(NamedTuple): config: dict[str, str] env_type: str + def __repr__(self) -> str: + return f"EnvFile(type={self.env_type})" + class DependencyRule(NamedTuple): child: str dependency: str -def _is_rule_satisfied(in_list: list, rule: DependencyRule) -> bool: - child_index = in_list.index(rule.child) - parent_index = in_list.index(rule.dependency) - return parent_index < child_index +def _is_rule_satisfied(in_list: list, rule: DependencyRule) -> tuple[bool, int]: + child_indices = [index for index, element in enumerate(in_list) if element.env_type == rule.child] + child_index = min(child_indices) + # child_index = in_list.index(rule.child) + parent_indices = [index for index, element in enumerate(in_list) if element.env_type == rule.dependency] + parent_index = max(parent_indices) + # parent_index = in_list.index(rule.dependency) + return parent_index < child_index, parent_index -def sort_env_files_by_rule(env_list: list, rules: list[DependencyRule]) -> list: +def sort_env_files_by_rule(env_list: list[EnvFile], rules: list[DependencyRule]) -> list: in_list = env_list.copy() - def swap_item_with_previous(in_list: list, index: int): + def swap_item_with_previous(in_list: list[EnvFile], index: int): """swaps item at index N with item at index N-1""" assert index > 0, "cannot swap with negative index" in_list[index], in_list[index - 1] = in_list[index - 1], in_list[index] @@ -32,11 +39,12 @@ def sort_env_files_by_rule(env_list: list, rules: list[DependencyRule]) -> list: for _ in range(10_000): rule_satisfied: list[bool] = [] for rule in rules: - if _is_rule_satisfied(in_list, rule): + is_rule_satisfied, parent_index = _is_rule_satisfied(in_list, rule) + if is_rule_satisfied: rule_satisfied.append(True) else: rule_satisfied.append(False) - parent_index = in_list.index(rule.dependency) + # parent_index = in_list.index(rule.dependency) swap_item_with_previous(in_list, parent_index) if all(rule_satisfied): return in_list -- 2.47.2 From 92160e002170fda25cfda6fb964727bee427653c Mon Sep 17 00:00:00 2001 From: Daniel Date: Sun, 3 Dec 2023 20:00:28 +0100 Subject: [PATCH 63/73] demo working --- tests/test_env_resolution.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/tests/test_env_resolution.py b/tests/test_env_resolution.py index 2e98efb..9d14908 100644 --- a/tests/test_env_resolution.py +++ b/tests/test_env_resolution.py @@ -1,6 +1,8 @@ import sys from pathlib import Path +from icecream import ic + sys.path.append(Path(__file__).parent.parent.resolve().__str__()) # import pytest @@ -43,13 +45,15 @@ from src.env_file_helper import DependencyRule, EnvFile, sort_env_files_by_rule ENV_FILES = [ - Path("envfiles/login.test.dev.local-it.cloud.env"), # authentik Path("envfiles/blog.test.dev.local-it.cloud.env"), # wordpress + Path("envfiles/login.test.dev.local-it.cloud.env"), # authentik ] env_files: list[EnvFile] = Coordinator._getn_env_files_list(ENV_FILES) dependency_rules: list[DependencyRule] = Coordinator._get_dependency_rules(env_files) - -print(dependency_rules) +ic(env_files) +sorted_env_files = sort_env_files_by_rule(env_files, dependency_rules) +ic(env_files) +ic(sorted_env_files) -- 2.47.2 From 05be2c632d1b67f62bf11589ebc4a3ae4bf0224b Mon Sep 17 00:00:00 2001 From: Daniel Date: Mon, 4 Dec 2023 11:25:41 +0100 Subject: [PATCH 64/73] turn Test object into dataclass object, make setups tests and cleanups a list --- src/coordinator.py | 4 +- src/runner.py | 101 ++++++++++++++++++++------------------------- 2 files changed, 47 insertions(+), 58 deletions(-) diff --git a/src/coordinator.py b/src/coordinator.py index f99e69d..0baf934 100644 --- a/src/coordinator.py +++ b/src/coordinator.py @@ -74,11 +74,11 @@ class Coordinator: logger.info("calling run_test()") self.runners: list[Runner] = self._load_runners(self.env_files) for runner in self.runners: - runner.run_setup() + runner.run_setups() for runner in self.runners: runner.run_tests() for runner in self.runners: - runner.run_cleanup() + runner.run_cleanups() logger.info("run_test() finished") def _load_runners(self, env_files: list[EnvFile]) -> list[Runner]: diff --git a/src/runner.py b/src/runner.py index 344bd10..31b9e22 100644 --- a/src/runner.py +++ b/src/runner.py @@ -1,5 +1,6 @@ +from dataclasses import dataclass from pathlib import Path -from typing import Callable, Optional, TypedDict +from typing import Callable import pytest from dotenv import dotenv_values @@ -8,19 +9,20 @@ from loguru import logger from src.dirmanager import DirManager -class ConditionalTest(TypedDict): - condition: Callable[[dict[str, str]], bool] +@dataclass +class Test: test_file: str + condition: Callable[[dict[str, str]], bool] | None = None + prevent_skip: bool = False class Runner: name: str = "" test_dir_name: str = "" - main_setup_name: Optional[str] = None - main_test_name: Optional[str] = None - main_cleanup_name: Optional[str] = None + setups: list[Test] = [] + tests: list[Test] = [] + cleanups: list[Test] = [] dependencies: list[type["Runner"]] = [] - conditional_tests: list[ConditionalTest] = [] prevent_skip = False def __init__(self, dotenv_path: Path, output_dir: Path, session_id: str): @@ -35,69 +37,54 @@ class Runner: self.root_dir = Path(__file__).parent def run_setup(self): - """runs the setup script if available""" - if not self._dependencies_passed(): - logger.warning(f"skipping run_tests() of {self.name}, because some dependencies have not passed") - return - - # run main setup if available - if isinstance(self.main_setup_name, str): - self._run_or_skip_test( - identifier_string=self.combine_names(self.name, self.main_setup_name), - test_path=self.root_dir / self.test_dir_name / self.main_setup_name, - ) + """runs the setup scripts if available""" + self._execute_test_list(self.setups) def run_tests(self): + """runs the test scripts if available""" + self._execute_test_list(self.tests) + + def run_cleanup(self): + """runs the cleanup scripts if available""" + self._execute_test_list(self.cleanups) + + def _execute_test_list(self, test_list: list[Test]): """runs the main test script and if available and sub test scripts if their running condition is met""" # check if required dependencies have passed if not self._dependencies_passed(): logger.warning(f"skipping run_tests() of {self.name}, because some dependencies have not passed") return - # run main setup if available - if isinstance(self.main_setup_name, str): - self._run_or_skip_test( - identifier_string=self.combine_names(self.name, self.main_setup_name), - test_path=self.root_dir / self.test_dir_name / self.main_setup_name, - ) + for test in test_list: + self._run_test_with_checks(test) - # run main test if available - if isinstance(self.main_test_name, str): - self._run_or_skip_test( - identifier_string=self.combine_names(self.name, self.main_test_name), - test_path=self.root_dir / self.test_dir_name / self.main_test_name, - ) + def _run_test_with_checks(self, test: Test): + # dependency passed: true / false + # already_passed: true / false + # prevent_skip: true / false + # condition_available: true / pass + # condition_met: true / false - # run sub tests if conditions are met - for cond_test in self.conditional_tests: - condition_function = cond_test["condition"] - cond_test_name = cond_test["test_file"] - identifier_string = self.combine_names(self.name, cond_test_name) - if condition_function(self.config): - test_path = self.root_dir / self.test_dir_name / cond_test_name - self._run_or_skip_test(identifier_string=identifier_string, test_path=test_path) + identifier_string = self.combine_names(self.name, test.test_file) + test_path = self.root_dir / self.test_dir_name / test.test_file + + # check if test aleady passed + if self._is_test_passed(identifier_string, remove_existing=True): + if test.prevent_skip: + logger.info(f"continuing , test {identifier_string} has passed but prevent_skip=True") else: - self._create_result_file(result=-1, identifier_string=identifier_string) + logger.info(f"skipping {identifier_string}, test has passed") + return - def run_cleanup(self): - """runs the cleanup script if available""" - if not self._dependencies_passed(): - logger.warning("skipping run_cleanup() of {self.name}, because some dependencies have not passed") + if test.condition and not test.condition(self.config): + # test condition is defined but not met + logger.info(f"skipping {identifier_string}, test condition is not met") return - if isinstance(self.main_cleanup_name, str): - identifier_string = self.combine_names(self.name, self.main_cleanup_name) - test_path = self.root_dir / self.test_dir_name / self.main_cleanup_name - logger.info(f"running {identifier_string}") - result = self._call_pytest(test_path) - self._create_result_file(result=result, identifier_string=identifier_string) - def _run_or_skip_test(self, identifier_string: str, test_path: Path): - if not self.prevent_skip and self._is_test_passed(identifier_string, remove_existing=True): - logger.info(f"skipping {identifier_string}") - else: - logger.info(f"running {identifier_string}") - result = self._call_pytest(test_path) - self._create_result_file(result=result, identifier_string=identifier_string) + # test condition is undefined or not met + logger.info(f"running {identifier_string}") + result = self._call_pytest(test_path) + self._create_result_file(result=result, identifier_string=identifier_string) def _is_test_passed(self, identifier_string: str, remove_existing: bool = False) -> bool: """returns True if the selected test matching identifier_string already passed @@ -177,6 +164,8 @@ class Runner: def _dependencies_passed(self): """returns true if the setup of each dependency has passed""" + # todo: check more than one setup + passed_tests = [r.name for r in self.DIRS.RESULTS.glob("*") if "passed" in r.name] results = [] for dependencie in self.dependencies: -- 2.47.2 From 5550098104849d9be0a0fe2ad23787aa8800644c Mon Sep 17 00:00:00 2001 From: Daniel Date: Mon, 4 Dec 2023 11:46:39 +0100 Subject: [PATCH 65/73] update runners with new runner structure --- src/tests_authentik/runner_authentik.py | 6 +++--- src/tests_demo/runner_demo.py | 19 ++++++++----------- src/tests_nextcloud/runner_nextcloud.py | 14 +++++++------- src/tests_wordpress/runner_wordpress.py | 11 +++++------ 4 files changed, 23 insertions(+), 27 deletions(-) diff --git a/src/tests_authentik/runner_authentik.py b/src/tests_authentik/runner_authentik.py index 9b7218f..5b19788 100644 --- a/src/tests_authentik/runner_authentik.py +++ b/src/tests_authentik/runner_authentik.py @@ -1,4 +1,4 @@ -from src.runner import Runner +from src.runner import Runner, Test def condition_always_true(dotenv_config: dict[str, str]) -> bool: @@ -12,5 +12,5 @@ def condition_always_false(dotenv_config: dict[str, str]) -> bool: class RunnerAuthentik(Runner): name = "authentik" test_dir_name = "tests_authentik" - main_setup_name = "setup_authentik.py" - # main_test_name = "test_authentik_dummy.py" + setups = [Test(test_file="setup_authentik.py")] + # tests = [Test(test_file="test_authentik_dummy.py")] diff --git a/src/tests_demo/runner_demo.py b/src/tests_demo/runner_demo.py index 310cf93..2957ad5 100644 --- a/src/tests_demo/runner_demo.py +++ b/src/tests_demo/runner_demo.py @@ -1,6 +1,4 @@ -from typing import Optional - -from src.runner import ConditionalTest, Runner +from src.runner import Runner, Test from src.tests_authentik.runner_authentik import RunnerAuthentik @@ -10,20 +8,19 @@ class RunnerDemo(Runner): name: str = "demo" # name of the test, used for logging / output naming test_dir_name: str = "tests_demo" # dir name holding all tests related to RunnerDemo - # Filename of Demo setup. If defined, it will run 1st by executing pytest - main_setup_name: Optional[str] = "setup_demo.py" - - # Filename of Demo test. This file contains unconditional tests that will be run in any - # case. If defined, it will run 2nd by executing pytest - main_test_name: Optional[str] = None - # this indicates that tests from RunnerDemo depend on the setup from RunnerAuthentik. # RunnerDemo will only execute, when setup_authentik.py has finished successfully. # For example, setup_authentik.py generates session states, that can be used as fixtures # that can be loaded from fixtures_authentik.py dependencies: list[type["Runner"]] = [RunnerAuthentik] + # todo: update these comments + # Filename of Demo setup. If defined, it will run 1st by executing pytest + # Filename of Demo test. This file contains unconditional tests that will be run in any + # case. If defined, it will run 2nd by executing pytest # this list can hold many more tests from RunnerDemo that run conditional. The condition # and the test file can be defined by creating a ConditionalTest instance: # ConditionalTest(condition: Callable, test_file: str) - conditional_tests: list[ConditionalTest] = [] + setups: list[Test] = [] + tests: list[Test] = [] + cleanups: list[Test] = [] diff --git a/src/tests_nextcloud/runner_nextcloud.py b/src/tests_nextcloud/runner_nextcloud.py index 36c7bd9..8468815 100644 --- a/src/tests_nextcloud/runner_nextcloud.py +++ b/src/tests_nextcloud/runner_nextcloud.py @@ -1,18 +1,18 @@ -from src.runner import ConditionalTest, Runner +from src.runner import Runner, Test from src.tests_authentik.runner_authentik import RunnerAuthentik -def fake_condition(arg): +def condition_always_false(dotenv_config: dict[str, str]) -> bool: return False class RunnerNextcloud(Runner): name: str = "nextcloud" test_dir_name: str = "tests_nextcloud" - main_setup_name = "setup_nextcloud.py" - main_test_name = "tests_nextcloud.py" - # main_cleanup_name = "cleanup_nextcloud.py" dependencies = [RunnerAuthentik] - conditional_tests: list[ConditionalTest] = [ - ConditionalTest(condition=fake_condition, test_file="tests_nextcloud_onlyoffice.py") + setups = [Test(test_file="setup_nextcloud.py")] + tests = [ + Test(test_file="tests_nextcloud.py"), + Test(condition=condition_always_false, test_file="tests_nextcloud_onlyoffice.py"), ] + # cleanups = [Test(test_file="cleanup_nextcloud.py")] diff --git a/src/tests_wordpress/runner_wordpress.py b/src/tests_wordpress/runner_wordpress.py index 186a554..097a66a 100644 --- a/src/tests_wordpress/runner_wordpress.py +++ b/src/tests_wordpress/runner_wordpress.py @@ -1,4 +1,4 @@ -from src.runner import ConditionalTest, Runner +from src.runner import Runner, Test from src.tests_authentik.runner_authentik import RunnerAuthentik @@ -20,10 +20,9 @@ def condition_has_locale(dotenv_config: dict[str, str]) -> bool: class RunnerWordpress(Runner): name = "wordpress" test_dir_name = "tests_wordpress" - main_setup_name = "setup_wordpress.py" - main_test_name = "test_wordpress.py" dependencies: list[type[Runner]] = [RunnerAuthentik] - conditional_tests = [ - # ConditionalTest(condition=condition_has_locale, test_file="test_wordpress_localization.py"), + setups = [Test(test_file="setup_wordpress.py")] + tests = [ + Test(test_file="test_wordpress.py"), + Test(condition=condition_has_locale, test_file="test_wordpress_localization.py"), ] - prevent_skip = True -- 2.47.2 From 8652031c464e60eb91e4198e091107f037f7d422 Mon Sep 17 00:00:00 2001 From: Daniel Date: Mon, 4 Dec 2023 11:47:54 +0100 Subject: [PATCH 66/73] rename --- src/runner.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/runner.py b/src/runner.py index 31b9e22..8c4cac1 100644 --- a/src/runner.py +++ b/src/runner.py @@ -36,7 +36,7 @@ class Runner: assert self.test_dir_name self.root_dir = Path(__file__).parent - def run_setup(self): + def run_setups(self): """runs the setup scripts if available""" self._execute_test_list(self.setups) @@ -44,7 +44,7 @@ class Runner: """runs the test scripts if available""" self._execute_test_list(self.tests) - def run_cleanup(self): + def run_cleanups(self): """runs the cleanup scripts if available""" self._execute_test_list(self.cleanups) -- 2.47.2 From 73965f5c48bb1ca373c80c9cf9a010e62a5ba4f3 Mon Sep 17 00:00:00 2001 From: Daniel Date: Mon, 4 Dec 2023 11:59:21 +0100 Subject: [PATCH 67/73] fix _dependencies_passed regarding new list of setups --- src/runner.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/runner.py b/src/runner.py index 8c4cac1..71ca7ed 100644 --- a/src/runner.py +++ b/src/runner.py @@ -162,15 +162,16 @@ class Runner: pass # create empty file def _dependencies_passed(self): - """returns true if the setup of each dependency has passed""" + """returns true if all setups of each dependency have passed""" - # todo: check more than one setup + # todo: what about conditional setups? passed_tests = [r.name for r in self.DIRS.RESULTS.glob("*") if "passed" in r.name] results = [] - for dependencie in self.dependencies: - dependencie_identifier = self.combine_names(dependencie.name, dependencie.main_setup_name) - results.append(any(dependencie_identifier in f for f in passed_tests)) + for dependencie_runner in self.dependencies: + for setup_name in dependencie_runner.setups: + dependencie_identifier = self.combine_names(dependencie_runner.name, setup_name.test_file) + results.append(any(dependencie_identifier in f for f in passed_tests)) return all(results) @staticmethod -- 2.47.2 From 42ed64353bd663281900245e62bbbec8d67dbe80 Mon Sep 17 00:00:00 2001 From: Daniel Date: Mon, 4 Dec 2023 12:40:42 +0100 Subject: [PATCH 68/73] fix test --- src/tests_wordpress/test_wordpress_localization.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/tests_wordpress/test_wordpress_localization.py b/src/tests_wordpress/test_wordpress_localization.py index a3a8a91..77bce95 100644 --- a/src/tests_wordpress/test_wordpress_localization.py +++ b/src/tests_wordpress/test_wordpress_localization.py @@ -5,11 +5,11 @@ from playwright.sync_api import BrowserContext, expect from src.dirmanager import DirManager -def test_welcome_message(user_context: BrowserContext, dotenv_config: dict[str, str], DIR: DirManager): - page = user_context.new_page() +def test_welcome_message(context: BrowserContext, dotenv_config: dict[str, str], DIR: DirManager): + page = context.new_page() url = "https://" + dotenv_config["DOMAIN"] page.goto(url) - expect(page.locator("#wpcontent")).to_be_visible() + expect(page.locator(".wp-block-heading")).to_be_visible() if "locale" in dotenv_config and "de" in dotenv_config["locale"]: expect(page.get_by_role("heading")).to_have_text("Willkommen bei WordPress!") -- 2.47.2 From ee4d3565061c27f522e16fc7d74ef391299289b5 Mon Sep 17 00:00:00 2001 From: Daniel Date: Mon, 4 Dec 2023 12:40:57 +0100 Subject: [PATCH 69/73] initial commit --- prototyping/email_stuff.py | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 prototyping/email_stuff.py diff --git a/prototyping/email_stuff.py b/prototyping/email_stuff.py new file mode 100644 index 0000000..dfbabe0 --- /dev/null +++ b/prototyping/email_stuff.py @@ -0,0 +1,33 @@ +# %% + +import base64 +import email +import imaplib + +# %% +import json +from imaplib import IMAP4, IMAP4_SSL +from pathlib import Path + +# -------------------------------- credentials ------------------------------- # + +cred_file = Path("../credentials.json") +with open(cred_file, "r") as f: + CREDENTIALS = json.load(f) + +username = CREDENTIALS["imap_user"] +password = CREDENTIALS["imap_pass"] + + +# ----------------------------------- imap ----------------------------------- # + +host = "mail.local-it.org" +imap_port = 143 +imap_ssl_port = 993 + + +# with IMAP4("domain.org") as M: +# M.noop() + +with IMAP4_SSL(host=host) as M: + M.noop() -- 2.47.2 From bf60b4b45259ec57bba3bb3bc4f8210645889ebb Mon Sep 17 00:00:00 2001 From: Daniel Date: Mon, 4 Dec 2023 12:40:57 +0100 Subject: [PATCH 70/73] get email subject working --- prototyping/email_stuff.py | 32 +++++++++++++++++++++++--------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/prototyping/email_stuff.py b/prototyping/email_stuff.py index dfbabe0..a8896f8 100644 --- a/prototyping/email_stuff.py +++ b/prototyping/email_stuff.py @@ -1,11 +1,7 @@ # %% - -import base64 import email -import imaplib - -# %% import json +from email.header import decode_header from imaplib import IMAP4, IMAP4_SSL from pathlib import Path @@ -26,8 +22,26 @@ imap_port = 143 imap_ssl_port = 993 -# with IMAP4("domain.org") as M: -# M.noop() +with IMAP4_SSL(host=host) as imap_server: + imap_server.login(username, password) + imap_server.select("INBOX") -with IMAP4_SSL(host=host) as M: - M.noop() + # Search for all emails in the folder + status, email_ids = imap_server.search(None, "ALL") + email_ids = email_ids[0].split() + + # Fetch email details using the retrieved IDs + for email_id in email_ids: + result, data = imap_server.fetch(email_id, "(RFC822)") + raw_email = data[0][1] # Raw content of the email + email_message = email.message_from_bytes(raw_email) + + # Extract the subject + subject_encoded = email_message.get("Subject") + decoded_subject = decode_header(subject_encoded)[0][0] + + if isinstance(decoded_subject, bytes): + decoded_subject = decoded_subject.decode() + + # Print or use the subject as needed + print("Subject:", decoded_subject) -- 2.47.2 From 843d9719168bcd4424be8222f567db78b76bb839 Mon Sep 17 00:00:00 2001 From: Daniel Date: Mon, 4 Dec 2023 12:40:57 +0100 Subject: [PATCH 71/73] add imap_ssl_email_client fixture --- src/conftest.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/conftest.py b/src/conftest.py index 5cedd24..690aaf6 100644 --- a/src/conftest.py +++ b/src/conftest.py @@ -5,6 +5,8 @@ # sys.path. It is thus good practise for projects to either put conftest.py under # a package scope or to never import anything from a conftest.py file. +import os +from imaplib import IMAP4_SSL from pathlib import Path import pytest @@ -69,3 +71,18 @@ def dotenv_config(request) -> dict[str, str]: dotenv_path = Path(dotenv_path) assert dotenv_path.is_file() return dotenv_values(dotenv_path) # type: ignore + + +@pytest.fixture(scope="session") +def imap_ssl_email_client() -> None: + assert os.environ["IMAP_HOST"] + assert os.environ["IMAP_PORT"] + assert os.environ["IMAP_USER"] + assert os.environ["IMAP_PASS"] + port = int(os.environ["IMAP_PORT"]) + imap_client = IMAP4_SSL(host=os.environ["IMAP_HOST"], port=port) + imap_client.login(os.environ["IMAP_USER"], os.environ["IMAP_PASS"]) + imap_client.select("INBOX") + yield imap_client + imap_client.close() + imap_client.logout() -- 2.47.2 From 40d114d10a7fd1b461a44728b15c2d5a3e93280a Mon Sep 17 00:00:00 2001 From: Daniel Date: Mon, 4 Dec 2023 12:40:57 +0100 Subject: [PATCH 72/73] load everything in credentials at once --- main.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/main.py b/main.py index 8cad255..0a5e168 100644 --- a/main.py +++ b/main.py @@ -44,8 +44,11 @@ cred_file = Path("credentials.json") with open(cred_file, "r") as f: CREDENTIALS = json.load(f) -os.environ["ADMIN_USER"] = CREDENTIALS["admin_user"] -os.environ["ADMIN_PASS"] = CREDENTIALS["admin_pass"] +for key, value in CREDENTIALS.items(): + os.environ[key] = value + +# os.environ["ADMIN_USER"] = CREDENTIALS["admin_user"] +# os.environ["ADMIN_PASS"] = CREDENTIALS["admin_pass"] # ----------------------------- define session_id ---------------------------- # -- 2.47.2 From 32d8fcf871135b7a3321ace783ffba624256672b Mon Sep 17 00:00:00 2001 From: Daniel Date: Mon, 4 Dec 2023 12:41:44 +0100 Subject: [PATCH 73/73] cleanup --- main.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/main.py b/main.py index 0a5e168..9005385 100644 --- a/main.py +++ b/main.py @@ -40,6 +40,7 @@ OUTPUT_DIR = Path("./test-output").resolve() # --------------------- load credentials to env variables -------------------- # + cred_file = Path("credentials.json") with open(cred_file, "r") as f: CREDENTIALS = json.load(f) @@ -47,9 +48,6 @@ with open(cred_file, "r") as f: for key, value in CREDENTIALS.items(): os.environ[key] = value -# os.environ["ADMIN_USER"] = CREDENTIALS["admin_user"] -# os.environ["ADMIN_PASS"] = CREDENTIALS["admin_pass"] - # ----------------------------- define session_id ---------------------------- # -- 2.47.2