Skip to content

Commit a6775bc

Browse files
authored
tests: refactor test harness to split apart a single file (#21391)
* re-instate previously flaky test Signed-off-by: Jens Langhammer <jens@goauthentik.io> * break up big file Signed-off-by: Jens Langhammer <jens@goauthentik.io> * move geoip data to subdir Signed-off-by: Jens Langhammer <jens@goauthentik.io> * i am but a weak man Signed-off-by: Jens Langhammer <jens@goauthentik.io> * fix ldap disconnect in testing Signed-off-by: Jens Langhammer <jens@goauthentik.io> * account for mismatched uid due to test server process Signed-off-by: Jens Langhammer <jens@goauthentik.io> --------- Signed-off-by: Jens Langhammer <jens@goauthentik.io>
1 parent debd091 commit a6775bc

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+295
-201
lines changed

Makefile

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -144,8 +144,14 @@ dev-create-db:
144144
dev-reset: dev-drop-db dev-create-db migrate ## Drop and restore the Authentik PostgreSQL instance to a "fresh install" state.
145145

146146
update-test-mmdb: ## Update test GeoIP and ASN Databases
147-
curl -L https://raw.githubusercontent.com/maxmind/MaxMind-DB/refs/heads/main/test-data/GeoLite2-ASN-Test.mmdb -o ${PWD}/tests/GeoLite2-ASN-Test.mmdb
148-
curl -L https://raw.githubusercontent.com/maxmind/MaxMind-DB/refs/heads/main/test-data/GeoLite2-City-Test.mmdb -o ${PWD}/tests/GeoLite2-City-Test.mmdb
147+
curl \
148+
-L \
149+
-o ${PWD}/tests/geoip/GeoLite2-ASN-Test.mmdb \
150+
https://raw.githubusercontent.com/maxmind/MaxMind-DB/refs/heads/main/test-data/GeoLite2-ASN-Test.mmdb
151+
curl \
152+
-L \
153+
-o ${PWD}/tests/geoip/GeoLite2-City-Test.mmdb \
154+
https://raw.githubusercontent.com/maxmind/MaxMind-DB/refs/heads/main/test-data/GeoLite2-City-Test.mmdb
149155

150156
bump: ## Bump authentik version. Usage: make bump version=20xx.xx.xx
151157
ifndef version

authentik/root/test_plugin.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from cryptography.hazmat.backends.openssl.backend import backend
77

88
from authentik import authentik_full_version
9-
from tests.e2e.utils import get_local_ip
9+
from tests.decorators import get_local_ip
1010

1111
IS_CI = "CI" in environ
1212

authentik/root/test_runner.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,8 +69,8 @@ def _setup_test_environment(self):
6969

7070
# Test-specific configuration
7171
test_config = {
72-
"events.context_processors.geoip": "tests/GeoLite2-City-Test.mmdb",
73-
"events.context_processors.asn": "tests/GeoLite2-ASN-Test.mmdb",
72+
"events.context_processors.geoip": "tests/geoip/GeoLite2-City-Test.mmdb",
73+
"events.context_processors.asn": "tests/geoip/GeoLite2-ASN-Test.mmdb",
7474
"blueprints_dir": "./blueprints",
7575
"outposts.container_image_base": f"ghcr.io/goauthentik/dev-%(type)s:{get_docker_tag()}",
7676
"tenants.enabled": False,

internal/outpost/flow/session.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,14 @@ func (fe *FlowExecutor) Session() *jwt.Token {
1515
return nil
1616
}
1717
t, _, _ := jwt.NewParser().ParseUnverified(sc.Value, &SessionCookieClaims{})
18+
// During testing the session cookie value is not a JWT but rather just the session ID
19+
// in which case we wrap that in a pseudo-JWT
20+
if t == nil {
21+
return &jwt.Token{
22+
Claims: &SessionCookieClaims{
23+
SessionID: sc.Value,
24+
},
25+
}
26+
}
1827
return t
1928
}

internal/outpost/ldap/ldap.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@ func (ls *LDAPServer) handleWSSessionEnd(ctx context.Context, msg ak.Event) erro
149149
ls.log.Info("Disconnecting session due to session end event")
150150
conn, ok := ls.connections[mmsg.SessionID]
151151
if !ok {
152+
ls.log.Warn("Could not disconnect session, connection not found")
152153
return nil
153154
}
154155
delete(ls.connections, mmsg.SessionID)

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,7 @@ module = [
256256
"authentik.tenants.*",
257257
"guardian.*",
258258
"lifecycle.*",
259+
"tests.*",
259260
"tests.e2e.*",
260261
"tests.integration.*",
261262
"tests.openid_conformance.*",

scripts/generate_config.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@ def generate_local_config() -> dict[str, Any]:
2727
"cert_discovery_dir": "./certs",
2828
"events": {
2929
"processors": {
30-
"geoip": "tests/GeoLite2-City-Test.mmdb",
31-
"asn": "tests/GeoLite2-ASN-Test.mmdb",
30+
"geoip": "tests/geoip/GeoLite2-City-Test.mmdb",
31+
"asn": "tests/geoip/GeoLite2-ASN-Test.mmdb",
3232
}
3333
},
3434
"storage": {
File renamed without changes.

tests/decorators.py

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import socket
2+
from collections.abc import Callable
3+
from functools import lru_cache, wraps
4+
from os import environ, getenv
5+
from typing import Any
6+
7+
from django.db import connection
8+
from django.db.migrations.loader import MigrationLoader
9+
from django.test.testcases import TransactionTestCase
10+
from selenium.common.exceptions import (
11+
NoSuchElementException,
12+
TimeoutException,
13+
WebDriverException,
14+
)
15+
from structlog.stdlib import get_logger
16+
17+
IS_CI = "CI" in environ
18+
RETRIES = int(environ.get("RETRIES", "3")) if IS_CI else 1
19+
SHADOW_ROOT_RETRIES = 5
20+
21+
JSONType = dict[str, Any] | list[Any] | str | int | float | bool | None
22+
23+
24+
def get_local_ip(override=True) -> str:
25+
"""Get the local machine's IP"""
26+
if (local_ip := getenv("LOCAL_IP")) and override:
27+
return local_ip
28+
hostname = socket.gethostname()
29+
try:
30+
return socket.gethostbyname(hostname)
31+
except socket.gaierror:
32+
return "0.0.0.0"
33+
34+
35+
@lru_cache
36+
def get_loader():
37+
"""Thin wrapper to lazily get a Migration Loader, only when it's needed
38+
and only once"""
39+
return MigrationLoader(connection)
40+
41+
42+
def retry(max_retires=RETRIES, exceptions=None):
43+
"""Retry test multiple times. Default to catching Selenium Timeout Exception"""
44+
45+
if not exceptions:
46+
exceptions = [WebDriverException, TimeoutException, NoSuchElementException]
47+
48+
logger = get_logger()
49+
50+
def retry_actual(func: Callable):
51+
"""Retry test multiple times"""
52+
count = 1
53+
54+
@wraps(func)
55+
def wrapper(self: TransactionTestCase, *args, **kwargs):
56+
"""Run test again if we're below max_retries, including tearDown and
57+
setUp. Otherwise raise the error"""
58+
nonlocal count
59+
try:
60+
return func(self, *args, **kwargs)
61+
62+
except tuple(exceptions) as exc:
63+
count += 1
64+
if count > max_retires:
65+
logger.debug("Exceeded retry count", exc=exc, test=self)
66+
67+
raise exc
68+
logger.debug("Retrying on error", exc=exc, test=self)
69+
self.tearDown()
70+
self._post_teardown()
71+
self._pre_setup()
72+
self.setUp()
73+
return wrapper(self, *args, **kwargs)
74+
75+
return wrapper
76+
77+
return retry_actual

tests/docker.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
"""authentik e2e testing utilities"""
22

3-
from os import environ
43
from time import sleep
54
from typing import Any
65
from unittest.case import TestCase
@@ -13,8 +12,6 @@
1312
from authentik.lib.generators import generate_id
1413
from authentik.root.test_runner import get_docker_tag
1514

16-
IS_CI = "CI" in environ
17-
1815

1916
class DockerTestCase(TestCase):
2017
"""Mixin for dealing with containers"""

0 commit comments

Comments
 (0)