mirror of
https://github.com/goauthentik/authentik.git
synced 2026-06-17 19:09:11 +03:00
tests: improve e2e/integration test reliability (#19540)
* add flakefinder Signed-off-by: Jens Langhammer <jens@goauthentik.io> * show local IP in test header Signed-off-by: Jens Langhammer <jens@goauthentik.io> * attempt to join worker on test finish Signed-off-by: Jens Langhammer <jens@goauthentik.io> * fix? Signed-off-by: Jens Langhammer <jens@goauthentik.io> * add timeout Signed-off-by: Jens Langhammer <jens@goauthentik.io> * add flush Signed-off-by: Jens Langhammer <jens@goauthentik.io> * stop -> close Signed-off-by: Jens Langhammer <jens@goauthentik.io> * fix rare test issue of this failing Signed-off-by: Jens Langhammer <jens@goauthentik.io> * check correctly Signed-off-by: Jens Langhammer <jens@goauthentik.io> * un-serialize rollback? Signed-off-by: Jens Langhammer <jens@goauthentik.io> * explicitly join before db teardown Signed-off-by: Jens Langhammer <jens@goauthentik.io> * skip flaky tests Signed-off-by: Jens Langhammer <jens@goauthentik.io> * new broker Signed-off-by: Jens Langhammer <jens@goauthentik.io> * classmethod Signed-off-by: Jens Langhammer <jens@goauthentik.io> * separate docker helpers Signed-off-by: Jens Langhammer <jens@goauthentik.io> * only timeout functions Signed-off-by: Jens Langhammer <jens@goauthentik.io> * type and format Signed-off-by: Jens Langhammer <jens@goauthentik.io> * show detected IP too Signed-off-by: Jens Langhammer <jens@goauthentik.io> * cleanup Signed-off-by: Jens Langhammer <jens@goauthentik.io> --------- Signed-off-by: Jens Langhammer <jens@goauthentik.io>
This commit is contained in:
@@ -38,7 +38,7 @@ def invalidate_flow_cache(sender, instance, **_):
|
||||
if isinstance(instance, Flow):
|
||||
total = delete_cache_prefix(f"{cache_key(instance)}*")
|
||||
LOGGER.debug("Invalidating Flow cache", flow=instance, len=total)
|
||||
if isinstance(instance, FlowStageBinding):
|
||||
if isinstance(instance, FlowStageBinding) and instance.target_id:
|
||||
total = delete_cache_prefix(f"{cache_key(instance.target)}*")
|
||||
LOGGER.debug("Invalidating Flow cache from FlowStageBinding", binding=instance, len=total)
|
||||
if isinstance(instance, Stage):
|
||||
|
||||
@@ -6,6 +6,7 @@ import pytest
|
||||
from cryptography.hazmat.backends.openssl.backend import backend
|
||||
|
||||
from authentik import authentik_full_version
|
||||
from tests.e2e.utils import get_local_ip
|
||||
|
||||
IS_CI = "CI" in environ
|
||||
|
||||
@@ -24,6 +25,7 @@ def pytest_report_header(*_, **__):
|
||||
return [
|
||||
f"authentik version: {authentik_full_version()}",
|
||||
f"OpenSSL version: {OPENSSL_VERSION}, FIPS: {backend._fips_enabled}",
|
||||
f"Local IP: {get_local_ip()} (Detected as {get_local_ip(override=False)})",
|
||||
]
|
||||
|
||||
|
||||
|
||||
@@ -56,6 +56,10 @@ class PytestTestRunner(DiscoverRunner): # pragma: no cover
|
||||
if kwargs.get("no_capture", False):
|
||||
self.args.append("--capture=no")
|
||||
|
||||
if kwargs.get("count", None):
|
||||
self.args.append("--flake-finder")
|
||||
self.args.append(f"--flake-runs={kwargs['count']}")
|
||||
|
||||
self._setup_test_environment()
|
||||
|
||||
def _setup_test_environment(self):
|
||||
@@ -113,6 +117,7 @@ class PytestTestRunner(DiscoverRunner): # pragma: no cover
|
||||
action="store_true",
|
||||
help="Disable any capturing of stdout/stderr during tests.",
|
||||
)
|
||||
parser.add_argument("--count", type=int, help="Re-run selected tests n times")
|
||||
|
||||
def _validate_test_label(self, label: str) -> bool:
|
||||
"""Validate test label format"""
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
from queue import PriorityQueue
|
||||
|
||||
import dramatiq
|
||||
from django.utils.module_loading import import_string
|
||||
from django_dramatiq_postgres.conf import Conf
|
||||
from dramatiq import set_broker
|
||||
from dramatiq.broker import Broker, MessageProxy, get_broker
|
||||
from dramatiq.middleware.middleware import Middleware
|
||||
from dramatiq.middleware.retries import Retries
|
||||
from dramatiq.results.middleware import Results
|
||||
from dramatiq.worker import Worker, _ConsumerThread, _WorkerThread
|
||||
@@ -65,7 +66,7 @@ def use_test_broker():
|
||||
actor.broker.declare_actor(actor)
|
||||
|
||||
for middleware_class, middleware_kwargs in Conf().middlewares:
|
||||
middleware: dramatiq.middleware.middleware.Middleware = import_string(middleware_class)(
|
||||
middleware: Middleware = import_string(middleware_class)(
|
||||
**middleware_kwargs,
|
||||
)
|
||||
if isinstance(middleware, MetricsMiddleware):
|
||||
@@ -79,4 +80,4 @@ def use_test_broker():
|
||||
)
|
||||
broker.add_middleware(middleware)
|
||||
|
||||
dramatiq.set_broker(broker)
|
||||
set_broker(broker)
|
||||
|
||||
@@ -97,6 +97,7 @@ dev = [
|
||||
"mypy==1.19.1",
|
||||
"pdoc==16.0.0",
|
||||
"pytest-django==4.11.1",
|
||||
"pytest-flakefinder==1.1.0",
|
||||
"pytest-github-actions-annotate-failures==0.3.0",
|
||||
"pytest-randomly==4.0.1",
|
||||
"pytest-timeout==2.4.0",
|
||||
@@ -105,7 +106,11 @@ dev = [
|
||||
"ruff==0.14.13",
|
||||
"selenium==4.39.0",
|
||||
"types-channels==4.3.0.20250822",
|
||||
"types-docker==7.1.0.20260109",
|
||||
"types-jwcrypto==1.5.0.20251102",
|
||||
"types-ldap3==2.9.13.20251121",
|
||||
"types-requests==2.32.4.20260107",
|
||||
"types-zxcvbn==4.5.0.20250809",
|
||||
]
|
||||
|
||||
[tool.uv]
|
||||
|
||||
+114
@@ -0,0 +1,114 @@
|
||||
"""authentik e2e testing utilities"""
|
||||
|
||||
from os import environ
|
||||
from time import sleep
|
||||
from typing import Any
|
||||
from unittest.case import TestCase
|
||||
|
||||
from docker import DockerClient, from_env
|
||||
from docker.errors import DockerException
|
||||
from docker.models.containers import Container
|
||||
from docker.models.networks import Network
|
||||
|
||||
from authentik.lib.generators import generate_id
|
||||
from authentik.root.test_runner import get_docker_tag
|
||||
|
||||
IS_CI = "CI" in environ
|
||||
|
||||
|
||||
class DockerTestCase(TestCase):
|
||||
"""Mixin for dealing with containers"""
|
||||
|
||||
max_healthcheck_attempts = 45
|
||||
|
||||
__client: DockerClient
|
||||
__network: Network
|
||||
|
||||
__label_id = generate_id()
|
||||
|
||||
def setUp(self) -> None:
|
||||
self.__client = from_env()
|
||||
self.__network = self.docker_client.networks.create(name=f"authentik-test-{generate_id()}")
|
||||
|
||||
@property
|
||||
def docker_client(self) -> DockerClient:
|
||||
return self.__client
|
||||
|
||||
@property
|
||||
def docker_network(self) -> Network:
|
||||
return self.__network
|
||||
|
||||
@property
|
||||
def docker_labels(self) -> dict[str, str]:
|
||||
return {"io.goauthentik.test": self.__label_id}
|
||||
|
||||
def wait_for_container(self, container: Container) -> Container:
|
||||
"""Check that container is health"""
|
||||
attempt = 0
|
||||
while True:
|
||||
container.reload()
|
||||
status = container.attrs.get("State", {}).get("Health", {}).get("Status")
|
||||
if status == "healthy":
|
||||
return container
|
||||
sleep(1)
|
||||
attempt += 1
|
||||
if attempt >= self.max_healthcheck_attempts:
|
||||
self.output_container_logs(container)
|
||||
raise self.failureException("Container failed to start")
|
||||
|
||||
def get_container_image(self, base: str) -> str:
|
||||
"""Try to pull docker image based on git branch, fallback to main if not found."""
|
||||
image = f"{base}:gh-main"
|
||||
try:
|
||||
branch_image = f"{base}:{get_docker_tag()}"
|
||||
self.docker_client.images.pull(branch_image)
|
||||
return branch_image
|
||||
except DockerException:
|
||||
self.docker_client.images.pull(image)
|
||||
return image
|
||||
|
||||
def run_container(self, **specs: Any) -> Container:
|
||||
if "network_mode" not in specs:
|
||||
specs["network"] = self.__network.name
|
||||
specs["labels"] = self.docker_labels
|
||||
specs["detach"] = True
|
||||
if hasattr(self, "live_server_url"):
|
||||
specs.setdefault("environment", {})
|
||||
specs["environment"]["AUTHENTIK_HOST"] = self.live_server_url
|
||||
container: Container = self.docker_client.containers.run(**specs)
|
||||
container.reload()
|
||||
state = container.attrs.get("State", {})
|
||||
if "Health" not in state:
|
||||
return container
|
||||
self.wait_for_container(container)
|
||||
return container
|
||||
|
||||
def output_container_logs(self, container: Container | None = None) -> None:
|
||||
"""Output the container logs to our STDOUT"""
|
||||
if not container:
|
||||
return
|
||||
if IS_CI:
|
||||
image = container.image
|
||||
if image:
|
||||
tags = image.tags[0] if len(image.tags) > 0 else str(image)
|
||||
print(f"::group::Container logs - {tags}")
|
||||
for log in container.logs().decode().split("\n"):
|
||||
print(log)
|
||||
if IS_CI:
|
||||
print("::endgroup::")
|
||||
|
||||
def tearDown(self) -> None:
|
||||
containers: list[Container] = self.docker_client.containers.list(
|
||||
filters={"label": ",".join(f"{x}={y}" for x, y in self.docker_labels.items())}
|
||||
)
|
||||
for container in containers:
|
||||
self.output_container_logs(container)
|
||||
try:
|
||||
container.kill()
|
||||
except DockerException:
|
||||
pass
|
||||
try:
|
||||
container.remove(force=True)
|
||||
except DockerException:
|
||||
pass
|
||||
self.__network.remove()
|
||||
@@ -1,6 +1,7 @@
|
||||
"""test OAuth2 OpenID Provider flow"""
|
||||
|
||||
from time import sleep
|
||||
from unittest import skip
|
||||
|
||||
from docker.types import Healthcheck
|
||||
from selenium.webdriver.common.by import By
|
||||
@@ -415,6 +416,7 @@ class TestProviderOAuth2OAuth(SeleniumTestCase):
|
||||
"Permission denied",
|
||||
)
|
||||
|
||||
@skip("Flaky test")
|
||||
@retry()
|
||||
@apply_blueprint(
|
||||
"default/flow-default-authentication-flow.yaml",
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
from json import dumps
|
||||
from time import sleep
|
||||
from unittest import skip
|
||||
|
||||
from selenium.webdriver.common.by import By
|
||||
from selenium.webdriver.support import expected_conditions as ec
|
||||
@@ -582,6 +583,7 @@ class TestProviderSAML(SeleniumTestCase):
|
||||
f"URL {self.driver.current_url} doesn't match expected URL {should_url}",
|
||||
)
|
||||
|
||||
@skip("Flaky test")
|
||||
@retry()
|
||||
@apply_blueprint(
|
||||
"default/flow-default-authentication-flow.yaml",
|
||||
|
||||
+16
-104
@@ -10,7 +10,6 @@ from sys import stderr
|
||||
from tempfile import gettempdir
|
||||
from time import sleep
|
||||
from typing import Any
|
||||
from unittest.case import TestCase
|
||||
from urllib.parse import urlencode
|
||||
|
||||
from django.apps import apps
|
||||
@@ -19,10 +18,8 @@ from django.db import connection
|
||||
from django.db.migrations.loader import MigrationLoader
|
||||
from django.test.testcases import TransactionTestCase
|
||||
from django.urls import reverse
|
||||
from docker import DockerClient, from_env
|
||||
from docker.errors import DockerException
|
||||
from docker.models.containers import Container
|
||||
from docker.models.networks import Network
|
||||
from dramatiq import get_broker
|
||||
from requests import RequestException
|
||||
from selenium import webdriver
|
||||
from selenium.common.exceptions import (
|
||||
@@ -45,9 +42,9 @@ from structlog.stdlib import get_logger
|
||||
from authentik.core.api.users import UserSerializer
|
||||
from authentik.core.models import User
|
||||
from authentik.core.tests.utils import create_test_admin_user
|
||||
from authentik.lib.generators import generate_id
|
||||
from authentik.lib.utils.http import get_http_session
|
||||
from authentik.root.test_runner import get_docker_tag
|
||||
from authentik.tasks.test import use_test_broker
|
||||
from tests.docker import DockerTestCase
|
||||
|
||||
IS_CI = "CI" in environ
|
||||
RETRIES = int(environ.get("RETRIES", "3")) if IS_CI else 1
|
||||
@@ -56,114 +53,18 @@ SHADOW_ROOT_RETRIES = 5
|
||||
JSONType = dict[str, Any] | list[Any] | str | int | float | bool | None
|
||||
|
||||
|
||||
def get_local_ip() -> str:
|
||||
def get_local_ip(override=True) -> str:
|
||||
"""Get the local machine's IP"""
|
||||
if local_ip := getenv("LOCAL_IP"):
|
||||
if (local_ip := getenv("LOCAL_IP")) and override:
|
||||
return local_ip
|
||||
hostname = socket.gethostname()
|
||||
ip_addr = socket.gethostbyname(hostname)
|
||||
return ip_addr
|
||||
|
||||
|
||||
class DockerTestCase(TestCase):
|
||||
"""Mixin for dealing with containers"""
|
||||
|
||||
max_healthcheck_attempts = 45
|
||||
|
||||
__client: DockerClient
|
||||
__network: Network
|
||||
|
||||
__label_id = generate_id()
|
||||
|
||||
def setUp(self) -> None:
|
||||
self.__client = from_env()
|
||||
self.__network = self.docker_client.networks.create(name=f"authentik-test-{generate_id()}")
|
||||
|
||||
@property
|
||||
def docker_client(self) -> DockerClient:
|
||||
return self.__client
|
||||
|
||||
@property
|
||||
def docker_network(self) -> Network:
|
||||
return self.__network
|
||||
|
||||
@property
|
||||
def docker_labels(self) -> dict:
|
||||
return {"io.goauthentik.test": self.__label_id}
|
||||
|
||||
def wait_for_container(self, container: Container):
|
||||
"""Check that container is health"""
|
||||
attempt = 0
|
||||
while True:
|
||||
container.reload()
|
||||
status = container.attrs.get("State", {}).get("Health", {}).get("Status")
|
||||
if status == "healthy":
|
||||
return container
|
||||
sleep(1)
|
||||
attempt += 1
|
||||
if attempt >= self.max_healthcheck_attempts:
|
||||
self.output_container_logs(container)
|
||||
raise self.failureException("Container failed to start")
|
||||
|
||||
def get_container_image(self, base: str) -> str:
|
||||
"""Try to pull docker image based on git branch, fallback to main if not found."""
|
||||
image = f"{base}:gh-main"
|
||||
try:
|
||||
branch_image = f"{base}:{get_docker_tag()}"
|
||||
self.docker_client.images.pull(branch_image)
|
||||
return branch_image
|
||||
except DockerException:
|
||||
self.docker_client.images.pull(image)
|
||||
return image
|
||||
|
||||
def run_container(self, **specs: dict[str, Any]) -> Container:
|
||||
if "network_mode" not in specs:
|
||||
specs["network"] = self.__network.name
|
||||
specs["labels"] = self.docker_labels
|
||||
specs["detach"] = True
|
||||
if hasattr(self, "live_server_url"):
|
||||
specs.setdefault("environment", {})
|
||||
specs["environment"]["AUTHENTIK_HOST"] = self.live_server_url
|
||||
container = self.docker_client.containers.run(**specs)
|
||||
container.reload()
|
||||
state = container.attrs.get("State", {})
|
||||
if "Health" not in state:
|
||||
return container
|
||||
self.wait_for_container(container)
|
||||
return container
|
||||
|
||||
def output_container_logs(self, container: Container | None = None):
|
||||
"""Output the container logs to our STDOUT"""
|
||||
if IS_CI:
|
||||
image = container.image
|
||||
tags = image.tags[0] if len(image.tags) > 0 else str(image)
|
||||
print(f"::group::Container logs - {tags}")
|
||||
for log in container.logs().decode().split("\n"):
|
||||
print(log)
|
||||
if IS_CI:
|
||||
print("::endgroup::")
|
||||
|
||||
def tearDown(self):
|
||||
containers: list[Container] = self.docker_client.containers.list(
|
||||
filters={"label": ",".join(f"{x}={y}" for x, y in self.docker_labels.items())}
|
||||
)
|
||||
for container in containers:
|
||||
self.output_container_logs(container)
|
||||
try:
|
||||
container.kill()
|
||||
except DockerException:
|
||||
pass
|
||||
try:
|
||||
container.remove(force=True)
|
||||
except DockerException:
|
||||
pass
|
||||
self.__network.remove()
|
||||
|
||||
|
||||
class SeleniumTestCase(DockerTestCase, StaticLiveServerTestCase):
|
||||
"""StaticLiveServerTestCase which automatically creates a Webdriver instance"""
|
||||
|
||||
serialized_rollback = True
|
||||
host = get_local_ip()
|
||||
wait_timeout: int
|
||||
user: User
|
||||
@@ -232,6 +133,17 @@ class SeleniumTestCase(DockerTestCase, StaticLiveServerTestCase):
|
||||
def driver_container(self) -> Container:
|
||||
return self.docker_client.containers.list(filters={"label": "io.goauthentik.tests"})[0]
|
||||
|
||||
@classmethod
|
||||
def _pre_setup(cls):
|
||||
use_test_broker()
|
||||
return super()._pre_setup()
|
||||
|
||||
def _post_teardown(self):
|
||||
broker = get_broker()
|
||||
broker.flush_all()
|
||||
broker.close()
|
||||
return super()._post_teardown()
|
||||
|
||||
def tearDown(self):
|
||||
if IS_CI:
|
||||
print("::endgroup::", file=stderr)
|
||||
|
||||
@@ -20,7 +20,8 @@ from authentik.outposts.models import (
|
||||
)
|
||||
from authentik.outposts.tasks import outpost_connection_discovery
|
||||
from authentik.providers.proxy.models import ProxyProvider
|
||||
from tests.e2e.utils import DockerTestCase, get_docker_tag
|
||||
from authentik.root.test_runner import get_docker_tag
|
||||
from tests.docker import DockerTestCase
|
||||
|
||||
|
||||
class OutpostDockerTests(DockerTestCase, ChannelsLiveServerTestCase):
|
||||
@@ -88,7 +89,7 @@ class OutpostDockerTests(DockerTestCase, ChannelsLiveServerTestCase):
|
||||
except PermissionError:
|
||||
pass
|
||||
|
||||
@pytest.mark.timeout(120)
|
||||
@pytest.mark.timeout(120, func_only=True)
|
||||
@CONFIG.patch("outposts.container_image_base", "ghcr.io/goauthentik/dev-proxy:gh-main")
|
||||
def test_docker_controller(self):
|
||||
"""test that deployment requires update"""
|
||||
@@ -96,7 +97,7 @@ class OutpostDockerTests(DockerTestCase, ChannelsLiveServerTestCase):
|
||||
controller.up()
|
||||
controller.down()
|
||||
|
||||
@pytest.mark.timeout(120)
|
||||
@pytest.mark.timeout(120, func_only=True)
|
||||
def test_docker_static(self):
|
||||
"""test that deployment requires update"""
|
||||
controller = DockerController(self.outpost, self.service_connection)
|
||||
|
||||
@@ -53,7 +53,7 @@ class OutpostKubernetesTests(TestCase):
|
||||
self.outpost.providers.add(self.provider)
|
||||
self.outpost.save()
|
||||
|
||||
@pytest.mark.timeout(120)
|
||||
@pytest.mark.timeout(120, func_only=True)
|
||||
def test_deployment_reconciler(self):
|
||||
"""test that deployment requires update"""
|
||||
controller = ProxyKubernetesController(self.outpost, self.service_connection)
|
||||
@@ -92,7 +92,7 @@ class OutpostKubernetesTests(TestCase):
|
||||
|
||||
deployment_reconciler.delete(deployment_reconciler.get_reference_object())
|
||||
|
||||
@pytest.mark.timeout(120)
|
||||
@pytest.mark.timeout(120, func_only=True)
|
||||
def test_service_reconciler(self):
|
||||
"""test that service requires update"""
|
||||
controller = ProxyKubernetesController(self.outpost, self.service_connection)
|
||||
@@ -121,7 +121,7 @@ class OutpostKubernetesTests(TestCase):
|
||||
|
||||
service_reconciler.delete(service_reconciler.get_reference_object())
|
||||
|
||||
@pytest.mark.timeout(120)
|
||||
@pytest.mark.timeout(120, func_only=True)
|
||||
def test_controller_rename(self):
|
||||
"""test that objects get deleted and re-created with new names"""
|
||||
controller = ProxyKubernetesController(self.outpost, self.service_connection)
|
||||
@@ -134,7 +134,7 @@ class OutpostKubernetesTests(TestCase):
|
||||
apps.read_namespaced_deployment("test", self.outpost.config.kubernetes_namespace)
|
||||
controller.down()
|
||||
|
||||
@pytest.mark.timeout(120)
|
||||
@pytest.mark.timeout(120, func_only=True)
|
||||
def test_controller_full_update(self):
|
||||
"""Test an update that triggers all objects"""
|
||||
controller = ProxyKubernetesController(self.outpost, self.service_connection)
|
||||
|
||||
@@ -20,7 +20,8 @@ from authentik.outposts.models import (
|
||||
from authentik.outposts.tasks import outpost_connection_discovery
|
||||
from authentik.providers.proxy.controllers.docker import DockerController
|
||||
from authentik.providers.proxy.models import ProxyProvider
|
||||
from tests.e2e.utils import DockerTestCase, get_docker_tag
|
||||
from authentik.root.test_runner import get_docker_tag
|
||||
from tests.docker import DockerTestCase
|
||||
|
||||
|
||||
class TestProxyDocker(DockerTestCase, ChannelsLiveServerTestCase):
|
||||
@@ -88,7 +89,7 @@ class TestProxyDocker(DockerTestCase, ChannelsLiveServerTestCase):
|
||||
except PermissionError:
|
||||
pass
|
||||
|
||||
@pytest.mark.timeout(120)
|
||||
@pytest.mark.timeout(120, func_only=True)
|
||||
@CONFIG.patch("outposts.container_image_base", "ghcr.io/goauthentik/dev-proxy:gh-main")
|
||||
def test_docker_controller(self):
|
||||
"""test that deployment requires update"""
|
||||
@@ -96,7 +97,7 @@ class TestProxyDocker(DockerTestCase, ChannelsLiveServerTestCase):
|
||||
controller.up()
|
||||
controller.down()
|
||||
|
||||
@pytest.mark.timeout(120)
|
||||
@pytest.mark.timeout(120, func_only=True)
|
||||
def test_docker_static(self):
|
||||
"""test that deployment requires update"""
|
||||
controller = DockerController(self.outpost, self.service_connection)
|
||||
|
||||
@@ -26,7 +26,7 @@ class TestProxyKubernetes(TestCase):
|
||||
outpost_connection_discovery.send()
|
||||
self.controller = None
|
||||
|
||||
@pytest.mark.timeout(120)
|
||||
@pytest.mark.timeout(120, func_only=True)
|
||||
def test_kubernetes_controller_static(self):
|
||||
"""Test Kubernetes Controller"""
|
||||
provider: ProxyProvider = ProxyProvider.objects.create(
|
||||
@@ -48,7 +48,7 @@ class TestProxyKubernetes(TestCase):
|
||||
manifest = self.controller.get_static_deployment()
|
||||
self.assertEqual(len(list(yaml.load_all(manifest, Loader=yaml.SafeLoader))), 4)
|
||||
|
||||
@pytest.mark.timeout(120)
|
||||
@pytest.mark.timeout(120, func_only=True)
|
||||
def test_kubernetes_controller_ingress(self):
|
||||
"""Test Kubernetes Controller's Ingress"""
|
||||
provider: ProxyProvider = ProxyProvider.objects.create(
|
||||
|
||||
@@ -308,6 +308,7 @@ dev = [
|
||||
{ name = "pdoc" },
|
||||
{ name = "pytest" },
|
||||
{ name = "pytest-django" },
|
||||
{ name = "pytest-flakefinder" },
|
||||
{ name = "pytest-github-actions-annotate-failures" },
|
||||
{ name = "pytest-randomly" },
|
||||
{ name = "pytest-timeout" },
|
||||
@@ -315,7 +316,11 @@ dev = [
|
||||
{ name = "ruff" },
|
||||
{ name = "selenium" },
|
||||
{ name = "types-channels" },
|
||||
{ name = "types-docker" },
|
||||
{ name = "types-jwcrypto" },
|
||||
{ name = "types-ldap3" },
|
||||
{ name = "types-requests" },
|
||||
{ name = "types-zxcvbn" },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
@@ -413,6 +418,7 @@ dev = [
|
||||
{ name = "pdoc", specifier = "==16.0.0" },
|
||||
{ name = "pytest", specifier = "==9.0.2" },
|
||||
{ name = "pytest-django", specifier = "==4.11.1" },
|
||||
{ name = "pytest-flakefinder", specifier = "==1.1.0" },
|
||||
{ name = "pytest-github-actions-annotate-failures", specifier = "==0.3.0" },
|
||||
{ name = "pytest-randomly", specifier = "==4.0.1" },
|
||||
{ name = "pytest-timeout", specifier = "==2.4.0" },
|
||||
@@ -420,7 +426,11 @@ dev = [
|
||||
{ name = "ruff", specifier = "==0.14.13" },
|
||||
{ name = "selenium", specifier = "==4.39.0" },
|
||||
{ name = "types-channels", specifier = "==4.3.0.20250822" },
|
||||
{ name = "types-docker", specifier = "==7.1.0.20260109" },
|
||||
{ name = "types-jwcrypto", specifier = "==1.5.0.20251102" },
|
||||
{ name = "types-ldap3", specifier = "==2.9.13.20251121" },
|
||||
{ name = "types-requests", specifier = "==2.32.4.20260107" },
|
||||
{ name = "types-zxcvbn", specifier = "==4.5.0.20250809" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3029,6 +3039,18 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/be/ac/bd0608d229ec808e51a21044f3f2f27b9a37e7a0ebaca7247882e67876af/pytest_django-4.11.1-py3-none-any.whl", hash = "sha256:1b63773f648aa3d8541000c26929c1ea63934be1cfa674c76436966d73fe6a10", size = 25281, upload-time = "2025-04-03T18:56:07.678Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pytest-flakefinder"
|
||||
version = "1.1.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "pytest" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/ec/53/69c56a93ea057895b5761c5318455804873a6cd9d796d7c55d41c2358125/pytest-flakefinder-1.1.0.tar.gz", hash = "sha256:e2412a1920bdb8e7908783b20b3d57e9dad590cc39a93e8596ffdd493b403e0e", size = 6795, upload-time = "2022-10-26T18:27:54.243Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/33/8b/06787150d0fd0cbd3a8054262b56f91631c7778c1bc91bf4637e47f909ad/pytest_flakefinder-1.1.0-py2.py3-none-any.whl", hash = "sha256:741e0e8eea427052f5b8c89c2b3c3019a50c39a59ce4df6a305a2c2d9ba2bd13", size = 4644, upload-time = "2022-10-26T18:27:52.128Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pytest-github-actions-annotate-failures"
|
||||
version = "0.3.0"
|
||||
@@ -3601,6 +3623,32 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/cb/52/4e3094e43d460feacb9051ec4c3498f8272f69d92b772647211478b25079/types_channels-4.3.0.20250822-py3-none-any.whl", hash = "sha256:d3fc0a1467c8cc901686826408c8a673822e07aa79cbe1a6d21946e7e55d9ddf", size = 21125, upload-time = "2025-08-22T03:04:25.539Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "types-docker"
|
||||
version = "7.1.0.20260109"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "types-paramiko" },
|
||||
{ name = "types-requests" },
|
||||
{ name = "urllib3" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/54/08/ffef2a8e29e9e22c724f9c1b22563c0938c3ab3fa728ff5b966465e12b93/types_docker-7.1.0.20260109.tar.gz", hash = "sha256:b36ef355ec9ba8bf29bcc4e32cc61dd9138ce4d8352c599c8fbc65f1a3e87b57", size = 32551, upload-time = "2026-01-09T03:21:49.238Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/8f/0d/cdf37dcd0cd4c942a1634daf3ae3a99833791c7a316bff4d4ce04a30652e/types_docker-7.1.0.20260109-py3-none-any.whl", hash = "sha256:001a5a377d3fb287b7279cf4265b8ba3857e7d4203a16ab03e6e512f68f2f3d4", size = 47216, upload-time = "2026-01-09T03:21:48.059Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "types-jwcrypto"
|
||||
version = "1.5.0.20251102"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "cryptography" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/08/a7/103a4b02c6fb8718994252d5840b11d770f090d4100aa460194cc009bc62/types_jwcrypto-1.5.0.20251102.tar.gz", hash = "sha256:c3b93a85d130a1c16999d2a3c435e5bd6a9b394754239190c5fe49cedcc0a98f", size = 11637, upload-time = "2025-11-02T03:07:38.388Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/cf/15/e305c0ae6aaca68842c73fee5080eadd51a702a1562f05d9653d6e9b5a94/types_jwcrypto-1.5.0.20251102-py3-none-any.whl", hash = "sha256:506c93a09c6a988fc5a56bfe92f0cf80b31a0acee98bd6e807277bb0c6f8c1d0", size = 12978, upload-time = "2025-11-02T03:07:37.398Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "types-ldap3"
|
||||
version = "2.9.13.20251121"
|
||||
@@ -3613,6 +3661,18 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/a9/28/05989820ae4694b15acf9fe26c73901fda117d99720954e969b5ec498399/types_ldap3-2.9.13.20251121-py3-none-any.whl", hash = "sha256:20356bf413cb178898f5b171463b44b82044b8b69f9331e09950009cfef05e48", size = 56808, upload-time = "2025-11-21T03:03:41.926Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "types-paramiko"
|
||||
version = "4.0.0.20250822"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "cryptography" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b7/b8/c6ff3b10c2f7b9897650af746f0dc6c5cddf054db857bc79d621f53c7d22/types_paramiko-4.0.0.20250822.tar.gz", hash = "sha256:1b56b0cbd3eec3d2fd123c9eb2704e612b777e15a17705a804279ea6525e0c53", size = 28730, upload-time = "2025-08-22T03:03:43.262Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/79/a1/b3774ed924a66ee2c041224d89c36f0c21f4f6cf75036d6ee7698bf8a4b9/types_paramiko-4.0.0.20250822-py3-none-any.whl", hash = "sha256:55bdb14db75ca89039725ec64ae3fa26b8d57b6991cfb476212fa8f83a59753c", size = 38833, upload-time = "2025-08-22T03:03:42.072Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "types-pyasn1"
|
||||
version = "0.6.0.20250914"
|
||||
@@ -3643,6 +3703,15 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/1c/12/709ea261f2bf91ef0a26a9eed20f2623227a8ed85610c1e54c5805692ecb/types_requests-2.32.4.20260107-py3-none-any.whl", hash = "sha256:b703fe72f8ce5b31ef031264fe9395cac8f46a04661a79f7ed31a80fb308730d", size = 20676, upload-time = "2026-01-07T03:20:52.929Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "types-zxcvbn"
|
||||
version = "4.5.0.20250809"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/6b/27/70ca6f3f295c48f87495e283061e60d53a814853bd213f24125cf4192cad/types_zxcvbn-4.5.0.20250809.tar.gz", hash = "sha256:da19c7c416ad26ecb934110260375e687f37f1ed897522214d97ca2e9ccb2de5", size = 9389, upload-time = "2025-08-09T03:15:01.058Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/6b/e0/0af4e865c28da104fa6ec45a2a336521fe34cbe606ef2ab6984b14d96e2c/types_zxcvbn-4.5.0.20250809-py3-none-any.whl", hash = "sha256:2cd151a5b35a976ae22017b5caed8f99d5e1be455cf9f9497cd86419073bc9cb", size = 10821, upload-time = "2025-08-09T03:15:00.299Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typing-extensions"
|
||||
version = "4.15.0"
|
||||
|
||||
Reference in New Issue
Block a user