Skip to content

The Coup™#18122

Draft
rissson wants to merge 178 commits intomainfrom
rust-server
Draft

The Coup™#18122
rissson wants to merge 178 commits intomainfrom
rust-server

Conversation

@rissson
Copy link
Member

@rissson rissson commented Nov 13, 2025

Ready for review:

  • core server functionality
  • static files (mising etag, push welcomed for that)
  • proxy protocol
  • subpath
  • tls
  • tls with client certs, for mtls stage
  • source IP and other data (scheme, host, etc.)
  • config loading
  • logging & observability setup (sentry, etc.)
  • metrics
  • worker functionality
  • allinone functionality
  • needs testing and reporting for error handling:
    • to test:
      • what if a long running task (managed by arbiter::Tasks) panics
      • what if a request returns Result::Err
      • what if a request panics
      • what if a "short" running task (started with tokio::task::spawn, like websocket connections) panics?
    • how to test:
      • modify some part of the code to make do what you want to test
      • run it
      • document what happens (probably in a comment here)
      • is it what you expect to happen?

Notes to reviewer:

  • some stuff in the go code is currently broken, specifically because of config changes, it will get fixed at a later time, ignore it for now
  • the python code changes should be reviewed as well, and should be working

Not ready for review:

  • the actual forwarding code for core. The code is currently very oriented for our use case, but should be made more generic for use in the proxy outpost, so this will change quite a bit again, but the functionality should be the same
  • anything proxy outpost related

How to test:

  • install rust (see rust-toolchain.toml for version needed)
  • cargo build and grab a coffee for the first build
  • install watchexec for reloading
  • make run or make run-watch
  • cargo t to run tests
  • cargo clippy for linting
  • cargo +nightly fmt for auto formatting
  • cargo install cargo-deny && cargo deny check for licensing and denied features checks (for instance we ban ring and force aws-lc-rs with FIPS for compliance) (currently fails on not allowed git dependencies, needs the config to be updated)
  • cargo install cargo-machete && cargo machete to check for unused dependencies

TODO:

  • etag for static files
  • ci (lint, test, build)
  • proxy outpost (ez)
  • http compression
  • fix outposts healthchecks

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
@netlify
Copy link

netlify bot commented Nov 13, 2025

Deploy Preview for authentik-storybook ready!

Name Link
🔨 Latest commit f3341a4
🔍 Latest deploy log https://app.netlify.com/projects/authentik-storybook/deploys/69bc36910d9a0e0008bda351
😎 Deploy Preview https://deploy-preview-18122--authentik-storybook.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@netlify
Copy link

netlify bot commented Nov 13, 2025

Deploy Preview for authentik-docs ready!

Name Link
🔨 Latest commit f3341a4
🔍 Latest deploy log https://app.netlify.com/projects/authentik-docs/deploys/69bc36910041430008c799d4
😎 Deploy Preview https://deploy-preview-18122--authentik-docs.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@netlify
Copy link

netlify bot commented Nov 13, 2025

Deploy Preview for authentik-integrations ready!

Name Link
🔨 Latest commit f3341a4
🔍 Latest deploy log https://app.netlify.com/projects/authentik-integrations/deploys/69bc36916bcee800081f2d15
😎 Deploy Preview https://deploy-preview-18122--authentik-integrations.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@rissson
Copy link
Member Author

rissson commented Nov 13, 2025

Memory usage findings for the worker:
1 process, 2 threads:
Python: 461MB, Rust: 242MB
2 process, 2 threads:
Python: 685MB, Rust: 466MB
Which is logical, since we have one less process in there

Current annoyances: logs from rust don't work (cause it isn't setup). Double ctrl+c doesn't fast exit the worker (we should probably just reduce the timeout in dev). Also, healthchecks aren't doing anything, nor the worker_status, but metrics work! Also, config is hardcoded. Also, no sentry and things.

@codecov
Copy link

codecov bot commented Nov 13, 2025

Codecov Report

❌ Patch coverage is 71.42857% with 6 lines in your changes missing coverage. Please review.
✅ Project coverage is 83.24%. Comparing base (ef202f0) to head (2e04738).
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
authentik/root/tests/test_views.py 0.00% 2 Missing ⚠️
authentik/tasks/api/workers.py 60.00% 2 Missing ⚠️
authentik/root/middleware.py 50.00% 1 Missing ⚠️
authentik/root/monitoring.py 0.00% 1 Missing ⚠️

❗ There is a different number of reports uploaded between BASE (ef202f0) and HEAD (2e04738). Click for more details.

HEAD has 10 uploads less than BASE
Flag BASE (ef202f0) HEAD (2e04738)
unit 10 6
unit-migrate 10 4
Additional details and impacted files
@@             Coverage Diff             @@
##             main   #18122       +/-   ##
===========================================
- Coverage   93.45%   83.24%   -10.21%     
===========================================
  Files         991      991               
  Lines       55948    55831      -117     
