From 9746e669374e3e03f6a407911864b39d2e02faa0 Mon Sep 17 00:00:00 2001 From: Mridang Agarwalla Date: Wed, 11 Jun 2025 14:21:10 +0300 Subject: [PATCH] refactor(tests): improved all integration tests to run against Docker containers --- .github/workflows/test.yml | 17 ---- docs/Makefile | 20 ---- docs/make.bat | 35 ------- docs/source/conf.py | 26 ------ docs/source/index.rst | 20 ---- etc/docker-compose.yaml | 89 ++++++++++++++++++ etc/example-zitadel-config.yaml | 17 ++++ etc/example-zitadel-init-steps.yaml | 35 +++++++ etc/example-zitadel-secrets.yaml | 8 ++ spec/auth/using_access_token_spec.py | 38 ++------ spec/auth/using_client_credentials_spec.py | 101 ++++++++++++--------- spec/auth/using_private_key_spec.py | 38 ++------ spec/base_spec.py | 87 ++++++++++++++++++ spec/check_session_service_spec.py | 40 ++++---- spec/check_user_service_spec.py | 27 +----- spec/conftest.py | 2 +- 16 files changed, 332 insertions(+), 268 deletions(-) delete mode 100644 docs/Makefile delete mode 100644 docs/make.bat delete mode 100644 docs/source/conf.py delete mode 100644 docs/source/index.rst create mode 100644 etc/docker-compose.yaml create mode 100644 etc/example-zitadel-config.yaml create mode 100644 etc/example-zitadel-init-steps.yaml create mode 100644 etc/example-zitadel-secrets.yaml create mode 100644 spec/base_spec.py diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a6620db5..1793dac3 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -6,17 +6,6 @@ on: ref: required: true type: string - secrets: - BASE_URL: - required: false - AUTH_TOKEN: - required: false - JWT_KEY: - required: false - CLIENT_ID: - required: false - CLIENT_SECRET: - required: false defaults: run: @@ -53,12 +42,6 @@ jobs: - name: Run Tests run: poetry run pytest --junitxml=build/reports/junit.xml - env: - BASE_URL: ${{ secrets.BASE_URL }} - AUTH_TOKEN: ${{ secrets.AUTH_TOKEN }} - JWT_KEY: ${{ secrets.JWT_KEY }} - CLIENT_ID: ${{ secrets.CLIENT_ID }} - CLIENT_SECRET: ${{ secrets.CLIENT_SECRET }} - name: Upload Results uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 diff --git a/docs/Makefile b/docs/Makefile deleted file mode 100644 index d0c3cbf1..00000000 --- a/docs/Makefile +++ /dev/null @@ -1,20 +0,0 @@ -# Minimal makefile for Sphinx documentation -# - -# You can set these variables from the command line, and also -# from the environment for the first two. -SPHINXOPTS ?= -SPHINXBUILD ?= sphinx-build -SOURCEDIR = source -BUILDDIR = build - -# Put it first so that "make" without argument is like "make help". -help: - @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) - -.PHONY: help Makefile - -# Catch-all target: route all unknown targets to Sphinx using the new -# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). -%: Makefile - @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/make.bat b/docs/make.bat deleted file mode 100644 index 747ffb7b..00000000 --- a/docs/make.bat +++ /dev/null @@ -1,35 +0,0 @@ -@ECHO OFF - -pushd %~dp0 - -REM Command file for Sphinx documentation - -if "%SPHINXBUILD%" == "" ( - set SPHINXBUILD=sphinx-build -) -set SOURCEDIR=source -set BUILDDIR=build - -%SPHINXBUILD% >NUL 2>NUL -if errorlevel 9009 ( - echo. - echo.The 'sphinx-build' command was not found. Make sure you have Sphinx - echo.installed, then set the SPHINXBUILD environment variable to point - echo.to the full path of the 'sphinx-build' executable. Alternatively you - echo.may add the Sphinx directory to PATH. - echo. - echo.If you don't have Sphinx installed, grab it from - echo.https://www.sphinx-doc.org/ - exit /b 1 -) - -if "%1" == "" goto help - -%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% -goto end - -:help -%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% - -:end -popd diff --git a/docs/source/conf.py b/docs/source/conf.py deleted file mode 100644 index 06440b84..00000000 --- a/docs/source/conf.py +++ /dev/null @@ -1,26 +0,0 @@ -# Configuration file for the Sphinx documentation builder. -# -# For the full list of built-in configuration values, see the documentation: -# https://www.sphinx-doc.org/en/master/usage/configuration.html - -# -- Project information ----------------------------------------------------- -# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information - -project = "Zitadel" -copyright = "2025, Mridang Agarwalla" -author = "Mridang Agarwalla" - -# -- General configuration --------------------------------------------------- -# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration - -extensions = [] - -templates_path = ["_templates"] -exclude_patterns = [] - - -# -- Options for HTML output ------------------------------------------------- -# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output - -html_theme = "alabaster" -html_static_path = ["_static"] diff --git a/docs/source/index.rst b/docs/source/index.rst deleted file mode 100644 index c99c8cf0..00000000 --- a/docs/source/index.rst +++ /dev/null @@ -1,20 +0,0 @@ -.. Zitadel documentation master file, created by - sphinx-quickstart on Thu May 8 16:12:39 2025. - You can adapt this file completely to your liking, but it should at least - contain the root `toctree` directive. - -Welcome to Zitadel's documentation! -=================================== - -.. toctree:: - :maxdepth: 2 - :caption: Contents: - - - -Indices and tables -================== - -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` diff --git a/etc/docker-compose.yaml b/etc/docker-compose.yaml new file mode 100644 index 00000000..238af6f9 --- /dev/null +++ b/etc/docker-compose.yaml @@ -0,0 +1,89 @@ +services: + db: + image: postgres:17-alpine + restart: unless-stopped + environment: + POSTGRES_USER: root + POSTGRES_PASSWORD: postgres + networks: + - storage + healthcheck: + test: [ "CMD-SHELL", "pg_isready", "-d", "db_prod" ] + interval: 10s + timeout: 60s + retries: 5 + start_period: 10s + volumes: + - data:/var/lib/postgresql/data:rw + + zitadel-init: + restart: 'no' + networks: + - storage + image: 'ghcr.io/zitadel/zitadel:latest' + command: 'init --config /example-zitadel-config.yaml --config /example-zitadel-secrets.yaml' + depends_on: + db: + condition: 'service_healthy' + volumes: + - './example-zitadel-config.yaml:/example-zitadel-config.yaml:ro' + - './example-zitadel-secrets.yaml:/example-zitadel-secrets.yaml:ro' + - './zitadel_output:/var/zitadel_output:rw' + + zitadel-setup: + restart: 'no' + networks: + - storage + image: 'ghcr.io/zitadel/zitadel:latest-debug' + user: root + entrypoint: '/bin/bash' + command: [ "-c", "/app/zitadel setup --config /example-zitadel-config.yaml --config /example-zitadel-secrets.yaml --steps /example-zitadel-init-steps.yaml --masterkey \"my_test_masterkey_0123456789ABEF\" && echo \"--- ZITADEL SETUP COMPLETE ---\" && echo \"Personal Access Token (PAT) will be in ./zitadel_output/pat.txt on your host.\" && echo \"Service Account Key will be in ./zitadel_output/sa-key.json on your host.\" && echo \"OAuth Client ID and Secret will be in 'zitadel' service logs (grep for 'Application created').\"" ] + environment: + - PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/app + depends_on: + zitadel-init: + condition: 'service_completed_successfully' + restart: false + volumes: + - './zitadel_output:/var/zitadel_output:rw' + - './example-zitadel-config.yaml:/example-zitadel-config.yaml:ro' + - './example-zitadel-secrets.yaml:/example-zitadel-secrets.yaml:ro' + - './example-zitadel-init-steps.yaml:/example-zitadel-init-steps.yaml:ro' + + zitadel: + restart: 'unless-stopped' + networks: + - backend + - storage + image: 'ghcr.io/zitadel/zitadel:latest' + command: > + start --config /example-zitadel-config.yaml + --config /example-zitadel-secrets.yaml + --masterkey my_test_masterkey_0123456789ABEF + depends_on: + zitadel-setup: + condition: 'service_completed_successfully' + restart: true + volumes: + - './example-zitadel-config.yaml:/example-zitadel-config.yaml:ro' + - './example-zitadel-secrets.yaml:/example-zitadel-secrets.yaml:ro' + - './zitadel_output:/var/zitadel_output:rw' + ports: + - "8099:8080" + healthcheck: + test: [ + "CMD", "/app/zitadel", "ready", + "--config", "/example-zitadel-config.yaml", + "--config", "/example-zitadel-secrets.yaml" + ] + interval: 10s + timeout: 60s + retries: 5 + start_period: 10s + +networks: + storage: { } + backend: { } + +volumes: + data: { } diff --git a/etc/example-zitadel-config.yaml b/etc/example-zitadel-config.yaml new file mode 100644 index 00000000..7635f8a2 --- /dev/null +++ b/etc/example-zitadel-config.yaml @@ -0,0 +1,17 @@ +ExternalSecure: false +ExternalDomain: localhost +ExternalPort: 8080 +TLS.Enabled: false +Database: + postgres: + Host: 'db' + Port: 5432 + Database: zitadel + User.SSL.Mode: 'disable' + Admin.SSL.Mode: 'disable' +OIDC: + DefaultLoginURLV2: "/ui/v2/login/login?authRequest=" + DefaultLogoutURLV2: "/ui/v2/login/logout?post_logout_redirect=" +SAML.DefaultLoginURLV2: "/ui/v2/login/login?authRequest=" +LogStore.Access.Stdout.Enabled: true +DefaultInstance.LoginPolicy.MfaInitSkipLifetime: "0s" diff --git a/etc/example-zitadel-init-steps.yaml b/etc/example-zitadel-init-steps.yaml new file mode 100644 index 00000000..d697ad8f --- /dev/null +++ b/etc/example-zitadel-init-steps.yaml @@ -0,0 +1,35 @@ +FirstInstance: + MachineKeyPath: '/var/zitadel_output/sa-key.json' + PatPath: '/var/zitadel_output/pat.txt' + Org: + Human: + PasswordChangeRequired: false + Username: zitadel-admin@zitadel.localhost + Password: Password1! + Machine: + Machine: + Username: api-user + Name: Combined API User + MachineKey: + ExpirationDate: '2030-01-01T00:00:00Z' + Type: 1 + Pat: + ExpirationDate: '2030-01-01T00:00:00Z' + Applications: + - OIDC: + RedirectUris: + - http://localhost:8080/callback + - http://127.0.0.1:8080/callback + ResponseTypes: + - CODE + - ID_TOKEN + - TOKEN + GrantTypes: + - AUTHORIZATION_CODE + - IMPLICIT + - REFRESH_TOKEN + - CLIENT_CREDENTIALS + AuthMethodType: POST + Name: 'MyOAuthAPIClient' + Type: 'WEB' +DefaultInstance.LoginPolicy.MfaInitSkipLifetime: "0s" diff --git a/etc/example-zitadel-secrets.yaml b/etc/example-zitadel-secrets.yaml new file mode 100644 index 00000000..66d495cb --- /dev/null +++ b/etc/example-zitadel-secrets.yaml @@ -0,0 +1,8 @@ +Database: + postgres: + User: + Username: 'zitadel_user' + Password: 'zitadel' + Admin: + Username: 'root' + Password: 'postgres' diff --git a/spec/auth/using_access_token_spec.py b/spec/auth/using_access_token_spec.py index 7aaef13b..47ff5c99 100644 --- a/spec/auth/using_access_token_spec.py +++ b/spec/auth/using_access_token_spec.py @@ -1,29 +1,12 @@ -import os +from typing import Dict import pytest import zitadel_client as zitadel +from spec.base_spec import docker_compose as docker_compose from zitadel_client.exceptions import ZitadelError -@pytest.fixture(scope="module") -def base_url() -> str: - """Provides the base URL for tests, skipping if unset.""" - url = os.getenv("BASE_URL") - if not url: - pytest.skip("Environment variable BASE_URL must be set", allow_module_level=True) - return url - - -@pytest.fixture(scope="module") -def auth_token() -> str: - """Provides the auth token for tests, skipping if unset.""" - url = os.getenv("AUTH_TOKEN") - if not url: - pytest.skip("Environment variable AUTH_TOKEN must be set", allow_module_level=True) - return url - - class TestUseAccessTokenSpec: """ SettingsService Integration Tests (Personal Access Token) @@ -37,25 +20,18 @@ class TestUseAccessTokenSpec: Each test instantiates a new client to ensure a clean, stateless call. """ - def test_retrieves_general_settings_with_valid_token( - self, - base_url: str, - auth_token: str, - ) -> None: + def test_retrieves_general_settings_with_valid_token(self, docker_compose: Dict[str, str]) -> None: # noqa F811 """Retrieves general settings successfully with a valid access token.""" client = zitadel.Zitadel.with_access_token( - base_url, - auth_token, + docker_compose["base_url"], + docker_compose["auth_token"], ) client.settings.settings_service_get_general_settings() - def test_raises_api_exception_with_invalid_token( - self, - base_url: str, - ) -> None: + def test_raises_api_exception_with_invalid_token(self, docker_compose: Dict[str, str]) -> None: # noqa F811 """Raises ApiException when using an invalid access token.""" client = zitadel.Zitadel.with_access_token( - base_url, + docker_compose["base_url"], "invalid", ) with pytest.raises(ZitadelError): diff --git a/spec/auth/using_client_credentials_spec.py b/spec/auth/using_client_credentials_spec.py index bc430640..1db71d03 100644 --- a/spec/auth/using_client_credentials_spec.py +++ b/spec/auth/using_client_credentials_spec.py @@ -1,38 +1,14 @@ -import os +import json +from typing import Any, Dict import pytest +import urllib3 import zitadel_client as zitadel +from spec.base_spec import docker_compose as docker_compose from zitadel_client.exceptions import ZitadelError -@pytest.fixture(scope="module") -def base_url() -> str: - """Provides the base URL for tests, skipping if unset.""" - url = os.getenv("BASE_URL") - if not url: - pytest.skip("Environment variable BASE_URL must be set", allow_module_level=True) - return url - - -@pytest.fixture(scope="module") -def client_id() -> str: - """Provides the client ID for tests, skipping if unset.""" - cid = os.getenv("CLIENT_ID") - if not cid: - pytest.skip("Environment variable CLIENT_ID must be set", allow_module_level=True) - return cid - - -@pytest.fixture(scope="module") -def client_secret() -> str: - """Provides the client secret for tests, skipping if unset.""" - cs = os.getenv("CLIENT_SECRET") - if not cs: - pytest.skip("Environment variable CLIENT_SECRET must be set", allow_module_level=True) - return cs - - class TestUseClientCredentialsSpec: """ SettingsService Integration Tests (Client Credentials) @@ -46,27 +22,68 @@ class TestUseClientCredentialsSpec: Each test instantiates a new client to ensure a clean, stateless call. """ - def test_retrieves_general_settings_with_valid_client_credentials( - self, - base_url: str, - client_id: str, - client_secret: str, - ) -> None: + @staticmethod + def generate_user_secret(token: str, login_name: str = "api-user") -> Dict[str, str]: + http = urllib3.PoolManager() + + user_id_response = http.request( + "GET", + "http://localhost:8099/management/v1/global/users/_by_login_name", + fields={"loginName": login_name}, + headers={"Authorization": f"Bearer {token}", "Accept": "application/json"}, + ) + + if user_id_response.status < 400: + user_response_map: Dict[str, Any] = json.loads(user_id_response.data.decode("utf-8")) + user_payload: Any = user_response_map.get("user") + user_id: Any = user_payload.get("id") if isinstance(user_payload, dict) else None + + if isinstance(user_id, str) and user_id: + put_headers = { + "Authorization": f"Bearer {token}", + "Accept": "application/json", + "Content-Type": "application/json", + } + encoded_body = json.dumps({}).encode("utf-8") + + secret_response = http.request( + "PUT", f"http://localhost:8099/management/v1/users/{user_id}/secret", headers=put_headers, body=encoded_body + ) + + if secret_response.status < 400: + secret_data: Dict[str, Any] = json.loads(secret_response.data.decode("utf-8")) + client_id: Any = secret_data.get("clientId") + client_secret: Any = secret_data.get("clientSecret") + + if isinstance(client_id, str) and client_id and isinstance(client_secret, str) and client_secret: + return {"client_id": client_id, "client_secret": client_secret} + else: + print(secret_data) # noqa T201 + raise ValueError("API response for secret is missing 'clientId' or 'clientSecret'.") + else: + error_body = secret_response.data.decode("utf-8") + raise Exception(f"API call to generate secret failed for user ID: '{user_id}'. Response: {error_body}") + else: + print(user_response_map) # noqa T201 + raise ValueError(f"Could not parse a valid user ID from API response for login name: '{login_name}'.") + else: + error_body = user_id_response.data.decode("utf-8") + raise Exception(f"API call to retrieve user failed for login name: '{login_name}'. Response: {error_body}") + + def test_retrieves_general_settings_with_valid_client_credentials(self, docker_compose: Dict[str, str]) -> None: # noqa F811 """Retrieves general settings successfully with valid client credentials.""" + credentials = self.generate_user_secret(docker_compose["auth_token"]) client = zitadel.Zitadel.with_client_credentials( - base_url, - client_id, - client_secret, + docker_compose["base_url"], + credentials["client_id"], + credentials["client_secret"], ) client.settings.settings_service_get_general_settings() - def test_raises_api_exception_with_invalid_client_credentials( - self, - base_url: str, - ) -> None: + def test_raises_api_exception_with_invalid_client_credentials(self, docker_compose: Dict[str, str]) -> None: # noqa F811 """Raises ApiException when using invalid client credentials.""" client = zitadel.Zitadel.with_client_credentials( - base_url, + docker_compose["base_url"], "invalid", "invalid", ) diff --git a/spec/auth/using_private_key_spec.py b/spec/auth/using_private_key_spec.py index 76446a82..3c11abf3 100644 --- a/spec/auth/using_private_key_spec.py +++ b/spec/auth/using_private_key_spec.py @@ -1,29 +1,12 @@ -import os -import pathlib +from typing import Dict import pytest import zitadel_client as zitadel +from spec.base_spec import docker_compose as docker_compose from zitadel_client import ZitadelError -@pytest.fixture(scope="module") -def base_url() -> str: - """Provides the base URL for tests, skipping if unset.""" - url = os.getenv("BASE_URL") - if not url: - pytest.skip("Environment variable BASE_URL must be set", allow_module_level=True) - return url - - -@pytest.fixture -def key_file(tmp_path: pathlib.Path) -> str: - raw: str = os.getenv("JWT_KEY") or "" - file_path: pathlib.Path = tmp_path / "jwt.json" - file_path.write_text(raw) - return str(file_path) - - class TestUsePrivateKeySpec: """ SettingsService Integration Tests (Private Key Assertion) @@ -37,26 +20,19 @@ class TestUsePrivateKeySpec: Each test instantiates a new client to ensure a clean, stateless call. """ - def test_retrieves_general_settings_with_valid_private_key( - self, - base_url: str, - key_file: str, - ) -> None: + def test_retrieves_general_settings_with_valid_private_key(self, docker_compose: Dict[str, str]) -> None: # noqa F811 """Retrieves general settings successfully with a valid private key.""" client = zitadel.Zitadel.with_private_key( - base_url, - key_file, + docker_compose["base_url"], + docker_compose["jwt_key"], ) client.settings.settings_service_get_general_settings() - def test_raises_api_exception_with_invalid_private_key( - self, - key_file: str, - ) -> None: + def test_raises_api_exception_with_invalid_private_key(self, docker_compose: Dict[str, str]) -> None: # noqa F811 """Raises ApiException when using an invalid private key path.""" client = zitadel.Zitadel.with_private_key( "https://zitadel.cloud", - key_file, + docker_compose["jwt_key"], ) with pytest.raises(ZitadelError): client.settings.settings_service_get_general_settings() diff --git a/spec/base_spec.py b/spec/base_spec.py new file mode 100644 index 00000000..ac876b09 --- /dev/null +++ b/spec/base_spec.py @@ -0,0 +1,87 @@ +import logging +import os +import shlex +import subprocess +import time +from typing import Dict, Generator + +import pytest + +LOGGER = logging.getLogger(__name__) +COMPOSE_FILE_PATH: str = os.path.join(os.path.dirname(__file__), "..", "etc", "docker-compose.yaml") +COMPOSE_FILE_DIR: str = os.path.dirname(COMPOSE_FILE_PATH) + + +@pytest.fixture(scope="module") +def docker_compose() -> Generator[Dict[str, str], None, None]: + """ + Abstract base class for integration tests that interact with a Docker + Compose stack. + + This fixture handles the lifecycle of the Docker Compose environment, + bringing it up before tests run and tearing it down afterwards. It also + provides mechanisms to load specific data (like authentication tokens + and JWT keys) from files and makes them accessible via the returned dictionary. + + Yields: + dict: A dictionary containing: + - "auth_token" (str): The authentication token loaded from a file. + - "jwt_key" (str): The absolute path to the JWT key file. + - "base_url" (str): The base URL for the services. + + After the tests are finished, the Docker Compose stack is torn down. + """ + LOGGER.info("Bringing up Docker Compose stack...") + command: list[str] = [ + "docker", + "compose", + "--file", + shlex.quote(COMPOSE_FILE_PATH), + "up", + "--detach", + "--no-color", + "--quiet-pull", + "--yes", + ] + result = subprocess.run(command, capture_output=True, text=True) # noqa: S603 + + LOGGER.info(result.stdout) + if result.returncode != 0: + error_message: str = f"Failed to bring up Docker Compose stack. Exit code: {result.returncode}\n{result.stderr}" + raise RuntimeError(error_message) + LOGGER.info("Docker Compose stack is up.") + + auth_token_path: str = os.path.join(COMPOSE_FILE_DIR, "zitadel_output", "pat.txt") + if os.path.exists(auth_token_path): + with open(auth_token_path, "r") as f: + auth_token = f.read().strip() + LOGGER.info(f"Loaded authToken: {auth_token}") + else: + raise Exception(f"Auth token file not found at: {auth_token_path}") + + jwt_key_path: str = os.path.join(COMPOSE_FILE_DIR, "zitadel_output", "sa-key.json") + if not os.path.exists(jwt_key_path): + raise Exception(f"JWT Key file not found at path: {jwt_key_path}") + jwt_key = jwt_key_path + LOGGER.info(f"Loaded JWT_KEY path: {jwt_key}") + + base_url: str = "http://localhost:8099" + LOGGER.info(f"Exposed BASE_URL: {base_url}") + + time.sleep(20) + + yield { + "auth_token": auth_token, + "jwt_key": jwt_key, + "base_url": base_url, + } + + LOGGER.info("Tearing down Docker Compose stack...") + command = ["docker", "compose", "--file", shlex.quote(COMPOSE_FILE_PATH), "down", "-v"] + result = subprocess.run(command, capture_output=True, text=True) # noqa: S603 + + LOGGER.info(result.stdout) + if result.returncode != 0: + LOGGER.warning(f"Failed to tear down Docker Compose stack. Exit code: {result.returncode}\n{result.stderr}") + else: + LOGGER.info("Docker Compose stack torn down.") diff --git a/spec/check_session_service_spec.py b/spec/check_session_service_spec.py index e1ebecc3..198f3591 100644 --- a/spec/check_session_service_spec.py +++ b/spec/check_session_service_spec.py @@ -1,10 +1,10 @@ -import os import uuid -from typing import Generator +from typing import Dict, Generator import pytest import zitadel_client as zitadel +from spec.base_spec import docker_compose as docker_compose from zitadel_client.exceptions import ApiError from zitadel_client.models import ( SessionServiceChecks, @@ -17,39 +17,33 @@ SessionServiceListSessionsResponse, SessionServiceSetSessionRequest, SessionServiceSetSessionResponse, + UserServiceAddHumanUserRequest, + UserServiceSetHumanEmail, + UserServiceSetHumanProfile, ) -# noinspection DuplicatedCode @pytest.fixture(scope="module") -def base_url() -> str: - """Provides the base URL for tests, skipping if unset.""" - url = os.getenv("BASE_URL") - if not url: - pytest.skip("Environment variable BASE_URL must be set", allow_module_level=True) - return url - - -@pytest.fixture(scope="module") -def auth_token() -> str: - """Provides a valid personal access token, skipping if unset.""" - token = os.getenv("AUTH_TOKEN") - if not token: - pytest.skip("Environment variable AUTH_TOKEN must be set", allow_module_level=True) - return token - - -@pytest.fixture(scope="module") -def client(base_url: str, auth_token: str) -> zitadel.Zitadel: +def client(docker_compose: Dict[str, str]) -> zitadel.Zitadel: # noqa F811 """Provides a Zitadel client configured with a personal access token.""" + base_url = docker_compose["base_url"] + auth_token = docker_compose["auth_token"] return zitadel.Zitadel.with_access_token(base_url, auth_token) @pytest.fixture def session(client: zitadel.Zitadel) -> Generator[SessionServiceCreateSessionResponse, None, None]: """Creates a fresh session for each test and cleans up afterward.""" + username = uuid.uuid4().hex + request1 = UserServiceAddHumanUserRequest( + username=username, + profile=UserServiceSetHumanProfile(given_name="John", family_name="Doe"), # type: ignore[call-arg] + email=UserServiceSetHumanEmail(email=f"johndoe{uuid.uuid4().hex}@example.com"), + ) + client.users.user_service_add_human_user(request1) + request = SessionServiceCreateSessionRequest( - checks=SessionServiceChecks(user=SessionServiceCheckUser(loginName="johndoe")), + checks=SessionServiceChecks(user=SessionServiceCheckUser(loginName=username)), lifetime="18000s", ) response = client.sessions.session_service_create_session(request) diff --git a/spec/check_user_service_spec.py b/spec/check_user_service_spec.py index 847b934f..4f6f3f33 100644 --- a/spec/check_user_service_spec.py +++ b/spec/check_user_service_spec.py @@ -1,10 +1,10 @@ -import os import uuid -from typing import Generator +from typing import Dict, Generator import pytest import zitadel_client as zitadel +from spec.base_spec import docker_compose as docker_compose from zitadel_client.exceptions import ApiError from zitadel_client.models import ( UserServiceAddHumanUserRequest, @@ -17,28 +17,11 @@ ) -# noinspection DuplicatedCode @pytest.fixture(scope="module") -def base_url() -> str: - """Provides the base URL for tests, skipping if unset.""" - url = os.getenv("BASE_URL") - if not url: - pytest.skip("Environment variable BASE_URL must be set", allow_module_level=True) - return url - - -@pytest.fixture(scope="module") -def auth_token() -> str: - """Provides a valid personal access token, skipping if unset.""" - token = os.getenv("AUTH_TOKEN") - if not token: - pytest.skip("Environment variable AUTH_TOKEN must be set", allow_module_level=True) - return token - - -@pytest.fixture(scope="module") -def client(base_url: str, auth_token: str) -> zitadel.Zitadel: +def client(docker_compose: Dict[str, str]) -> zitadel.Zitadel: # noqa F811 """Provides a Zitadel client configured with a personal access token.""" + base_url = docker_compose["base_url"] + auth_token = docker_compose["auth_token"] return zitadel.Zitadel.with_access_token(base_url, auth_token) diff --git a/spec/conftest.py b/spec/conftest.py index b6dfda5a..80fed6e7 100644 --- a/spec/conftest.py +++ b/spec/conftest.py @@ -397,7 +397,7 @@ def _opentestcase(self, report: TestReport) -> _NodeReporter: return reporter # noinspection DuplicatedCode - def pytest_runtest_logreport(self, report: TestReport) -> None: + def pytest_runtest_logreport(self, report: TestReport) -> None: # noqa: C901 """Handle a setup/call/teardown report, generating the appropriate XML tags as necessary.