===========================================
- Hits        52285    46477     -5808     
- Misses       3663     9354     +5691     
Flag Coverage Δ
conformance 37.42% <66.66%> (-0.10%) ⬇️
e2e 42.97% <66.66%> (-0.07%) ⬇️
integration 22.16% <52.38%> (-0.11%) ⬇️
unit 80.69% <71.42%> (-10.93%) ⬇️
unit-migrate 55.88% <66.66%> (-35.83%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
@rissson rissson moved this from Todo to In Progress in authentik Core Jan 13, 2026
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
rissson added 4 commits March 12, 2026 14:45
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
@codecov
Copy link

codecov bot commented Mar 13, 2026

❌ 7 Tests Failed:

Tests completed Failed Passed Skipped
3119 7 3112 2
View the top 3 failed test(s) by shortest run time
authentik.lib.tests.test_config.TestConfig::test_db_default
Stack Traces | 0.015s run time
self = <unittest.case._Outcome object at 0x7f28eb8b5080>
test_case = <authentik.lib.tests.test_config.TestConfig testMethod=test_db_default>
subTest = False

    @contextlib.contextmanager
    def testPartExecutor(self, test_case, subTest=False):
        old_success = self.success
        self.success = True
        try:
>           yield

.../hostedtoolcache/Python/3.14.3................../x64/lib/python3.14/unittest/case.py:58: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <authentik.lib.tests.test_config.TestConfig testMethod=test_db_default>
result = <TestCaseFunction test_db_default>

    def run(self, result=None):
        if result is None:
            result = self.defaultTestResult()
            startTestRun = getattr(result, 'startTestRun', None)
            stopTestRun = getattr(result, 'stopTestRun', None)
            if startTestRun is not None:
                startTestRun()
        else:
            stopTestRun = None
    
        result.startTest(self)
        try:
            testMethod = getattr(self, self._testMethodName)
            if (getattr(self.__class__, "__unittest_skip__", False) or
                getattr(testMethod, "__unittest_skip__", False)):
                # If the class or method was skipped.
                skip_why = (getattr(self.__class__, '__unittest_skip_why__', '')
                            or getattr(testMethod, '__unittest_skip_why__', ''))
                _addSkip(result, self, skip_why)
                return result
    
            expecting_failure = (
                getattr(self, "__unittest_expecting_failure__", False) or
                getattr(testMethod, "__unittest_expecting_failure__", False)
            )
            outcome = _Outcome(result)
            start_time = time.perf_counter()
            try:
                self._outcome = outcome
    
                with outcome.testPartExecutor(self):
                    self._callSetUp()
                if outcome.success:
                    outcome.expecting_failure = expecting_failure
                    with outcome.testPartExecutor(self):
>                       self._callTestMethod(testMethod)

.../hostedtoolcache/Python/3.14.3................../x64/lib/python3.14/unittest/case.py:669: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <authentik.lib.tests.test_config.TestConfig testMethod=test_db_default>
method = <bound method TestConfig.test_db_default of <authentik.lib.tests.test_config.TestConfig testMethod=test_db_default>>

    def _callTestMethod(self, method):
>       result = method()
                 ^^^^^^^^

.../hostedtoolcache/Python/3.14.3................../x64/lib/python3.14/unittest/case.py:615: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <authentik.lib.tests.test_config.TestConfig testMethod=test_db_default>

    def test_db_default(self):
        """Test default DB Config"""
        config = ConfigLoader()
        config.set("postgresql.host", "foo")
        config.set("postgresql.name", "foo")
        config.set("postgresql.user", "foo")
        config.set("postgresql.password", "foo")
        config.set("postgresql.port", "foo")
        config.set("postgresql.sslmode", "foo")
        config.set("postgresql.sslrootcert", "foo")
        config.set("postgresql.sslcert", "foo")
        config.set("postgresql.sslkey", "foo")
        config.set("postgresql.test.name", "foo")
        conf = django_db_config(config)
>       self.assertEqual(
            conf,
            {
                "default": {
                    "ENGINE": "psqlextra.backend",
                    "HOST": "foo",
                    "NAME": "foo",
                    "OPTIONS": {
                        "pool": False,
                        "sslcert": "foo",
                        "sslkey": "foo",
                        "sslmode": "foo",
                        "sslrootcert": "foo",
                    },
                    "PASSWORD": "foo",
                    "PORT": "foo",
                    "TEST": {"NAME": "foo"},
                    "USER": "foo",
                    "CONN_MAX_AGE": 0,
                    "CONN_HEALTH_CHECKS": False,
                    "DISABLE_SERVER_SIDE_CURSORS": False,
                }
            },
        )

.../lib/tests/test_config.py:197: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <authentik.lib.tests.test_config.TestConfig testMethod=test_db_default>
first = {'default': {'CONN_HEALTH_CHECKS': False, 'CONN_MAX_AGE': 60, 'DISABLE_SERVER_SIDE_CURSORS': False, 'ENGINE': 'psqlextra.backend', ...}}
second = {'default': {'CONN_HEALTH_CHECKS': False, 'CONN_MAX_AGE': 0, 'DISABLE_SERVER_SIDE_CURSORS': False, 'ENGINE': 'psqlextra.backend', ...}}
msg = None

    def assertEqual(self, first, second, msg=None):
        """Fail if the two objects are unequal as determined by the '=='
           operator.
        """
        assertion_func = self._getAssertEqualityFunc(first, second)
>       assertion_func(first, second, msg=msg)

.../hostedtoolcache/Python/3.14.3................../x64/lib/python3.14/unittest/case.py:925: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <authentik.lib.tests.test_config.TestConfig testMethod=test_db_default>
d1 = {'default': {'CONN_HEALTH_CHECKS': False, 'CONN_MAX_AGE': 60, 'DISABLE_SERVER_SIDE_CURSORS': False, 'ENGINE': 'psqlextra.backend', ...}}
d2 = {'default': {'CONN_HEALTH_CHECKS': False, 'CONN_MAX_AGE': 0, 'DISABLE_SERVER_SIDE_CURSORS': False, 'ENGINE': 'psqlextra.backend', ...}}
msg = None

    def assertDictEqual(self, d1, d2, msg=None):
        self.assertIsInstance(d1, dict, 'First argument is not a dictionary')
        self.assertIsInstance(d2, dict, 'Second argument is not a dictionary')
    
        if d1 != d2:
            standardMsg = '%s != %s' % _common_shorten_repr(d1, d2)
            diff = ('\n' + '\n'.join(difflib.ndiff(
                           pprint.pformat(d1).splitlines(),
                           pprint.pformat(d2).splitlines())))
            standardMsg = self._truncateMessage(standardMsg, diff)
>           self.fail(self._formatMessage(msg, standardMsg))

.../hostedtoolcache/Python/3.14.3................../x64/lib/python3.14/unittest/case.py:1224: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <authentik.lib.tests.test_config.TestConfig testMethod=test_db_default>
msg = "{'def[50 chars]o', 'PORT': 'foo', 'USER': 'foo', 'PASSWORD': [232 chars]o'}}} != {'def[50 chars]o', 'NAME': 'foo', 'O...SWORD': 'foo',\n               'PORT': 'foo',\n               'TEST': {'NAME': 'foo'},\n               'USER': 'foo'}}"

    def fail(self, msg=None):
        """Fail immediately, with the given message."""
>       raise self.failureException(msg)
E       AssertionError: {'def[50 chars]o', 'PORT': 'foo', 'USER': 'foo', 'PASSWORD': [232 chars]o'}}} != {'def[50 chars]o', 'NAME': 'foo', 'OPTIONS': {'pool': False, [231 chars]lse}}
E         {'default': {'CONN_HEALTH_CHECKS': False,
E       -              'CONN_MAX_AGE': 60,
E       ?                              -
E       
E       +              'CONN_MAX_AGE': 0,
E                      'DISABLE_SERVER_SIDE_CURSORS': False,
E                      'ENGINE': 'psqlextra.backend',
E                      'HOST': 'foo',
E                      'NAME': 'foo',
E                      'OPTIONS': {'pool': False,
E                                  'sslcert': 'foo',
E                                  'sslkey': 'foo',
E                                  'sslmode': 'foo',
E                                  'sslrootcert': 'foo'},
E                      'PASSWORD': 'foo',
E                      'PORT': 'foo',
E                      'TEST': {'NAME': 'foo'},
E                      'USER': 'foo'}}

.../hostedtoolcache/Python/3.14.3................../x64/lib/python3.14/unittest/case.py:750: AssertionError
authentik.lib.tests.test_config.TestConfig::test_db_read_replicas_diff_ssl
Stack Traces | 0.015s run time
self = <unittest.case._Outcome object at 0x7f28eb8b4520>
test_case = <authentik.lib.tests.test_config.TestConfig testMethod=test_db_read_replicas_diff_ssl>
subTest = False

    @contextlib.contextmanager
    def testPartExecutor(self, test_case, subTest=False):
        old_success = self.success
        self.success = True
        try:
>           yield

.../hostedtoolcache/Python/3.14.3................../x64/lib/python3.14/unittest/case.py:58: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <authentik.lib.tests.test_config.TestConfig testMethod=test_db_read_replicas_diff_ssl>
result = <TestCaseFunction test_db_read_replicas_diff_ssl>

    def run(self, result=None):
        if result is None:
            result = self.defaultTestResult()
            startTestRun = getattr(result, 'startTestRun', None)
            stopTestRun = getattr(result, 'stopTestRun', None)
            if startTestRun is not None:
                startTestRun()
        else:
            stopTestRun = None
    
        result.startTest(self)
        try:
            testMethod = getattr(self, self._testMethodName)
            if (getattr(self.__class__, "__unittest_skip__", False) or
                getattr(testMethod, "__unittest_skip__", False)):
                # If the class or method was skipped.
                skip_why = (getattr(self.__class__, '__unittest_skip_why__', '')
                            or getattr(testMethod, '__unittest_skip_why__', ''))
                _addSkip(result, self, skip_why)
                return result
    
            expecting_failure = (
                getattr(self, "__unittest_expecting_failure__", False) or
                getattr(testMethod, "__unittest_expecting_failure__", False)
            )
            outcome = _Outcome(result)
            start_time = time.perf_counter()
            try:
                self._outcome = outcome
    
                with outcome.testPartExecutor(self):
                    self._callSetUp()
                if outcome.success:
                    outcome.expecting_failure = expecting_failure
                    with outcome.testPartExecutor(self):
>                       self._callTestMethod(testMethod)

.../hostedtoolcache/Python/3.14.3................../x64/lib/python3.14/unittest/case.py:669: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <authentik.lib.tests.test_config.TestConfig testMethod=test_db_read_replicas_diff_ssl>
method = <bound method TestConfig.test_db_read_replicas_diff_ssl of <authentik.lib.tests.test_config.TestConfig testMethod=test_db_read_replicas_diff_ssl>>

    def _callTestMethod(self, method):
>       result = method()
                 ^^^^^^^^

.../hostedtoolcache/Python/3.14.3................../x64/lib/python3.14/unittest/case.py:615: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <authentik.lib.tests.test_config.TestConfig testMethod=test_db_read_replicas_diff_ssl>

    def test_db_read_replicas_diff_ssl(self):
        """Test read replicas (with different SSL Settings)"""
        """Test read replicas"""
        config = ConfigLoader()
        config.set("postgresql.host", "foo")
        config.set("postgresql.name", "foo")
        config.set("postgresql.user", "foo")
        config.set("postgresql.password", "foo")
        config.set("postgresql.port", "foo")
        config.set("postgresql.sslmode", "foo")
        config.set("postgresql.sslrootcert", "foo")
        config.set("postgresql.sslcert", "foo")
        config.set("postgresql.sslkey", "foo")
        config.set("postgresql.test.name", "foo")
        # Read replica
        config.set("postgresql.read_replicas.0.host", "bar")
        config.set("postgresql.read_replicas.0.sslcert", "bar")
        conf = django_db_config(config)
>       self.assertEqual(
            conf,
            {
                "default": {
                    "ENGINE": "psqlextra.backend",
                    "HOST": "foo",
                    "NAME": "foo",
                    "OPTIONS": {
                        "pool": False,
                        "sslcert": "foo",
                        "sslkey": "foo",
                        "sslmode": "foo",
                        "sslrootcert": "foo",
                    },
                    "PASSWORD": "foo",
                    "PORT": "foo",
                    "TEST": {"NAME": "foo"},
                    "USER": "foo",
                    "DISABLE_SERVER_SIDE_CURSORS": False,
                    "CONN_MAX_AGE": 0,
                    "CONN_HEALTH_CHECKS": False,
                },
                "replica_0": {
                    "ENGINE": "psqlextra.backend",
                    "HOST": "bar",
                    "NAME": "foo",
                    "OPTIONS": {
                        "pool": False,
                        "sslcert": "bar",
                        "sslkey": "foo",
                        "sslmode": "foo",
                        "sslrootcert": "foo",
                    },
                    "PASSWORD": "foo",
                    "PORT": "foo",
                    "TEST": {"NAME": "foo"},
                    "USER": "foo",
                    "DISABLE_SERVER_SIDE_CURSORS": False,
                    "CONN_MAX_AGE": 0,
                    "CONN_HEALTH_CHECKS": False,
                },
            },
        )

.../lib/tests/test_config.py:438: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <authentik.lib.tests.test_config.TestConfig testMethod=test_db_read_replicas_diff_ssl>
first = {'default': {'CONN_HEALTH_CHECKS': False, 'CONN_MAX_AGE': 60, 'DISABLE_SERVER_SIDE_CURSORS': False, 'ENGINE': 'psqlext...N_HEALTH_CHECKS': False, 'CONN_MAX_AGE': 60, 'DISABLE_SERVER_SIDE_CURSORS': False, 'ENGINE': 'psqlextra.backend', ...}}
second = {'default': {'CONN_HEALTH_CHECKS': False, 'CONN_MAX_AGE': 0, 'DISABLE_SERVER_SIDE_CURSORS': False, 'ENGINE': 'psqlextr...NN_HEALTH_CHECKS': False, 'CONN_MAX_AGE': 0, 'DISABLE_SERVER_SIDE_CURSORS': False, 'ENGINE': 'psqlextra.backend', ...}}
msg = None

    def assertEqual(self, first, second, msg=None):
        """Fail if the two objects are unequal as determined by the '=='
           operator.
        """
        assertion_func = self._getAssertEqualityFunc(first, second)
>       assertion_func(first, second, msg=msg)

.../hostedtoolcache/Python/3.14.3................../x64/lib/python3.14/unittest/case.py:925: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <authentik.lib.tests.test_config.TestConfig testMethod=test_db_read_replicas_diff_ssl>
d1 = {'default': {'CONN_HEALTH_CHECKS': False, 'CONN_MAX_AGE': 60, 'DISABLE_SERVER_SIDE_CURSORS': False, 'ENGINE': 'psqlext...N_HEALTH_CHECKS': False, 'CONN_MAX_AGE': 60, 'DISABLE_SERVER_SIDE_CURSORS': False, 'ENGINE': 'psqlextra.backend', ...}}
d2 = {'default': {'CONN_HEALTH_CHECKS': False, 'CONN_MAX_AGE': 0, 'DISABLE_SERVER_SIDE_CURSORS': False, 'ENGINE': 'psqlextr...NN_HEALTH_CHECKS': False, 'CONN_MAX_AGE': 0, 'DISABLE_SERVER_SIDE_CURSORS': False, 'ENGINE': 'psqlextra.backend', ...}}
msg = None

    def assertDictEqual(self, d1, d2, msg=None):
        self.assertIsInstance(d1, dict, 'First argument is not a dictionary')
        self.assertIsInstance(d2, dict, 'Second argument is not a dictionary')
    
        if d1 != d2:
            standardMsg = '%s != %s' % _common_shorten_repr(d1, d2)
            diff = ('\n' + '\n'.join(difflib.ndiff(
                           pprint.pformat(d1).splitlines(),
                           pprint.pformat(d2).splitlines())))
            standardMsg = self._truncateMessage(standardMsg, diff)
>           self.fail(self._formatMessage(msg, standardMsg))

.../hostedtoolcache/Python/3.14.3................../x64/lib/python3.14/unittest/case.py:1224: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <authentik.lib.tests.test_config.TestConfig testMethod=test_db_read_replicas_diff_ssl>
msg = "{'def[50 chars]o', 'PORT': 'foo', 'USER': 'foo', 'PASSWORD': [572 chars]o'}}} != {'def[50 chars]o', 'NAME': 'foo', 'O...: 'foo',\n                 'PORT': 'foo',\n                 'TEST': {'NAME': 'foo'},\n                 'USER': 'foo'}}"

    def fail(self, msg=None):
        """Fail immediately, with the given message."""
>       raise self.failureException(msg)
E       AssertionError: {'def[50 chars]o', 'PORT': 'foo', 'USER': 'foo', 'PASSWORD': [572 chars]o'}}} != {'def[50 chars]o', 'NAME': 'foo', 'OPTIONS': {'pool': False, [570 chars]lse}}
E         {'default': {'CONN_HEALTH_CHECKS': False,
E       -              'CONN_MAX_AGE': 60,
E       ?                              -
E       
E       +              'CONN_MAX_AGE': 0,
E                      'DISABLE_SERVER_SIDE_CURSORS': False,
E                      'ENGINE': 'psqlextra.backend',
E                      'HOST': 'foo',
E                      'NAME': 'foo',
E                      'OPTIONS': {'pool': False,
E                                  'sslcert': 'foo',
E                                  'sslkey': 'foo',
E                                  'sslmode': 'foo',
E                                  'sslrootcert': 'foo'},
E                      'PASSWORD': 'foo',
E                      'PORT': 'foo',
E                      'TEST': {'NAME': 'foo'},
E                      'USER': 'foo'},
E          'replica_0': {'CONN_HEALTH_CHECKS': False,
E       -                'CONN_MAX_AGE': 60,
E       ?                                -
E       
E       +                'CONN_MAX_AGE': 0,
E                        'DISABLE_SERVER_SIDE_CURSORS': False,
E                        'ENGINE': 'psqlextra.backend',
E                        'HOST': 'bar',
E                        'NAME': 'foo',
E                        'OPTIONS': {'pool': False,
E                                    'sslcert': 'bar',
E                                    'sslkey': 'foo',
E                                    'sslmode': 'foo',
E                                    'sslrootcert': 'foo'},
E                        'PASSWORD': 'foo',
E                        'PORT': 'foo',
E                        'TEST': {'NAME': 'foo'},
E                        'USER': 'foo'}}

.../hostedtoolcache/Python/3.14.3................../x64/lib/python3.14/unittest/case.py:750: AssertionError
authentik.lib.tests.test_config.TestConfig::test_db_read_replicas
Stack Traces | 0.016s run time
self = <unittest.case._Outcome object at 0x7f28e8af8750>
test_case = <authentik.lib.tests.test_config.TestConfig testMethod=test_db_read_replicas>
subTest = False

    @contextlib.contextmanager
    def testPartExecutor(self, test_case, subTest=False):
        old_success = self.success
        self.success = True
        try:
>           yield

.../hostedtoolcache/Python/3.14.3................../x64/lib/python3.14/unittest/case.py:58: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <authentik.lib.tests.test_config.TestConfig testMethod=test_db_read_replicas>
result = <TestCaseFunction test_db_read_replicas>

    def run(self, result=None):
        if result is None:
            result = self.defaultTestResult()
            startTestRun = getattr(result, 'startTestRun', None)
            stopTestRun = getattr(result, 'stopTestRun', None)
            if startTestRun is not None:
                startTestRun()
        else:
            stopTestRun = None
    
        result.startTest(self)
        try:
            testMethod = getattr(self, self._testMethodName)
            if (getattr(self.__class__, "__unittest_skip__", False) or
                getattr(testMethod, "__unittest_skip__", False)):
                # If the class or method was skipped.
                skip_why = (getattr(self.__class__, '__unittest_skip_why__', '')
                            or getattr(testMethod, '__unittest_skip_why__', ''))
                _addSkip(result, self, skip_why)
                return result
    
            expecting_failure = (
                getattr(self, "__unittest_expecting_failure__", False) or
                getattr(testMethod, "__unittest_expecting_failure__", False)
            )
            outcome = _Outcome(result)
            start_time = time.perf_counter()
            try:
                self._outcome = outcome
    
                with outcome.testPartExecutor(self):
                    self._callSetUp()
                if outcome.success:
                    outcome.expecting_failure = expecting_failure
                    with outcome.testPartExecutor(self):
>                       self._callTestMethod(testMethod)

.../hostedtoolcache/Python/3.14.3................../x64/lib/python3.14/unittest/case.py:669: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <authentik.lib.tests.test_config.TestConfig testMethod=test_db_read_replicas>
method = <bound method TestConfig.test_db_read_replicas of <authentik.lib.tests.test_config.TestConfig testMethod=test_db_read_replicas>>

    def _callTestMethod(self, method):
>       result = method()
                 ^^^^^^^^

.../hostedtoolcache/Python/3.14.3................../x64/lib/python3.14/unittest/case.py:615: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <authentik.lib.tests.test_config.TestConfig testMethod=test_db_read_replicas>

    def test_db_read_replicas(self):
        """Test read replicas"""
        config = ConfigLoader()
        config.set("postgresql.host", "foo")
        config.set("postgresql.name", "foo")
        config.set("postgresql.user", "foo")
        config.set("postgresql.password", "foo")
        config.set("postgresql.port", "foo")
        config.set("postgresql.sslmode", "foo")
        config.set("postgresql.sslrootcert", "foo")
        config.set("postgresql.sslcert", "foo")
        config.set("postgresql.sslkey", "foo")
        config.set("postgresql.test.name", "foo")
        # Read replica
        config.set("postgresql.read_replicas.0.host", "bar")
        conf = django_db_config(config)
>       self.assertEqual(
            conf,
            {
                "default": {
                    "ENGINE": "psqlextra.backend",
                    "HOST": "foo",
                    "NAME": "foo",
                    "OPTIONS": {
                        "pool": False,
                        "sslcert": "foo",
                        "sslkey": "foo",
                        "sslmode": "foo",
                        "sslrootcert": "foo",
                    },
                    "PASSWORD": "foo",
                    "PORT": "foo",
                    "TEST": {"NAME": "foo"},
                    "USER": "foo",
                    "CONN_MAX_AGE": 0,
                    "CONN_HEALTH_CHECKS": False,
                    "DISABLE_SERVER_SIDE_CURSORS": False,
                },
                "replica_0": {
                    "ENGINE": "psqlextra.backend",
                    "HOST": "bar",
                    "NAME": "foo",
                    "OPTIONS": {
                        "pool": False,
                        "sslcert": "foo",
                        "sslkey": "foo",
                        "sslmode": "foo",
                        "sslrootcert": "foo",
                    },
                    "PASSWORD": "foo",
                    "PORT": "foo",
                    "TEST": {"NAME": "foo"},
                    "USER": "foo",
                    "CONN_MAX_AGE": 0,
                    "CONN_HEALTH_CHECKS": False,
                    "DISABLE_SERVER_SIDE_CURSORS": False,
                },
            },
        )

.../lib/tests/test_config.py:248: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <authentik.lib.tests.test_config.TestConfig testMethod=test_db_read_replicas>
first = {'default': {'CONN_HEALTH_CHECKS': False, 'CONN_MAX_AGE': 60, 'DISABLE_SERVER_SIDE_CURSORS': False, 'ENGINE': 'psqlext...N_HEALTH_CHECKS': False, 'CONN_MAX_AGE': 60, 'DISABLE_SERVER_SIDE_CURSORS': False, 'ENGINE': 'psqlextra.backend', ...}}
second = {'default': {'CONN_HEALTH_CHECKS': False, 'CONN_MAX_AGE': 0, 'DISABLE_SERVER_SIDE_CURSORS': False, 'ENGINE': 'psqlextr...NN_HEALTH_CHECKS': False, 'CONN_MAX_AGE': 0, 'DISABLE_SERVER_SIDE_CURSORS': False, 'ENGINE': 'psqlextra.backend', ...}}
msg = None

    def assertEqual(self, first, second, msg=None):
        """Fail if the two objects are unequal as determined by the '=='
           operator.
        """
        assertion_func = self._getAssertEqualityFunc(first, second)
>       assertion_func(first, second, msg=msg)

.../hostedtoolcache/Python/3.14.3................../x64/lib/python3.14/unittest/case.py:925: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <authentik.lib.tests.test_config.TestConfig testMethod=test_db_read_replicas>
d1 = {'default': {'CONN_HEALTH_CHECKS': False, 'CONN_MAX_AGE': 60, 'DISABLE_SERVER_SIDE_CURSORS': False, 'ENGINE': 'psqlext...N_HEALTH_CHECKS': False, 'CONN_MAX_AGE': 60, 'DISABLE_SERVER_SIDE_CURSORS': False, 'ENGINE': 'psqlextra.backend', ...}}
d2 = {'default': {'CONN_HEALTH_CHECKS': False, 'CONN_MAX_AGE': 0, 'DISABLE_SERVER_SIDE_CURSORS': False, 'ENGINE': 'psqlextr...NN_HEALTH_CHECKS': False, 'CONN_MAX_AGE': 0, 'DISABLE_SERVER_SIDE_CURSORS': False, 'ENGINE': 'psqlextra.backend', ...}}
msg = None

    def assertDictEqual(self, d1, d2, msg=None):
        self.assertIsInstance(d1, dict, 'First argument is not a dictionary')
        self.assertIsInstance(d2, dict, 'Second argument is not a dictionary')
    
        if d1 != d2:
            standardMsg = '%s != %s' % _common_shorten_repr(d1, d2)
            diff = ('\n' + '\n'.join(difflib.ndiff(
                           pprint.pformat(d1).splitlines(),
                           pprint.pformat(d2).splitlines())))
            standardMsg = self._truncateMessage(standardMsg, diff)
>           self.fail(self._formatMessage(msg, standardMsg))

.../hostedtoolcache/Python/3.14.3................../x64/lib/python3.14/unittest/case.py:1224: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <authentik.lib.tests.test_config.TestConfig testMethod=test_db_read_replicas>
msg = "{'def[50 chars]o', 'PORT': 'foo', 'USER': 'foo', 'PASSWORD': [572 chars]o'}}} != {'def[50 chars]o', 'NAME': 'foo', 'O...: 'foo',\n                 'PORT': 'foo',\n                 'TEST': {'NAME': 'foo'},\n                 'USER': 'foo'}}"

    def fail(self, msg=None):
        """Fail immediately, with the given message."""
>       raise self.failureException(msg)
E       AssertionError: {'def[50 chars]o', 'PORT': 'foo', 'USER': 'foo', 'PASSWORD': [572 chars]o'}}} != {'def[50 chars]o', 'NAME': 'foo', 'OPTIONS': {'pool': False, [570 chars]lse}}
E         {'default': {'CONN_HEALTH_CHECKS': False,
E       -              'CONN_MAX_AGE': 60,
E       ?                              -
E       
E       +              'CONN_MAX_AGE': 0,
E                      'DISABLE_SERVER_SIDE_CURSORS': False,
E                      'ENGINE': 'psqlextra.backend',
E                      'HOST': 'foo',
E                      'NAME': 'foo',
E                      'OPTIONS': {'pool': False,
E                                  'sslcert': 'foo',
E                                  'sslkey': 'foo',
E                                  'sslmode': 'foo',
E                                  'sslrootcert': 'foo'},
E                      'PASSWORD': 'foo',
E                      'PORT': 'foo',
E                      'TEST': {'NAME': 'foo'},
E                      'USER': 'foo'},
E          'replica_0': {'CONN_HEALTH_CHECKS': False,
E       -                'CONN_MAX_AGE': 60,
E       ?                                -
E       
E       +                'CONN_MAX_AGE': 0,
E                        'DISABLE_SERVER_SIDE_CURSORS': False,
E                        'ENGINE': 'psqlextra.backend',
E                        'HOST': 'bar',
E                        'NAME': 'foo',
E                        'OPTIONS': {'pool': False,
E                                    'sslcert': 'foo',
E                                    'sslkey': 'foo',
E                                    'sslmode': 'foo',
E                                    'sslrootcert': 'foo'},
E                        'PASSWORD': 'foo',
E                        'PORT': 'foo',
E                        'TEST': {'NAME': 'foo'},
E                        'USER': 'foo'}}

.../hostedtoolcache/Python/3.14.3................../x64/lib/python3.14/unittest/case.py:750: AssertionError
authentik.lib.tests.test_config.TestConfig::test_db_read_replicas_pgbouncer
Stack Traces | 0.016s run time
self = <unittest.case._Outcome object at 0x7f28eb8b5160>
test_case = <authentik.lib.tests.test_config.TestConfig testMethod=test_db_read_replicas_pgbouncer>
subTest = False

    @contextlib.contextmanager
    def testPartExecutor(self, test_case, subTest=False):
        old_success = self.success
        self.success = True
        try:
>           yield

.../hostedtoolcache/Python/3.14.3................../x64/lib/python3.14/unittest/case.py:58: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <authentik.lib.tests.test_config.TestConfig testMethod=test_db_read_replicas_pgbouncer>
result = <TestCaseFunction test_db_read_replicas_pgbouncer>

    def run(self, result=None):
        if result is None:
            result = self.defaultTestResult()
            startTestRun = getattr(result, 'startTestRun', None)
            stopTestRun = getattr(result, 'stopTestRun', None)
            if startTestRun is not None:
                startTestRun()
        else:
            stopTestRun = None
    
        result.startTest(self)
        try:
            testMethod = getattr(self, self._testMethodName)
            if (getattr(self.__class__, "__unittest_skip__", False) or
                getattr(testMethod, "__unittest_skip__", False)):
                # If the class or method was skipped.
                skip_why = (getattr(self.__class__, '__unittest_skip_why__', '')
                            or getattr(testMethod, '__unittest_skip_why__', ''))
                _addSkip(result, self, skip_why)
                return result
    
            expecting_failure = (
                getattr(self, "__unittest_expecting_failure__", False) or
                getattr(testMethod, "__unittest_expecting_failure__", False)
            )
            outcome = _Outcome(result)
            start_time = time.perf_counter()
            try:
                self._outcome = outcome
    
                with outcome.testPartExecutor(self):
                    self._callSetUp()
                if outcome.success:
                    outcome.expecting_failure = expecting_failure
                    with outcome.testPartExecutor(self):
>                       self._callTestMethod(testMethod)

.../hostedtoolcache/Python/3.14.3................../x64/lib/python3.14/unittest/case.py:669: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <authentik.lib.tests.test_config.TestConfig testMethod=test_db_read_replicas_pgbouncer>
method = <bound method TestConfig.test_db_read_replicas_pgbouncer of <authentik.lib.tests.test_config.TestConfig testMethod=test_db_read_replicas_pgbouncer>>

    def _callTestMethod(self, method):
>       result = method()
                 ^^^^^^^^

.../hostedtoolcache/Python/3.14.3................../x64/lib/python3.14/unittest/case.py:615: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <authentik.lib.tests.test_config.TestConfig testMethod=test_db_read_replicas_pgbouncer>

    def test_db_read_replicas_pgbouncer(self):
        """Test read replicas"""
        config = ConfigLoader()
        config.set("postgresql.host", "foo")
        config.set("postgresql.name", "foo")
        config.set("postgresql.user", "foo")
        config.set("postgresql.password", "foo")
        config.set("postgresql.port", "foo")
        config.set("postgresql.sslmode", "foo")
        config.set("postgresql.sslrootcert", "foo")
        config.set("postgresql.sslcert", "foo")
        config.set("postgresql.sslkey", "foo")
        config.set("postgresql.test.name", "foo")
        config.set("postgresql.use_pgbouncer", True)
        # Read replica
        config.set("postgresql.read_replicas.0.host", "bar")
        # Override conn_max_age
        config.set("postgresql.read_replicas.0.conn_max_age", 10)
        # This isn't supported
        config.set("postgresql.read_replicas.0.use_pgbouncer", False)
        conf = django_db_config(config)
>       self.assertEqual(
            conf,
            {
                "default": {
                    "DISABLE_SERVER_SIDE_CURSORS": True,
                    "CONN_MAX_AGE": None,
                    "CONN_HEALTH_CHECKS": False,
                    "ENGINE": "psqlextra.backend",
                    "HOST": "foo",
                    "NAME": "foo",
                    "OPTIONS": {
                        "pool": False,
                        "sslcert": "foo",
                        "sslkey": "foo",
                        "sslmode": "foo",
                        "sslrootcert": "foo",
                    },
                    "PASSWORD": "foo",
                    "PORT": "foo",
                    "TEST": {"NAME": "foo"},
                    "USER": "foo",
                },
                "replica_0": {
                    "DISABLE_SERVER_SIDE_CURSORS": True,
                    "CONN_MAX_AGE": 10,
                    "CONN_HEALTH_CHECKS": False,
                    "ENGINE": "psqlextra.backend",
                    "HOST": "bar",
                    "NAME": "foo",
                    "OPTIONS": {
                        "pool": False,
                        "sslcert": "foo",
                        "sslkey": "foo",
                        "sslmode": "foo",
                        "sslrootcert": "foo",
                    },
                    "PASSWORD": "foo",
                    "PORT": "foo",
                    "TEST": {"NAME": "foo"},
                    "USER": "foo",
                },
            },
        )

.../lib/tests/test_config.py:313: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <authentik.lib.tests.test_config.TestConfig testMethod=test_db_read_replicas_pgbouncer>
first = {'default': {'CONN_HEALTH_CHECKS': False, 'CONN_MAX_AGE': 60, 'DISABLE_SERVER_SIDE_CURSORS': True, 'ENGINE': 'psqlextr...NN_HEALTH_CHECKS': False, 'CONN_MAX_AGE': 10, 'DISABLE_SERVER_SIDE_CURSORS': True, 'ENGINE': 'psqlextra.backend', ...}}
second = {'default': {'CONN_HEALTH_CHECKS': False, 'CONN_MAX_AGE': None, 'DISABLE_SERVER_SIDE_CURSORS': True, 'ENGINE': 'psqlex...NN_HEALTH_CHECKS': False, 'CONN_MAX_AGE': 10, 'DISABLE_SERVER_SIDE_CURSORS': True, 'ENGINE': 'psqlextra.backend', ...}}
msg = None

    def assertEqual(self, first, second, msg=None):
        """Fail if the two objects are unequal as determined by the '=='
           operator.
        """
        assertion_func = self._getAssertEqualityFunc(first, second)
>       assertion_func(first, second, msg=msg)

.../hostedtoolcache/Python/3.14.3................../x64/lib/python3.14/unittest/case.py:925: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <authentik.lib.tests.test_config.TestConfig testMethod=test_db_read_replicas_pgbouncer>
d1 = {'default': {'CONN_HEALTH_CHECKS': False, 'CONN_MAX_AGE': 60, 'DISABLE_SERVER_SIDE_CURSORS': True, 'ENGINE': 'psqlextr...NN_HEALTH_CHECKS': False, 'CONN_MAX_AGE': 10, 'DISABLE_SERVER_SIDE_CURSORS': True, 'ENGINE': 'psqlextra.backend', ...}}
d2 = {'default': {'CONN_HEALTH_CHECKS': False, 'CONN_MAX_AGE': None, 'DISABLE_SERVER_SIDE_CURSORS': True, 'ENGINE': 'psqlex...NN_HEALTH_CHECKS': False, 'CONN_MAX_AGE': 10, 'DISABLE_SERVER_SIDE_CURSORS': True, 'ENGINE': 'psqlextra.backend', ...}}
msg = None

    def assertDictEqual(self, d1, d2, msg=None):
        self.assertIsInstance(d1, dict, 'First argument is not a dictionary')
        self.assertIsInstance(d2, dict, 'Second argument is not a dictionary')
    
        if d1 != d2:
            standardMsg = '%s != %s' % _common_shorten_repr(d1, d2)
            diff = ('\n' + '\n'.join(difflib.ndiff(
                           pprint.pformat(d1).splitlines(),
                           pprint.pformat(d2).splitlines())))
            standardMsg = self._truncateMessage(standardMsg, diff)
>           self.fail(self._formatMessage(msg, standardMsg))

.../hostedtoolcache/Python/3.14.3................../x64/lib/python3.14/unittest/case.py:1224: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <authentik.lib.tests.test_config.TestConfig testMethod=test_db_read_replicas_pgbouncer>
msg = "{'default': {'ENGINE': 'psqlextra.backend', 'HOST': 'fo[616 chars]o'}}} != {'default': {'DISABLE_SERVER_SIDE_CURSORS'...: 'foo',\n                 'PORT': 'foo',\n                 'TEST': {'NAME': 'foo'},\n                 'USER': 'foo'}}"

    def fail(self, msg=None):
        """Fail immediately, with the given message."""
>       raise self.failureException(msg)
E       AssertionError: {'default': {'ENGINE': 'psqlextra.backend', 'HOST': 'fo[616 chars]o'}}} != {'default': {'DISABLE_SERVER_SIDE_CURSORS': True, 'CONN[618 chars]oo'}}
E         {'default': {'CONN_HEALTH_CHECKS': False,
E       -              'CONN_MAX_AGE': 60,
E       ?                              ^^
E       
E       +              'CONN_MAX_AGE': None,
E       ?                              ^^^^
E       
E                      'DISABLE_SERVER_SIDE_CURSORS': True,
E                      'ENGINE': 'psqlextra.backend',
E                      'HOST': 'foo',
E                      'NAME': 'foo',
E                      'OPTIONS': {'pool': False,
E                                  'sslcert': 'foo',
E                                  'sslkey': 'foo',
E                                  'sslmode': 'foo',
E                                  'sslrootcert': 'foo'},
E                      'PASSWORD': 'foo',
E                      'PORT': 'foo',
E                      'TEST': {'NAME': 'foo'},
E                      'USER': 'foo'},
E          'replica_0': {'CONN_HEALTH_CHECKS': False,
E                        'CONN_MAX_AGE': 10,
E                        'DISABLE_SERVER_SIDE_CURSORS': True,
E                        'ENGINE': 'psqlextra.backend',
E                        'HOST': 'bar',
E                        'NAME': 'foo',
E                        'OPTIONS': {'pool': False,
E                                    'sslcert': 'foo',
E                                    'sslkey': 'foo',
E                                    'sslmode': 'foo',
E                                    'sslrootcert': 'foo'},
E                        'PASSWORD': 'foo',
E                        'PORT': 'foo',
E                        'TEST': {'NAME': 'foo'},
E                        'USER': 'foo'}}

.../hostedtoolcache/Python/3.14.3................../x64/lib/python3.14/unittest/case.py:750: AssertionError
authentik.lib.tests.test_config.TestConfig::test_db_read_replicas_pgpool
Stack Traces | 0.016s run time
self = <unittest.case._Outcome object at 0x7f290534b310>
test_case = <authentik.lib.tests.test_config.TestConfig testMethod=test_db_read_replicas_pgpool>
subTest = False

    @contextlib.contextmanager
    def testPartExecutor(self, test_case, subTest=False):
        old_success = self.success
        self.success = True
        try:
>           yield

.../hostedtoolcache/Python/3.14.3................../x64/lib/python3.14/unittest/case.py:58: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <authentik.lib.tests.test_config.TestConfig testMethod=test_db_read_replicas_pgpool>
result = <TestCaseFunction test_db_read_replicas_pgpool>

    def run(self, result=None):
        if result is None:
            result = self.defaultTestResult()
            startTestRun = getattr(result, 'startTestRun', None)
            stopTestRun = getattr(result, 'stopTestRun', None)
            if startTestRun is not None:
                startTestRun()
        else:
            stopTestRun = None
    
        result.startTest(self)
        try:
            testMethod = getattr(self, self._testMethodName)
            if (getattr(self.__class__, "__unittest_skip__", False) or
                getattr(testMethod, "__unittest_skip__", False)):
                # If the class or method was skipped.
                skip_why = (getattr(self.__class__, '__unittest_skip_why__', '')
                            or getattr(testMethod, '__unittest_skip_why__', ''))
                _addSkip(result, self, skip_why)
                return result
    
            expecting_failure = (
                getattr(self, "__unittest_expecting_failure__", False) or
                getattr(testMethod, "__unittest_expecting_failure__", False)
            )
            outcome = _Outcome(result)
            start_time = time.perf_counter()
            try:
                self._outcome = outcome
    
                with outcome.testPartExecutor(self):
                    self._callSetUp()
                if outcome.success:
                    outcome.expecting_failure = expecting_failure
                    with outcome.testPartExecutor(self):
>                       self._callTestMethod(testMethod)

.../hostedtoolcache/Python/3.14.3................../x64/lib/python3.14/unittest/case.py:669: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <authentik.lib.tests.test_config.TestConfig testMethod=test_db_read_replicas_pgpool>
method = <bound method TestConfig.test_db_read_replicas_pgpool of <authentik.lib.tests.test_config.TestConfig testMethod=test_db_read_replicas_pgpool>>

    def _callTestMethod(self, method):
>       result = method()
                 ^^^^^^^^

.../hostedtoolcache/Python/3.14.3................../x64/lib/python3.14/unittest/case.py:615: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <authentik.lib.tests.test_config.TestConfig testMethod=test_db_read_replicas_pgpool>

    def test_db_read_replicas_pgpool(self):
        """Test read replicas"""
        config = ConfigLoader()
        config.set("postgresql.host", "foo")
        config.set("postgresql.name", "foo")
        config.set("postgresql.user", "foo")
        config.set("postgresql.password", "foo")
        config.set("postgresql.port", "foo")
        config.set("postgresql.sslmode", "foo")
        config.set("postgresql.sslrootcert", "foo")
        config.set("postgresql.sslcert", "foo")
        config.set("postgresql.sslkey", "foo")
        config.set("postgresql.test.name", "foo")
        config.set("postgresql.use_pgpool", True)
        # Read replica
        config.set("postgresql.read_replicas.0.host", "bar")
        # This isn't supported
        config.set("postgresql.read_replicas.0.use_pgpool", False)
        conf = django_db_config(config)
>       self.assertEqual(
            conf,
            {
                "default": {
                    "DISABLE_SERVER_SIDE_CURSORS": True,
                    "CONN_MAX_AGE": 0,
                    "CONN_HEALTH_CHECKS": False,
                    "ENGINE": "psqlextra.backend",
                    "HOST": "foo",
                    "NAME": "foo",
                    "OPTIONS": {
                        "pool": False,
                        "sslcert": "foo",
                        "sslkey": "foo",
                        "sslmode": "foo",
                        "sslrootcert": "foo",
                    },
                    "PASSWORD": "foo",
                    "PORT": "foo",
                    "TEST": {"NAME": "foo"},
                    "USER": "foo",
                },
                "replica_0": {
                    "DISABLE_SERVER_SIDE_CURSORS": True,
                    "CONN_MAX_AGE": 0,
                    "CONN_HEALTH_CHECKS": False,
                    "ENGINE": "psqlextra.backend",
                    "HOST": "bar",
                    "NAME": "foo",
                    "OPTIONS": {
                        "pool": False,
                        "sslcert": "foo",
                        "sslkey": "foo",
                        "sslmode": "foo",
                        "sslrootcert": "foo",
                    },
                    "PASSWORD": "foo",
                    "PORT": "foo",
                    "TEST": {"NAME": "foo"},
                    "USER": "foo",
                },
            },
        )

.../lib/tests/test_config.py:376: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <authentik.lib.tests.test_config.TestConfig testMethod=test_db_read_replicas_pgpool>
first = {'default': {'CONN_HEALTH_CHECKS': False, 'CONN_MAX_AGE': 60, 'DISABLE_SERVER_SIDE_CURSORS': True, 'ENGINE': 'psqlextr...NN_HEALTH_CHECKS': False, 'CONN_MAX_AGE': 60, 'DISABLE_SERVER_SIDE_CURSORS': True, 'ENGINE': 'psqlextra.backend', ...}}
second = {'default': {'CONN_HEALTH_CHECKS': False, 'CONN_MAX_AGE': 0, 'DISABLE_SERVER_SIDE_CURSORS': True, 'ENGINE': 'psqlextra...ONN_HEALTH_CHECKS': False, 'CONN_MAX_AGE': 0, 'DISABLE_SERVER_SIDE_CURSORS': True, 'ENGINE': 'psqlextra.backend', ...}}
msg = None

    def assertEqual(self, first, second, msg=None):
        """Fail if the two objects are unequal as determined by the '=='
           operator.
        """
        assertion_func = self._getAssertEqualityFunc(first, second)
>       assertion_func(first, second, msg=msg)

.../hostedtoolcache/Python/3.14.3................../x64/lib/python3.14/unittest/case.py:925: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <authentik.lib.tests.test_config.TestConfig testMethod=test_db_read_replicas_pgpool>
d1 = {'default': {'CONN_HEALTH_CHECKS': False, 'CONN_MAX_AGE': 60, 'DISABLE_SERVER_SIDE_CURSORS': True, 'ENGINE': 'psqlextr...NN_HEALTH_CHECKS': False, 'CONN_MAX_AGE': 60, 'DISABLE_SERVER_SIDE_CURSORS': True, 'ENGINE': 'psqlextra.backend', ...}}
d2 = {'default': {'CONN_HEALTH_CHECKS': False, 'CONN_MAX_AGE': 0, 'DISABLE_SERVER_SIDE_CURSORS': True, 'ENGINE': 'psqlextra...ONN_HEALTH_CHECKS': False, 'CONN_MAX_AGE': 0, 'DISABLE_SERVER_SIDE_CURSORS': True, 'ENGINE': 'psqlextra.backend', ...}}
msg = None

    def assertDictEqual(self, d1, d2, msg=None):
        self.assertIsInstance(d1, dict, 'First argument is not a dictionary')
        self.assertIsInstance(d2, dict, 'Second argument is not a dictionary')
    
        if d1 != d2:
            standardMsg = '%s != %s' % _common_shorten_repr(d1, d2)
            diff = ('\n' + '\n'.join(difflib.ndiff(
                           pprint.pformat(d1).splitlines(),
                           pprint.pformat(d2).splitlines())))
            standardMsg = self._truncateMessage(standardMsg, diff)
>           self.fail(self._formatMessage(msg, standardMsg))

.../hostedtoolcache/Python/3.14.3................../x64/lib/python3.14/unittest/case.py:1224: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <authentik.lib.tests.test_config.TestConfig testMethod=test_db_read_replicas_pgpool>
msg = "{'default': {'ENGINE': 'psqlextra.backend', 'HOST': 'fo[616 chars]o'}}} != {'default': {'DISABLE_SERVER_SIDE_CURSORS'...: 'foo',\n                 'PORT': 'foo',\n                 'TEST': {'NAME': 'foo'},\n                 'USER': 'foo'}}"

    def fail(self, msg=None):
        """Fail immediately, with the given message."""
>       raise self.failureException(msg)
E       AssertionError: {'default': {'ENGINE': 'psqlextra.backend', 'HOST': 'fo[616 chars]o'}}} != {'default': {'DISABLE_SERVER_SIDE_CURSORS': True, 'CONN[614 chars]oo'}}
E         {'default': {'CONN_HEALTH_CHECKS': False,
E       -              'CONN_MAX_AGE': 60,
E       ?                              -
E       
E       +              'CONN_MAX_AGE': 0,
E                      'DISABLE_SERVER_SIDE_CURSORS': True,
E                      'ENGINE': 'psqlextra.backend',
E                      'HOST': 'foo',
E                      'NAME': 'foo',
E                      'OPTIONS': {'pool': False,
E                                  'sslcert': 'foo',
E                                  'sslkey': 'foo',
E                                  'sslmode': 'foo',
E                                  'sslrootcert': 'foo'},
E                      'PASSWORD': 'foo',
E                      'PORT': 'foo',
E                      'TEST': {'NAME': 'foo'},
E                      'USER': 'foo'},
E          'replica_0': {'CONN_HEALTH_CHECKS': False,
E       -                'CONN_MAX_AGE': 60,
E       ?                                -
E       
E       +                'CONN_MAX_AGE': 0,
E                        'DISABLE_SERVER_SIDE_CURSORS': True,
E                        'ENGINE': 'psqlextra.backend',
E                        'HOST': 'bar',
E                        'NAME': 'foo',
E                        'OPTIONS': {'pool': False,
E                                    'sslcert': 'foo',
E                                    'sslkey': 'foo',
E                                    'sslmode': 'foo',
E                                    'sslrootcert': 'foo'},
E                        'PASSWORD': 'foo',
E                        'PORT': 'foo',
E                        'TEST': {'NAME': 'foo'},
E                        'USER': 'foo'}}

.../hostedtoolcache/Python/3.14.3................../x64/lib/python3.14/unittest/case.py:750: AssertionError
authentik.root.tests.test_views.TestRoot::test_monitoring_error
Stack Traces | 1.93s run time
self = <unittest.case._Outcome object at 0x7ff5a3908050>
test_case = <authentik.root.tests.test_views.TestRoot testMethod=test_monitoring_error>
subTest = False

    @contextlib.contextmanager
    def testPartExecutor(self, test_case, subTest=False):
        old_success = self.success
        self.success = True
        try:
>           yield

.../hostedtoolcache/Python/3.14.3.............../x64/lib/python3.14/unittest/case.py:58: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <authentik.root.tests.test_views.TestRoot testMethod=test_monitoring_error>
result = <TestCaseFunction test_monitoring_error>

    def run(self, result=None):
        if result is None:
            result = self.defaultTestResult()
            startTestRun = getattr(result, 'startTestRun', None)
            stopTestRun = getattr(result, 'stopTestRun', None)
            if startTestRun is not None:
                startTestRun()
        else:
            stopTestRun = None
    
        result.startTest(self)
        try:
            testMethod = getattr(self, self._testMethodName)
            if (getattr(self.__class__, "__unittest_skip__", False) or
                getattr(testMethod, "__unittest_skip__", False)):
                # If the class or method was skipped.
                skip_why = (getattr(self.__class__, '__unittest_skip_why__', '')
                            or getattr(testMethod, '__unittest_skip_why__', ''))
                _addSkip(result, self, skip_why)
                return result
    
            expecting_failure = (
                getattr(self, "__unittest_expecting_failure__", False) or
                getattr(testMethod, "__unittest_expecting_failure__", False)
            )
            outcome = _Outcome(result)
            start_time = time.perf_counter()
            try:
                self._outcome = outcome
    
                with outcome.testPartExecutor(self):
                    self._callSetUp()
                if outcome.success:
                    outcome.expecting_failure = expecting_failure
                    with outcome.testPartExecutor(self):
>                       self._callTestMethod(testMethod)

.../hostedtoolcache/Python/3.14.3.............../x64/lib/python3.14/unittest/case.py:669: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <authentik.root.tests.test_views.TestRoot testMethod=test_monitoring_error>
method = <bound method TestRoot.test_monitoring_error of <authentik.root.tests.test_views.TestRoot testMethod=test_monitoring_error>>

    def _callTestMethod(self, method):
>       result = method()
                 ^^^^^^^^

.../hostedtoolcache/Python/3.14.3.............../x64/lib/python3.14/unittest/case.py:615: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <authentik.root.tests.test_views.TestRoot testMethod=test_monitoring_error>

    def test_monitoring_error(self):
        """Test monitoring without any credentials"""
        response = self.client.get(reverse("metrics"))
>       self.assertEqual(response.status_code, 401)

.../root/tests/test_views.py:27: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <authentik.root.tests.test_views.TestRoot testMethod=test_monitoring_error>
first = 204, second = 401, msg = None

    def assertEqual(self, first, second, msg=None):
        """Fail if the two objects are unequal as determined by the '=='
           operator.
        """
        assertion_func = self._getAssertEqualityFunc(first, second)
>       assertion_func(first, second, msg=msg)

.../hostedtoolcache/Python/3.14.3.............../x64/lib/python3.14/unittest/case.py:925: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <authentik.root.tests.test_views.TestRoot testMethod=test_monitoring_error>
first = 204, second = 401, msg = '204 != 401'

    def _baseAssertEqual(self, first, second, msg=None):
        """The default assertEqual implementation, not type specific."""
        if not first == second:
            standardMsg = '%s != %s' % _common_shorten_repr(first, second)
            msg = self._formatMessage(msg, standardMsg)
>           raise self.failureException(msg)
E           AssertionError: 204 != 401

.../hostedtoolcache/Python/3.14.3.............../x64/lib/python3.14/unittest/case.py:918: AssertionError
authentik.root.tests.test_views.TestRoot::test_monitoring_ok
Stack Traces | 1.96s run time
self = <unittest.case._Outcome object at 0x7ff5b804f4d0>
test_case = <authentik.root.tests.test_views.TestRoot testMethod=test_monitoring_ok>
subTest = False

    @contextlib.contextmanager
    def testPartExecutor(self, test_case, subTest=False):
        old_success = self.success
        self.success = True
        try:
>           yield

.../hostedtoolcache/Python/3.14.3.............../x64/lib/python3.14/unittest/case.py:58: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <authentik.root.tests.test_views.TestRoot testMethod=test_monitoring_ok>
result = <TestCaseFunction test_monitoring_ok>

    def run(self, result=None):
        if result is None:
            result = self.defaultTestResult()
            startTestRun = getattr(result, 'startTestRun', None)
            stopTestRun = getattr(result, 'stopTestRun', None)
            if startTestRun is not None:
                startTestRun()
        else:
            stopTestRun = None
    
        result.startTest(self)
        try:
            testMethod = getattr(self, self._testMethodName)
            if (getattr(self.__class__, "__unittest_skip__", False) or
                getattr(testMethod, "__unittest_skip__", False)):
                # If the class or method was skipped.
                skip_why = (getattr(self.__class__, '__unittest_skip_why__', '')
                            or getattr(testMethod, '__unittest_skip_why__', ''))
                _addSkip(result, self, skip_why)
                return result
    
            expecting_failure = (
                getattr(self, "__unittest_expecting_failure__", False) or
                getattr(testMethod, "__unittest_expecting_failure__", False)
            )
            outcome = _Outcome(result)
            start_time = time.perf_counter()
            try:
                self._outcome = outcome
    
                with outcome.testPartExecutor(self):
                    self._callSetUp()
                if outcome.success:
                    outcome.expecting_failure = expecting_failure
                    with outcome.testPartExecutor(self):
>                       self._callTestMethod(testMethod)

.../hostedtoolcache/Python/3.14.3.............../x64/lib/python3.14/unittest/case.py:669: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <authentik.root.tests.test_views.TestRoot testMethod=test_monitoring_ok>
method = <bound method TestRoot.test_monitoring_ok of <authentik.root.tests.test_views.TestRoot testMethod=test_monitoring_ok>>

    def _callTestMethod(self, method):
>       result = method()
                 ^^^^^^^^

.../hostedtoolcache/Python/3.14.3.............../x64/lib/python3.14/unittest/case.py:615: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <authentik.root.tests.test_views.TestRoot testMethod=test_monitoring_ok>

    def test_monitoring_ok(self):
        """Test monitoring with credentials"""
        auth_headers = {"HTTP_AUTHORIZATION": f"Bearer {self.token}"}
        response = self.client.get(reverse("metrics"), **auth_headers)
>       self.assertEqual(response.status_code, 200)

.../root/tests/test_views.py:33: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <authentik.root.tests.test_views.TestRoot testMethod=test_monitoring_ok>
first = 204, second = 200, msg = None

    def assertEqual(self, first, second, msg=None):
        """Fail if the two objects are unequal as determined by the '=='
           operator.
        """
        assertion_func = self._getAssertEqualityFunc(first, second)
>       assertion_func(first, second, msg=msg)

.../hostedtoolcache/Python/3.14.3.............../x64/lib/python3.14/unittest/case.py:925: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <authentik.root.tests.test_views.TestRoot testMethod=test_monitoring_ok>
first = 204, second = 200, msg = '204 != 200'

    def _baseAssertEqual(self, first, second, msg=None):
        """The default assertEqual implementation, not type specific."""
        if not first == second:
            standardMsg = '%s != %s' % _common_shorten_repr(first, second)
            msg = self._formatMessage(msg, standardMsg)
>           raise self.failureException(msg)
E           AssertionError: 204 != 200

.../hostedtoolcache/Python/3.14.3.............../x64/lib/python3.14/unittest/case.py:918: AssertionError

To view more test analytics, go to the Test Analytics Dashboard
📋 Got 3 mins? Take this short survey to help us improve Test Analytics.

rissson added 8 commits March 16, 2026 14:09
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
rissson and others added 15 commits March 19, 2026 14:22
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
Co-authored-by: Tana M Berry <tanamarieberry@yahoo.com>
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
…rver

Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: In Progress

Development

Successfully merging this pull request may close these issues.

2 participants