Skip to content

outposts: update permissions more eagerly#17783

Merged
BeryJu merged 10 commits intomainfrom
sdko/fix-embedded-outpost-env-var
Oct 30, 2025
Merged

outposts: update permissions more eagerly#17783
BeryJu merged 10 commits intomainfrom
sdko/fix-embedded-outpost-env-var

Conversation

@dominic-r
Copy link
Member

@dominic-r dominic-r commented Oct 29, 2025

@netlify
Copy link

netlify bot commented Oct 29, 2025

Deploy Preview for authentik-docs canceled.

Name Link
🔨 Latest commit 2d98bab
🔍 Latest deploy log https://app.netlify.com/projects/authentik-docs/deploys/69037aa502c9180008e5443f

@netlify
Copy link

netlify bot commented Oct 29, 2025

Deploy Preview for authentik-storybook canceled.

Name Link
🔨 Latest commit 2d98bab
🔍 Latest deploy log https://app.netlify.com/projects/authentik-storybook/deploys/69037aa5dc7df50008271b8b

@netlify
Copy link

netlify bot commented Oct 29, 2025

Deploy Preview for authentik-integrations canceled.

Name Link
🔨 Latest commit 2d98bab
🔍 Latest deploy log https://app.netlify.com/projects/authentik-integrations/deploys/69037aa5f7d68b0008cf4632

@dominic-r dominic-r self-assigned this Oct 29, 2025
@dominic-r dominic-r requested a review from BeryJu October 29, 2025 01:30
@dominic-r dominic-r added area:backend backport/version-2025.10 Add this label to PRs to backport changes to version-2025.10 labels Oct 29, 2025
@dominic-r dominic-r moved this from Todo to Needs review in authentik Core Oct 29, 2025
@dominic-r dominic-r added this to the Release 2025.10.1 milestone Oct 29, 2025
@codecov
Copy link

codecov bot commented Oct 29, 2025

❌ 6 Tests Failed:

Tests completed Failed Passed Skipped
2195 6 2189 2
View the top 3 failed test(s) by shortest run time
authentik.tenants.tests.test_api.TestAPI::test_tenant_create_delete
Stack Traces | 10.6s run time
self = <DatabaseWrapper vendor='postgresql' alias='default'>

    def _commit(self):
        if self.connection is not None:
            with debug_transaction(self, "COMMIT"), self.wrap_database_errors:
>               return self.connection.commit()

.venv/lib/python3.13.../backends/base/base.py:303: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <psycopg.Connection [BAD] at 0x7f0770f5b200>

    def commit(self) -> None:
        """Commit any pending transaction to the database."""
        with self.lock:
>           self.wait(self._commit_gen())

.venv/lib/python3.13............/site-packages/psycopg/connection.py:262: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <psycopg.Connection [BAD] at 0x7f0770f5b200>
gen = <generator object BaseConnection._commit_gen at 0x7f074ece65c0>
interval = 0.1

    def wait(self, gen: PQGen[RV], interval: float | None = _WAIT_INTERVAL) -> RV:
        """
        Consume a generator operating on the connection.
    
        The function must be used on generators that don't change connection
        fd (i.e. not on connect and reset).
        """
        try:
>           return waiting.wait(gen, self.pgconn.socket, interval=interval)

.venv/lib/python3.13............/site-packages/psycopg/connection.py:414: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

>   ???

psycopg_c/_psycopg/waiting.pyx:213: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <psycopg.Connection [BAD] at 0x7f0770f5b200>

    def _commit_gen(self) -> PQGen[None]:
        """Generator implementing `Connection.commit()`."""
        if self._num_transactions:
            raise e.ProgrammingError(
                "Explicit commit() forbidden within a Transaction "
                "context. (Transaction will be automatically committed "
                "on successful exit from context.)"
            )
        if self._tpc:
            raise e.ProgrammingError(
                "commit() cannot be used during a two-phase transaction"
            )
        if self.pgconn.transaction_status == IDLE:
            return
    
>       yield from self._exec_command(b"COMMIT")

.venv/lib/python3.13............/site-packages/psycopg/_connection_base.py:580: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <psycopg.Connection [BAD] at 0x7f0770f5b200>, command = b'COMMIT'
result_format = <Format.TEXT: 0>

    def _exec_command(
        self, command: Query, result_format: pq.Format = TEXT
    ) -> PQGen[PGresult | None]:
        """
        Generator to send a command and receive the result to the backend.
    
        Only used to implement internal commands such as "commit", with eventual
        arguments bound client-side. The cursor can do more complex stuff.
        """
        self._check_connection_ok()
    
        if isinstance(command, str):
            command = command.encode(self.pgconn._encoding)
        elif isinstance(command, Composable):
            command = command.as_bytes(self)
    
        if self._pipeline:
            cmd = partial(
                self.pgconn.send_query_params,
                command,
                None,
                result_format=result_format,
            )
            self._pipeline.command_queue.append(cmd)
            self._pipeline.result_queue.append(None)
            return None
    
        # Unless needed, use the simple query protocol, e.g. to interact with
        # pgbouncer. In pipeline mode we always use the advanced query protocol
        # instead, see #350
        if result_format == TEXT:
            self.pgconn.send_query(command)
        else:
            self.pgconn.send_query_params(command, None, result_format=result_format)
    
        result: PGresult = (yield from generators.execute(self.pgconn))[-1]
        if result.status != COMMAND_OK and result.status != TUPLES_OK:
            if result.status == FATAL_ERROR:
>               raise e.error_from_result(result, encoding=self.pgconn._encoding)
E               psycopg.errors.ForeignKeyViolation: insert or update on table "authentik_core_token" violates foreign key constraint "authentik_core_token_user_id_479d5b79_fk_authentik_core_user_id"
E               DETAIL:  Key (user_id)=(2) is not present in table "authentik_core_user".

.venv/lib/python3.13............/site-packages/psycopg/_connection_base.py:478: ForeignKeyViolation

The above exception was the direct cause of the following exception:

self = <unittest.case._Outcome object at 0x7f0777a7b620>
test_case = <authentik.tenants.tests.test_api.TestAPI testMethod=test_tenant_create_delete>
subTest = False

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

.../hostedtoolcache/Python/3.13.7........./x64/lib/python3.13/unittest/case.py:58: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <authentik.tenants.tests.test_api.TestAPI testMethod=test_tenant_create_delete>
result = <TestCaseFunction test_tenant_create_delete>

    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()

.../hostedtoolcache/Python/3.13.7........./x64/lib/python3.13/unittest/case.py:647: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <authentik.tenants.tests.test_api.TestAPI testMethod=test_tenant_create_delete>

    def _callSetUp(self):
>       self.setUp()

.../hostedtoolcache/Python/3.13.7........./x64/lib/python3.13/unittest/case.py:603: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <authentik.tenants.tests.test_api.TestAPI testMethod=test_tenant_create_delete>

    def setUp(self):
        with schema_context(get_public_schema_name()):
>           Tenant.objects.update_or_create(
                defaults={"name": "Template", "ready": False},
                schema_name=get_tenant_base_schema(),
            )

.../tenants/tests/utils.py:24: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <django.db.models.manager.Manager object at 0x7f076362d5d0>, args = ()
kwargs = {'defaults': {'name': 'Template', 'ready': False}, 'schema_name': 'template'}

    @wraps(method)
    def manager_method(self, *args, **kwargs):
>       return getattr(self.get_queryset(), name)(*args, **kwargs)

.venv/lib/python3.13.../db/models/manager.py:87: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <QuerySet [<Tenant: Tenant Default>]>
defaults = {'name': 'Template', 'ready': False}
create_defaults = {'name': 'Template', 'ready': False}
kwargs = {'schema_name': 'template'}
update_defaults = {'name': 'Template', 'ready': False}
obj = <Tenant: Tenant Template>, created = True

    def update_or_create(self, defaults=None, create_defaults=None, **kwargs):
        """
        Look up an object with the given kwargs, updating one with defaults
        if it exists, otherwise create a new one. Optionally, an object can
        be created with different values than defaults by using
        create_defaults.
        Return a tuple (object, created), where created is a boolean
        specifying whether an object was created.
        """
        update_defaults = defaults or {}
        if create_defaults is None:
            create_defaults = update_defaults
    
        self._for_write = True
>       with transaction.atomic(using=self.db):

.venv/lib/python3.13.../db/models/query.py:985: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <django.db.transaction.Atomic object at 0x7f0763680d70>, exc_type = None
exc_value = None, traceback = None

    def __exit__(self, exc_type, exc_value, traceback):
        connection = get_connection(self.using)
    
        if connection.in_atomic_block:
            connection.atomic_blocks.pop()
    
        if connection.savepoint_ids:
            sid = connection.savepoint_ids.pop()
        else:
            # Prematurely unset this flag to allow using commit or rollback.
            connection.in_atomic_block = False
    
        try:
            if connection.closed_in_transaction:
                # The database will perform a rollback by itself.
                # Wait until we exit the outermost block.
                pass
    
            elif exc_type is None and not connection.needs_rollback:
                if connection.in_atomic_block:
                    # Release savepoint if there is one
                    if sid is not None:
                        try:
                            connection.savepoint_commit(sid)
                        except DatabaseError:
                            try:
                                connection.savepoint_rollback(sid)
                                # The savepoint won't be reused. Release it to
                                # minimize overhead for the database server.
                                connection.savepoint_commit(sid)
                            except Error:
                                # If rolling back to a savepoint fails, mark for
                                # rollback at a higher level and avoid shadowing
                                # the original exception.
                                connection.needs_rollback = True
                            raise
                else:
                    # Commit transaction
                    try:
>                       connection.commit()

.venv/lib/python3.13.../django/db/transaction.py:263: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

args = (<DatabaseWrapper vendor='postgresql' alias='default'>,), kwargs = {}

    @wraps(func)
    def inner(*args, **kwargs):
        # Detect a running event loop in this thread.
        try:
            get_running_loop()
        except RuntimeError:
            pass
        else:
            if not os.environ.get("DJANGO_ALLOW_ASYNC_UNSAFE"):
                raise SynchronousOnlyOperation(message)
        # Pass onward.
>       return func(*args, **kwargs)

.venv/lib/python3.13.../django/utils/asyncio.py:26: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <DatabaseWrapper vendor='postgresql' alias='default'>

    @async_unsafe
    def commit(self):
        """Commit a transaction and reset the dirty flag."""
        self.validate_thread_sharing()
        self.validate_no_atomic_block()
>       self._commit()

.venv/lib/python3.13.../backends/base/base.py:327: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <DatabaseWrapper vendor='postgresql' alias='default'>

    def _commit(self):
        if self.connection is not None:
>           with debug_transaction(self, "COMMIT"), self.wrap_database_errors:

.venv/lib/python3.13.../backends/base/base.py:302: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <django.db.utils.DatabaseErrorWrapper object at 0x7f0774909010>
exc_type = <class 'psycopg.errors.ForeignKeyViolation'>
exc_value = ForeignKeyViolation('insert or update on table "authentik_core_token" violates foreign key constraint "authentik_core_...ser_id_479d5b79_fk_authentik_core_user_id"\nDETAIL:  Key (user_id)=(2) is not present in table "authentik_core_user".')
traceback = <traceback object at 0x7f0760ec5e80>

    def __exit__(self, exc_type, exc_value, traceback):
        if exc_type is None:
            return
        for dj_exc_type in (
            DataError,
            OperationalError,
            IntegrityError,
            InternalError,
            ProgrammingError,
            NotSupportedError,
            DatabaseError,
            InterfaceError,
            Error,
        ):
            db_exc_type = getattr(self.wrapper.Database, dj_exc_type.__name__)
            if issubclass(exc_type, db_exc_type):
                dj_exc_value = dj_exc_type(*exc_value.args)
                # Only set the 'errors_occurred' flag for errors that may make
                # the connection unusable.
                if dj_exc_type not in (DataError, IntegrityError):
                    self.wrapper.errors_occurred = True
>               raise dj_exc_value.with_traceback(traceback) from exc_value

.venv/lib/python3.13.../django/db/utils.py:91: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <DatabaseWrapper vendor='postgresql' alias='default'>

    def _commit(self):
        if self.connection is not None:
            with debug_transaction(self, "COMMIT"), self.wrap_database_errors:
>               return self.connection.commit()

.venv/lib/python3.13.../backends/base/base.py:303: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <psycopg.Connection [BAD] at 0x7f0770f5b200>

    def commit(self) -> None:
        """Commit any pending transaction to the database."""
        with self.lock:
>           self.wait(self._commit_gen())

.venv/lib/python3.13............/site-packages/psycopg/connection.py:262: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <psycopg.Connection [BAD] at 0x7f0770f5b200>
gen = <generator object BaseConnection._commit_gen at 0x7f074ece65c0>
interval = 0.1

    def wait(self, gen: PQGen[RV], interval: float | None = _WAIT_INTERVAL) -> RV:
        """
        Consume a generator operating on the connection.
    
        The function must be used on generators that don't change connection
        fd (i.e. not on connect and reset).
        """
        try:
>           return waiting.wait(gen, self.pgconn.socket, interval=interval)

.venv/lib/python3.13............/site-packages/psycopg/connection.py:414: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

>   ???

psycopg_c/_psycopg/waiting.pyx:213: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <psycopg.Connection [BAD] at 0x7f0770f5b200>

    def _commit_gen(self) -> PQGen[None]:
        """Generator implementing `Connection.commit()`."""
        if self._num_transactions:
            raise e.ProgrammingError(
                "Explicit commit() forbidden within a Transaction "
                "context. (Transaction will be automatically committed "
                "on successful exit from context.)"
            )
        if self._tpc:
            raise e.ProgrammingError(
                "commit() cannot be used during a two-phase transaction"
            )
        if self.pgconn.transaction_status == IDLE:
            return
    
>       yield from self._exec_command(b"COMMIT")

.venv/lib/python3.13............/site-packages/psycopg/_connection_base.py:580: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <psycopg.Connection [BAD] at 0x7f0770f5b200>, command = b'COMMIT'
result_format = <Format.TEXT: 0>

    def _exec_command(
        self, command: Query, result_format: pq.Format = TEXT
    ) -> PQGen[PGresult | None]:
        """
        Generator to send a command and receive the result to the backend.
    
        Only used to implement internal commands such as "commit", with eventual
        arguments bound client-side. The cursor can do more complex stuff.
        """
        self._check_connection_ok()
    
        if isinstance(command, str):
            command = command.encode(self.pgconn._encoding)
        elif isinstance(command, Composable):
            command = command.as_bytes(self)
    
        if self._pipeline:
            cmd = partial(
                self.pgconn.send_query_params,
                command,
                None,
                result_format=result_format,
            )
            self._pipeline.command_queue.append(cmd)
            self._pipeline.result_queue.append(None)
            return None
    
        # Unless needed, use the simple query protocol, e.g. to interact with
        # pgbouncer. In pipeline mode we always use the advanced query protocol
        # instead, see #350
        if result_format == TEXT:
            self.pgconn.send_query(command)
        else:
            self.pgconn.send_query_params(command, None, result_format=result_format)
    
        result: PGresult = (yield from generators.execute(self.pgconn))[-1]
        if result.status != COMMAND_OK and result.status != TUPLES_OK:
            if result.status == FATAL_ERROR:
>               raise e.error_from_result(result, encoding=self.pgconn._encoding)
E               django.db.utils.IntegrityError: insert or update on table "authentik_core_token" violates foreign key constraint "authentik_core_token_user_id_479d5b79_fk_authentik_core_user_id"
E               DETAIL:  Key (user_id)=(2) is not present in table "authentik_core_user".

.venv/lib/python3.13............/site-packages/psycopg/_connection_base.py:478: IntegrityError
authentik.tenants.tests.test_domain.TestDomainAPI::test_domain
Stack Traces | 10.7s run time
self = <DatabaseWrapper vendor='postgresql' alias='default'>

    def _commit(self):
        if self.connection is not None:
            with debug_transaction(self, "COMMIT"), self.wrap_database_errors:
>               return self.connection.commit()

.venv/lib/python3.13.../backends/base/base.py:303: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <psycopg.Connection [BAD] at 0x7f0770f58f50>

    def commit(self) -> None:
        """Commit any pending transaction to the database."""
        with self.lock:
>           self.wait(self._commit_gen())

.venv/lib/python3.13............/site-packages/psycopg/connection.py:262: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <psycopg.Connection [BAD] at 0x7f0770f58f50>
gen = <generator object BaseConnection._commit_gen at 0x7f0770b56ec0>
interval = 0.1

    def wait(self, gen: PQGen[RV], interval: float | None = _WAIT_INTERVAL) -> RV:
        """
        Consume a generator operating on the connection.
    
        The function must be used on generators that don't change connection
        fd (i.e. not on connect and reset).
        """
        try:
>           return waiting.wait(gen, self.pgconn.socket, interval=interval)

.venv/lib/python3.13............/site-packages/psycopg/connection.py:414: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

>   ???

psycopg_c/_psycopg/waiting.pyx:213: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <psycopg.Connection [BAD] at 0x7f0770f58f50>

    def _commit_gen(self) -> PQGen[None]:
        """Generator implementing `Connection.commit()`."""
        if self._num_transactions:
            raise e.ProgrammingError(
                "Explicit commit() forbidden within a Transaction "
                "context. (Transaction will be automatically committed "
                "on successful exit from context.)"
            )
        if self._tpc:
            raise e.ProgrammingError(
                "commit() cannot be used during a two-phase transaction"
            )
        if self.pgconn.transaction_status == IDLE:
            return
    
>       yield from self._exec_command(b"COMMIT")

.venv/lib/python3.13............/site-packages/psycopg/_connection_base.py:580: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <psycopg.Connection [BAD] at 0x7f0770f58f50>, command = b'COMMIT'
result_format = <Format.TEXT: 0>

    def _exec_command(
        self, command: Query, result_format: pq.Format = TEXT
    ) -> PQGen[PGresult | None]:
        """
        Generator to send a command and receive the result to the backend.
    
        Only used to implement internal commands such as "commit", with eventual
        arguments bound client-side. The cursor can do more complex stuff.
        """
        self._check_connection_ok()
    
        if isinstance(command, str):
            command = command.encode(self.pgconn._encoding)
        elif isinstance(command, Composable):
            command = command.as_bytes(self)
    
        if self._pipeline:
            cmd = partial(
                self.pgconn.send_query_params,
                command,
                None,
                result_format=result_format,
            )
            self._pipeline.command_queue.append(cmd)
            self._pipeline.result_queue.append(None)
            return None
    
        # Unless needed, use the simple query protocol, e.g. to interact with
        # pgbouncer. In pipeline mode we always use the advanced query protocol
        # instead, see #350
        if result_format == TEXT:
            self.pgconn.send_query(command)
        else:
            self.pgconn.send_query_params(command, None, result_format=result_format)
    
        result: PGresult = (yield from generators.execute(self.pgconn))[-1]
        if result.status != COMMAND_OK and result.status != TUPLES_OK:
            if result.status == FATAL_ERROR:
>               raise e.error_from_result(result, encoding=self.pgconn._encoding)
E               psycopg.errors.ForeignKeyViolation: insert or update on table "authentik_core_token" violates foreign key constraint "authentik_core_token_user_id_479d5b79_fk_authentik_core_user_id"
E               DETAIL:  Key (user_id)=(2) is not present in table "authentik_core_user".

.venv/lib/python3.13............/site-packages/psycopg/_connection_base.py:478: ForeignKeyViolation

The above exception was the direct cause of the following exception:

self = <unittest.case._Outcome object at 0x7f0769e9e900>
test_case = <authentik.tenants.tests.test_domain.TestDomainAPI testMethod=test_domain>
subTest = False

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

.../hostedtoolcache/Python/3.13.7........./x64/lib/python3.13/unittest/case.py:58: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <authentik.tenants.tests.test_domain.TestDomainAPI testMethod=test_domain>
result = <TestCaseFunction test_domain>

    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()

.../hostedtoolcache/Python/3.13.7........./x64/lib/python3.13/unittest/case.py:647: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <authentik.tenants.tests.test_domain.TestDomainAPI testMethod=test_domain>

    def _callSetUp(self):
>       self.setUp()

.../hostedtoolcache/Python/3.13.7........./x64/lib/python3.13/unittest/case.py:603: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <authentik.tenants.tests.test_domain.TestDomainAPI testMethod=test_domain>

    def setUp(self):
>       super().setUp()

.../tenants/tests/test_domain.py:18: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <authentik.tenants.tests.test_domain.TestDomainAPI testMethod=test_domain>

    def setUp(self):
        with schema_context(get_public_schema_name()):
>           Tenant.objects.update_or_create(
                defaults={"name": "Template", "ready": False},
                schema_name=get_tenant_base_schema(),
            )

.../tenants/tests/utils.py:24: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <django.db.models.manager.Manager object at 0x7f076362d5d0>, args = ()
kwargs = {'defaults': {'name': 'Template', 'ready': False}, 'schema_name': 'template'}

    @wraps(method)
    def manager_method(self, *args, **kwargs):
>       return getattr(self.get_queryset(), name)(*args, **kwargs)

.venv/lib/python3.13.../db/models/manager.py:87: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <QuerySet [<Tenant: Tenant Default>]>
defaults = {'name': 'Template', 'ready': False}
create_defaults = {'name': 'Template', 'ready': False}
kwargs = {'schema_name': 'template'}
update_defaults = {'name': 'Template', 'ready': False}
obj = <Tenant: Tenant Template>, created = True

    def update_or_create(self, defaults=None, create_defaults=None, **kwargs):
        """
        Look up an object with the given kwargs, updating one with defaults
        if it exists, otherwise create a new one. Optionally, an object can
        be created with different values than defaults by using
        create_defaults.
        Return a tuple (object, created), where created is a boolean
        specifying whether an object was created.
        """
        update_defaults = defaults or {}
        if create_defaults is None:
            create_defaults = update_defaults
    
        self._for_write = True
>       with transaction.atomic(using=self.db):

.venv/lib/python3.13.../db/models/query.py:985: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <django.db.transaction.Atomic object at 0x7f0763680c20>, exc_type = None
exc_value = None, traceback = None

    def __exit__(self, exc_type, exc_value, traceback):
        connection = get_connection(self.using)
    
        if connection.in_atomic_block:
            connection.atomic_blocks.pop()
    
        if connection.savepoint_ids:
            sid = connection.savepoint_ids.pop()
        else:
            # Prematurely unset this flag to allow using commit or rollback.
            connection.in_atomic_block = False
    
        try:
            if connection.closed_in_transaction:
                # The database will perform a rollback by itself.
                # Wait until we exit the outermost block.
                pass
    
            elif exc_type is None and not connection.needs_rollback:
                if connection.in_atomic_block:
                    # Release savepoint if there is one
                    if sid is not None:
                        try:
                            connection.savepoint_commit(sid)
                        except DatabaseError:
                            try:
                                connection.savepoint_rollback(sid)
                                # The savepoint won't be reused. Release it to
                                # minimize overhead for the database server.
                                connection.savepoint_commit(sid)
                            except Error:
                                # If rolling back to a savepoint fails, mark for
                                # rollback at a higher level and avoid shadowing
                                # the original exception.
                                connection.needs_rollback = True
                            raise
                else:
                    # Commit transaction
                    try:
>                       connection.commit()

.venv/lib/python3.13.../django/db/transaction.py:263: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

args = (<DatabaseWrapper vendor='postgresql' alias='default'>,), kwargs = {}

    @wraps(func)
    def inner(*args, **kwargs):
        # Detect a running event loop in this thread.
        try:
            get_running_loop()
        except RuntimeError:
            pass
        else:
            if not os.environ.get("DJANGO_ALLOW_ASYNC_UNSAFE"):
                raise SynchronousOnlyOperation(message)
        # Pass onward.
>       return func(*args, **kwargs)

.venv/lib/python3.13.../django/utils/asyncio.py:26: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <DatabaseWrapper vendor='postgresql' alias='default'>

    @async_unsafe
    def commit(self):
        """Commit a transaction and reset the dirty flag."""
        self.validate_thread_sharing()
        self.validate_no_atomic_block()
>       self._commit()

.venv/lib/python3.13.../backends/base/base.py:327: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <DatabaseWrapper vendor='postgresql' alias='default'>

    def _commit(self):
        if self.connection is not None:
>           with debug_transaction(self, "COMMIT"), self.wrap_database_errors:

.venv/lib/python3.13.../backends/base/base.py:302: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <django.db.utils.DatabaseErrorWrapper object at 0x7f0774909010>
exc_type = <class 'psycopg.errors.ForeignKeyViolation'>
exc_value = ForeignKeyViolation('insert or update on table "authentik_core_token" violates foreign key constraint "authentik_core_...ser_id_479d5b79_fk_authentik_core_user_id"\nDETAIL:  Key (user_id)=(2) is not present in table "authentik_core_user".')
traceback = <traceback object at 0x7f0770d81b80>

    def __exit__(self, exc_type, exc_value, traceback):
        if exc_type is None:
            return
        for dj_exc_type in (
            DataError,
            OperationalError,
            IntegrityError,
            InternalError,
            ProgrammingError,
            NotSupportedError,
            DatabaseError,
            InterfaceError,
            Error,
        ):
            db_exc_type = getattr(self.wrapper.Database, dj_exc_type.__name__)
            if issubclass(exc_type, db_exc_type):
                dj_exc_value = dj_exc_type(*exc_value.args)
                # Only set the 'errors_occurred' flag for errors that may make
                # the connection unusable.
                if dj_exc_type not in (DataError, IntegrityError):
                    self.wrapper.errors_occurred = True
>               raise dj_exc_value.with_traceback(traceback) from exc_value

.venv/lib/python3.13.../django/db/utils.py:91: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <DatabaseWrapper vendor='postgresql' alias='default'>

    def _commit(self):
        if self.connection is not None:
            with debug_transaction(self, "COMMIT"), self.wrap_database_errors:
>               return self.connection.commit()

.venv/lib/python3.13.../backends/base/base.py:303: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <psycopg.Connection [BAD] at 0x7f0770f58f50>

    def commit(self) -> None:
        """Commit any pending transaction to the database."""
        with self.lock:
>           self.wait(self._commit_gen())

.venv/lib/python3.13............/site-packages/psycopg/connection.py:262: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <psycopg.Connection [BAD] at 0x7f0770f58f50>
gen = <generator object BaseConnection._commit_gen at 0x7f0770b56ec0>
interval = 0.1

    def wait(self, gen: PQGen[RV], interval: float | None = _WAIT_INTERVAL) -> RV:
        """
        Consume a generator operating on the connection.
    
        The function must be used on generators that don't change connection
        fd (i.e. not on connect and reset).
        """
        try:
>           return waiting.wait(gen, self.pgconn.socket, interval=interval)

.venv/lib/python3.13............/site-packages/psycopg/connection.py:414: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

>   ???

psycopg_c/_psycopg/waiting.pyx:213: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <psycopg.Connection [BAD] at 0x7f0770f58f50>

    def _commit_gen(self) -> PQGen[None]:
        """Generator implementing `Connection.commit()`."""
        if self._num_transactions:
            raise e.ProgrammingError(
                "Explicit commit() forbidden within a Transaction "
                "context. (Transaction will be automatically committed "
                "on successful exit from context.)"
            )
        if self._tpc:
            raise e.ProgrammingError(
                "commit() cannot be used during a two-phase transaction"
            )
        if self.pgconn.transaction_status == IDLE:
            return
    
>       yield from self._exec_command(b"COMMIT")

.venv/lib/python3.13............/site-packages/psycopg/_connection_base.py:580: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <psycopg.Connection [BAD] at 0x7f0770f58f50>, command = b'COMMIT'
result_format = <Format.TEXT: 0>

    def _exec_command(
        self, command: Query, result_format: pq.Format = TEXT
    ) -> PQGen[PGresult | None]:
        """
        Generator to send a command and receive the result to the backend.
    
        Only used to implement internal commands such as "commit", with eventual
        arguments bound client-side. The cursor can do more complex stuff.
        """
        self._check_connection_ok()
    
        if isinstance(command, str):
            command = command.encode(self.pgconn._encoding)
        elif isinstance(command, Composable):
            command = command.as_bytes(self)
    
        if self._pipeline:
            cmd = partial(
                self.pgconn.send_query_params,
                command,
                None,
                result_format=result_format,
            )
            self._pipeline.command_queue.append(cmd)
            self._pipeline.result_queue.append(None)
            return None
    
        # Unless needed, use the simple query protocol, e.g. to interact with
        # pgbouncer. In pipeline mode we always use the advanced query protocol
        # instead, see #350
        if result_format == TEXT:
            self.pgconn.send_query(command)
        else:
            self.pgconn.send_query_params(command, None, result_format=result_format)
    
        result: PGresult = (yield from generators.execute(self.pgconn))[-1]
        if result.status != COMMAND_OK and result.status != TUPLES_OK:
            if result.status == FATAL_ERROR:
>               raise e.error_from_result(result, encoding=self.pgconn._encoding)
E               django.db.utils.IntegrityError: insert or update on table "authentik_core_token" violates foreign key constraint "authentik_core_token_user_id_479d5b79_fk_authentik_core_user_id"
E               DETAIL:  Key (user_id)=(2) is not present in table "authentik_core_user".

.venv/lib/python3.13............/site-packages/psycopg/_connection_base.py:478: IntegrityError
authentik.tenants.tests.test_recovery.TestRecovery::test_recovery_admin_group_invalid
Stack Traces | 10.8s run time
self = <DatabaseWrapper vendor='postgresql' alias='default'>

    def _commit(self):
        if self.connection is not None:
            with debug_transaction(self, "COMMIT"), self.wrap_database_errors:
>               return self.connection.commit()

.venv/lib/python3.13.../backends/base/base.py:303: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <psycopg.Connection [BAD] at 0x7f0770ef6a80>

    def commit(self) -> None:
        """Commit any pending transaction to the database."""
        with self.lock:
>           self.wait(self._commit_gen())

.venv/lib/python3.13............/site-packages/psycopg/connection.py:262: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <psycopg.Connection [BAD] at 0x7f0770ef6a80>
gen = <generator object BaseConnection._commit_gen at 0x7f0770179600>
interval = 0.1

    def wait(self, gen: PQGen[RV], interval: float | None = _WAIT_INTERVAL) -> RV:
        """
        Consume a generator operating on the connection.
    
        The function must be used on generators that don't change connection
        fd (i.e. not on connect and reset).
        """
        try:
>           return waiting.wait(gen, self.pgconn.socket, interval=interval)

.venv/lib/python3.13............/site-packages/psycopg/connection.py:414: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

>   ???

psycopg_c/_psycopg/waiting.pyx:213: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <psycopg.Connection [BAD] at 0x7f0770ef6a80>

    def _commit_gen(self) -> PQGen[None]:
        """Generator implementing `Connection.commit()`."""
        if self._num_transactions:
            raise e.ProgrammingError(
                "Explicit commit() forbidden within a Transaction "
                "context. (Transaction will be automatically committed "
                "on successful exit from context.)"
            )
        if self._tpc:
            raise e.ProgrammingError(
                "commit() cannot be used during a two-phase transaction"
            )
        if self.pgconn.transaction_status == IDLE:
            return
    
>       yield from self._exec_command(b"COMMIT")

.venv/lib/python3.13............/site-packages/psycopg/_connection_base.py:580: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <psycopg.Connection [BAD] at 0x7f0770ef6a80>, command = b'COMMIT'
result_format = <Format.TEXT: 0>

    def _exec_command(
        self, command: Query, result_format: pq.Format = TEXT
    ) -> PQGen[PGresult | None]:
        """
        Generator to send a command and receive the result to the backend.
    
        Only used to implement internal commands such as "commit", with eventual
        arguments bound client-side. The cursor can do more complex stuff.
        """
        self._check_connection_ok()
    
        if isinstance(command, str):
            command = command.encode(self.pgconn._encoding)
        elif isinstance(command, Composable):
            command = command.as_bytes(self)
    
        if self._pipeline:
            cmd = partial(
                self.pgconn.send_query_params,
                command,
                None,
                result_format=result_format,
            )
            self._pipeline.command_queue.append(cmd)
            self._pipeline.result_queue.append(None)
            return None
    
        # Unless needed, use the simple query protocol, e.g. to interact with
        # pgbouncer. In pipeline mode we always use the advanced query protocol
        # instead, see #350
        if result_format == TEXT:
            self.pgconn.send_query(command)
        else:
            self.pgconn.send_query_params(command, None, result_format=result_format)
    
        result: PGresult = (yield from generators.execute(self.pgconn))[-1]
        if result.status != COMMAND_OK and result.status != TUPLES_OK:
            if result.status == FATAL_ERROR:
>               raise e.error_from_result(result, encoding=self.pgconn._encoding)
E               psycopg.errors.ForeignKeyViolation: insert or update on table "authentik_core_token" violates foreign key constraint "authentik_core_token_user_id_479d5b79_fk_authentik_core_user_id"
E               DETAIL:  Key (user_id)=(2) is not present in table "authentik_core_user".

.venv/lib/python3.13............/site-packages/psycopg/_connection_base.py:478: ForeignKeyViolation

The above exception was the direct cause of the following exception:

self = <unittest.case._Outcome object at 0x7f07629ffcb0>
test_case = <authentik.tenants.tests.test_recovery.TestRecovery testMethod=test_recovery_admin_group_invalid>
subTest = False

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

.../hostedtoolcache/Python/3.13.7........./x64/lib/python3.13/unittest/case.py:58: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <authentik.tenants.tests.test_recovery.TestRecovery testMethod=test_recovery_admin_group_invalid>
result = <TestCaseFunction test_recovery_admin_group_invalid>

    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()

.../hostedtoolcache/Python/3.13.7........./x64/lib/python3.13/unittest/case.py:647: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <authentik.tenants.tests.test_recovery.TestRecovery testMethod=test_recovery_admin_group_invalid>

    def _callSetUp(self):
>       self.setUp()

.../hostedtoolcache/Python/3.13.7........./x64/lib/python3.13/unittest/case.py:603: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <authentik.tenants.tests.test_recovery.TestRecovery testMethod=test_recovery_admin_group_invalid>

    def setUp(self):
>       super().setUp()

.../tenants/tests/test_recovery.py:22: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <authentik.tenants.tests.test_recovery.TestRecovery testMethod=test_recovery_admin_group_invalid>

    def setUp(self):
        with schema_context(get_public_schema_name()):
>           Tenant.objects.update_or_create(
                defaults={"name": "Template", "ready": False},
                schema_name=get_tenant_base_schema(),
            )

.../tenants/tests/utils.py:24: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <django.db.models.manager.Manager object at 0x7f076362d5d0>, args = ()
kwargs = {'defaults': {'name': 'Template', 'ready': False}, 'schema_name': 'template'}

    @wraps(method)
    def manager_method(self, *args, **kwargs):
>       return getattr(self.get_queryset(), name)(*args, **kwargs)

.venv/lib/python3.13.../db/models/manager.py:87: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <QuerySet [<Tenant: Tenant Default>]>
defaults = {'name': 'Template', 'ready': False}
create_defaults = {'name': 'Template', 'ready': False}
kwargs = {'schema_name': 'template'}
update_defaults = {'name': 'Template', 'ready': False}
obj = <Tenant: Tenant Template>, created = True

    def update_or_create(self, defaults=None, create_defaults=None, **kwargs):
        """
        Look up an object with the given kwargs, updating one with defaults
        if it exists, otherwise create a new one. Optionally, an object can
        be created with different values than defaults by using
        create_defaults.
        Return a tuple (object, created), where created is a boolean
        specifying whether an object was created.
        """
        update_defaults = defaults or {}
        if create_defaults is None:
            create_defaults = update_defaults
    
        self._for_write = True
>       with transaction.atomic(using=self.db):

.venv/lib/python3.13.../db/models/query.py:985: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <django.db.transaction.Atomic object at 0x7f0769ca3540>, exc_type = None
exc_value = None, traceback = None

    def __exit__(self, exc_type, exc_value, traceback):
        connection = get_connection(self.using)
    
        if connection.in_atomic_block:
            connection.atomic_blocks.pop()
    
        if connection.savepoint_ids:
            sid = connection.savepoint_ids.pop()
        else:
            # Prematurely unset this flag to allow using commit or rollback.
            connection.in_atomic_block = False
    
        try:
            if connection.closed_in_transaction:
                # The database will perform a rollback by itself.
                # Wait until we exit the outermost block.
                pass
    
            elif exc_type is None and not connection.needs_rollback:
                if connection.in_atomic_block:
                    # Release savepoint if there is one
                    if sid is not None:
                        try:
                            connection.savepoint_commit(sid)
                        except DatabaseError:
                            try:
                                connection.savepoint_rollback(sid)
                                # The savepoint won't be reused. Release it to
                                # minimize overhead for the database server.
                                connection.savepoint_commit(sid)
                            except Error:
                                # If rolling back to a savepoint fails, mark for
                                # rollback at a higher level and avoid shadowing
                                # the original exception.
                                connection.needs_rollback = True
                            raise
                else:
                    # Commit transaction
                    try:
>                       connection.commit()

.venv/lib/python3.13.../django/db/transaction.py:263: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

args = (<DatabaseWrapper vendor='postgresql' alias='default'>,), kwargs = {}

    @wraps(func)
    def inner(*args, **kwargs):
        # Detect a running event loop in this thread.
        try:
            get_running_loop()
        except RuntimeError:
            pass
        else:
            if not os.environ.get("DJANGO_ALLOW_ASYNC_UNSAFE"):
                raise SynchronousOnlyOperation(message)
        # Pass onward.
>       return func(*args, **kwargs)

.venv/lib/python3.13.../django/utils/asyncio.py:26: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <DatabaseWrapper vendor='postgresql' alias='default'>

    @async_unsafe
    def commit(self):
        """Commit a transaction and reset the dirty flag."""
        self.validate_thread_sharing()
        self.validate_no_atomic_block()
>       self._commit()

.venv/lib/python3.13.../backends/base/base.py:327: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <DatabaseWrapper vendor='postgresql' alias='default'>

    def _commit(self):
        if self.connection is not None:
>           with debug_transaction(self, "COMMIT"), self.wrap_database_errors:

.venv/lib/python3.13.../backends/base/base.py:302: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <django.db.utils.DatabaseErrorWrapper object at 0x7f0774909010>
exc_type = <class 'psycopg.errors.ForeignKeyViolation'>
exc_value = ForeignKeyViolation('insert or update on table "authentik_core_token" violates foreign key constraint "authentik_core_...ser_id_479d5b79_fk_authentik_core_user_id"\nDETAIL:  Key (user_id)=(2) is not present in table "authentik_core_user".')
traceback = <traceback object at 0x7f0770a34540>

    def __exit__(self, exc_type, exc_value, traceback):
        if exc_type is None:
            return
        for dj_exc_type in (
            DataError,
            OperationalError,
            IntegrityError,
            InternalError,
            ProgrammingError,
            NotSupportedError,
            DatabaseError,
            InterfaceError,
            Error,
        ):
            db_exc_type = getattr(self.wrapper.Database, dj_exc_type.__name__)
            if issubclass(exc_type, db_exc_type):
                dj_exc_value = dj_exc_type(*exc_value.args)
                # Only set the 'errors_occurred' flag for errors that may make
                # the connection unusable.
                if dj_exc_type not in (DataError, IntegrityError):
                    self.wrapper.errors_occurred = True
>               raise dj_exc_value.with_traceback(traceback) from exc_value

.venv/lib/python3.13.../django/db/utils.py:91: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <DatabaseWrapper vendor='postgresql' alias='default'>

    def _commit(self):
        if self.connection is not None:
            with debug_transaction(self, "COMMIT"), self.wrap_database_errors:
>               return self.connection.commit()

.venv/lib/python3.13.../backends/base/base.py:303: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <psycopg.Connection [BAD] at 0x7f0770ef6a80>

    def commit(self) -> None:
        """Commit any pending transaction to the database."""
        with self.lock:
>           self.wait(self._commit_gen())

.venv/lib/python3.13............/site-packages/psycopg/connection.py:262: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <psycopg.Connection [BAD] at 0x7f0770ef6a80>
gen = <generator object BaseConnection._commit_gen at 0x7f0770179600>
interval = 0.1

    def wait(self, gen: PQGen[RV], interval: float | None = _WAIT_INTERVAL) -> RV:
        """
        Consume a generator operating on the connection.
    
        The function must be used on generators that don't change connection
        fd (i.e. not on connect and reset).
        """
        try:
>           return waiting.wait(gen, self.pgconn.socket, interval=interval)

.venv/lib/python3.13............/site-packages/psycopg/connection.py:414: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

>   ???

psycopg_c/_psycopg/waiting.pyx:213: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <psycopg.Connection [BAD] at 0x7f0770ef6a80>

    def _commit_gen(self) -> PQGen[None]:
        """Generator implementing `Connection.commit()`."""
        if self._num_transactions:
            raise e.ProgrammingError(
                "Explicit commit() forbidden within a Transaction "
                "context. (Transaction will be automatically committed "
                "on successful exit from context.)"
            )
        if self._tpc:
            raise e.ProgrammingError(
                "commit() cannot be used during a two-phase transaction"
            )
        if self.pgconn.transaction_status == IDLE:
            return
    
>       yield from self._exec_command(b"COMMIT")

.venv/lib/python3.13............/site-packages/psycopg/_connection_base.py:580: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <psycopg.Connection [BAD] at 0x7f0770ef6a80>, command = b'COMMIT'
result_format = <Format.TEXT: 0>

    def _exec_command(
        self, command: Query, result_format: pq.Format = TEXT
    ) -> PQGen[PGresult | None]:
        """
        Generator to send a command and receive the result to the backend.
    
        Only used to implement internal commands such as "commit", with eventual
        arguments bound client-side. The cursor can do more complex stuff.
        """
        self._check_connection_ok()
    
        if isinstance(command, str):
            command = command.encode(self.pgconn._encoding)
        elif isinstance(command, Composable):
            command = command.as_bytes(self)
    
        if self._pipeline:
            cmd = partial(
                self.pgconn.send_query_params,
                command,
                None,
                result_format=result_format,
            )
            self._pipeline.command_queue.append(cmd)
            self._pipeline.result_queue.append(None)
            return None
    
        # Unless needed, use the simple query protocol, e.g. to interact with
        # pgbouncer. In pipeline mode we always use the advanced query protocol
        # instead, see #350
        if result_format == TEXT:
            self.pgconn.send_query(command)
        else:
            self.pgconn.send_query_params(command, None, result_format=result_format)
    
        result: PGresult = (yield from generators.execute(self.pgconn))[-1]
        if result.status != COMMAND_OK and result.status != TUPLES_OK:
            if result.status == FATAL_ERROR:
>               raise e.error_from_result(result, encoding=self.pgconn._encoding)
E               django.db.utils.IntegrityError: insert or update on table "authentik_core_token" violates foreign key constraint "authentik_core_token_user_id_479d5b79_fk_authentik_core_user_id"
E               DETAIL:  Key (user_id)=(2) is not present in table "authentik_core_user".

.venv/lib/python3.13............/site-packages/psycopg/_connection_base.py:478: IntegrityError
authentik.tenants.tests.test_api.TestAPI::test_unauthenticated
Stack Traces | 10.8s run time
self = <DatabaseWrapper vendor='postgresql' alias='default'>

    def _commit(self):
        if self.connection is not None:
            with debug_transaction(self, "COMMIT"), self.wrap_database_errors:
>               return self.connection.commit()

.venv/lib/python3.13.../backends/base/base.py:303: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <psycopg.Connection [BAD] at 0x7f0761bc4b90>

    def commit(self) -> None:
        """Commit any pending transaction to the database."""
        with self.lock:
>           self.wait(self._commit_gen())

.venv/lib/python3.13............/site-packages/psycopg/connection.py:262: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <psycopg.Connection [BAD] at 0x7f0761bc4b90>
gen = <generator object BaseConnection._commit_gen at 0x7f0770b55180>
interval = 0.1

    def wait(self, gen: PQGen[RV], interval: float | None = _WAIT_INTERVAL) -> RV:
        """
        Consume a generator operating on the connection.
    
        The function must be used on generators that don't change connection
        fd (i.e. not on connect and reset).
        """
        try:
>           return waiting.wait(gen, self.pgconn.socket, interval=interval)

.venv/lib/python3.13............/site-packages/psycopg/connection.py:414: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

>   ???

psycopg_c/_psycopg/waiting.pyx:213: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <psycopg.Connection [BAD] at 0x7f0761bc4b90>

    def _commit_gen(self) -> PQGen[None]:
        """Generator implementing `Connection.commit()`."""
        if self._num_transactions:
            raise e.ProgrammingError(
                "Explicit commit() forbidden within a Transaction "
                "context. (Transaction will be automatically committed "
                "on successful exit from context.)"
            )
        if self._tpc:
            raise e.ProgrammingError(
                "commit() cannot be used during a two-phase transaction"
            )
        if self.pgconn.transaction_status == IDLE:
            return
    
>       yield from self._exec_command(b"COMMIT")

.venv/lib/python3.13............/site-packages/psycopg/_connection_base.py:580: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <psycopg.Connection [BAD] at 0x7f0761bc4b90>, command = b'COMMIT'
result_format = <Format.TEXT: 0>

    def _exec_command(
        self, command: Query, result_format: pq.Format = TEXT
    ) -> PQGen[PGresult | None]:
        """
        Generator to send a command and receive the result to the backend.
    
        Only used to implement internal commands such as "commit", with eventual
        arguments bound client-side. The cursor can do more complex stuff.
        """
        self._check_connection_ok()
    
        if isinstance(command, str):
            command = command.encode(self.pgconn._encoding)
        elif isinstance(command, Composable):
            command = command.as_bytes(self)
    
        if self._pipeline:
            cmd = partial(
                self.pgconn.send_query_params,
                command,
                None,
                result_format=result_format,
            )
            self._pipeline.command_queue.append(cmd)
            self._pipeline.result_queue.append(None)
            return None
    
        # Unless needed, use the simple query protocol, e.g. to interact with
        # pgbouncer. In pipeline mode we always use the advanced query protocol
        # instead, see #350
        if result_format == TEXT:
            self.pgconn.send_query(command)
        else:
            self.pgconn.send_query_params(command, None, result_format=result_format)
    
        result: PGresult = (yield from generators.execute(self.pgconn))[-1]
        if result.status != COMMAND_OK and result.status != TUPLES_OK:
            if result.status == FATAL_ERROR:
>               raise e.error_from_result(result, encoding=self.pgconn._encoding)
E               psycopg.errors.ForeignKeyViolation: insert or update on table "authentik_core_token" violates foreign key constraint "authentik_core_token_user_id_479d5b79_fk_authentik_core_user_id"
E               DETAIL:  Key (user_id)=(2) is not present in table "authentik_core_user".

.venv/lib/python3.13............/site-packages/psycopg/_connection_base.py:478: ForeignKeyViolation

The above exception was the direct cause of the following exception:

self = <unittest.case._Outcome object at 0x7f0763bda900>
test_case = <authentik.tenants.tests.test_api.TestAPI testMethod=test_unauthenticated>
subTest = False

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

.../hostedtoolcache/Python/3.13.7........./x64/lib/python3.13/unittest/case.py:58: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <authentik.tenants.tests.test_api.TestAPI testMethod=test_unauthenticated>
result = <TestCaseFunction test_unauthenticated>

    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()

.../hostedtoolcache/Python/3.13.7........./x64/lib/python3.13/unittest/case.py:647: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <authentik.tenants.tests.test_api.TestAPI testMethod=test_unauthenticated>

    def _callSetUp(self):
>       self.setUp()

.../hostedtoolcache/Python/3.13.7........./x64/lib/python3.13/unittest/case.py:603: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <authentik.tenants.tests.test_api.TestAPI testMethod=test_unauthenticated>

    def setUp(self):
        with schema_context(get_public_schema_name()):
>           Tenant.objects.update_or_create(
                defaults={"name": "Template", "ready": False},
                schema_name=get_tenant_base_schema(),
            )

.../tenants/tests/utils.py:24: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <django.db.models.manager.Manager object at 0x7f076362d5d0>, args = ()
kwargs = {'defaults': {'name': 'Template', 'ready': False}, 'schema_name': 'template'}

    @wraps(method)
    def manager_method(self, *args, **kwargs):
>       return getattr(self.get_queryset(), name)(*args, **kwargs)

.venv/lib/python3.13.../db/models/manager.py:87: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <QuerySet [<Tenant: Tenant Default>]>
defaults = {'name': 'Template', 'ready': False}
create_defaults = {'name': 'Template', 'ready': False}
kwargs = {'schema_name': 'template'}
update_defaults = {'name': 'Template', 'ready': False}
obj = <Tenant: Tenant Template>, created = True

    def update_or_create(self, defaults=None, create_defaults=None, **kwargs):
        """
        Look up an object with the given kwargs, updating one with defaults
        if it exists, otherwise create a new one. Optionally, an object can
        be created with different values than defaults by using
        create_defaults.
        Return a tuple (object, created), where created is a boolean
        specifying whether an object was created.
        """
        update_defaults = defaults or {}
        if create_defaults is None:
            create_defaults = update_defaults
    
        self._for_write = True
>       with transaction.atomic(using=self.db):

.venv/lib/python3.13.../db/models/query.py:985: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <django.db.transaction.Atomic object at 0x7f0763e822e0>, exc_type = None
exc_value = None, traceback = None

    def __exit__(self, exc_type, exc_value, traceback):
        connection = get_connection(self.using)
    
        if connection.in_atomic_block:
            connection.atomic_blocks.pop()
    
        if connection.savepoint_ids:
            sid = connection.savepoint_ids.pop()
        else:
            # Prematurely unset this flag to allow using commit or rollback.
            connection.in_atomic_block = False
    
        try:
            if connection.closed_in_transaction:
                # The database will perform a rollback by itself.
                # Wait until we exit the outermost block.
                pass
    
            elif exc_type is None and not connection.needs_rollback:
                if connection.in_atomic_block:
                    # Release savepoint if there is one
                    if sid is not None:
                        try:
                            connection.savepoint_commit(sid)
                        except DatabaseError:
                            try:
                                connection.savepoint_rollback(sid)
                                # The savepoint won't be reused. Release it to
                                # minimize overhead for the database server.
                                connection.savepoint_commit(sid)
                            except Error:
                                # If rolling back to a savepoint fails, mark for
                                # rollback at a higher level and avoid shadowing
                                # the original exception.
                                connection.needs_rollback = True
                            raise
                else:
                    # Commit transaction
                    try:
>                       connection.commit()

.venv/lib/python3.13.../django/db/transaction.py:263: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

args = (<DatabaseWrapper vendor='postgresql' alias='default'>,), kwargs = {}

    @wraps(func)
    def inner(*args, **kwargs):
        # Detect a running event loop in this thread.
        try:
            get_running_loop()
        except RuntimeError:
            pass
        else:
            if not os.environ.get("DJANGO_ALLOW_ASYNC_UNSAFE"):
                raise SynchronousOnlyOperation(message)
        # Pass onward.
>       return func(*args, **kwargs)

.venv/lib/python3.13.../django/utils/asyncio.py:26: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <DatabaseWrapper vendor='postgresql' alias='default'>

    @async_unsafe
    def commit(self):
        """Commit a transaction and reset the dirty flag."""
        self.validate_thread_sharing()
        self.validate_no_atomic_block()
>       self._commit()

.venv/lib/python3.13.../backends/base/base.py:327: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <DatabaseWrapper vendor='postgresql' alias='default'>

    def _commit(self):
        if self.connection is not None:
>           with debug_transaction(self, "COMMIT"), self.wrap_database_errors:

.venv/lib/python3.13.../backends/base/base.py:302: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <django.db.utils.DatabaseErrorWrapper object at 0x7f0774909010>
exc_type = <class 'psycopg.errors.ForeignKeyViolation'>
exc_value = ForeignKeyViolation('insert or update on table "authentik_core_token" violates foreign key constraint "authentik_core_...ser_id_479d5b79_fk_authentik_core_user_id"\nDETAIL:  Key (user_id)=(2) is not present in table "authentik_core_user".')
traceback = <traceback object at 0x7f0761f2c780>

    def __exit__(self, exc_type, exc_value, traceback):
        if exc_type is None:
            return
        for dj_exc_type in (
            DataError,
            OperationalError,
            IntegrityError,
            InternalError,
            ProgrammingError,
            NotSupportedError,
            DatabaseError,
            InterfaceError,
            Error,
        ):
            db_exc_type = getattr(self.wrapper.Database, dj_exc_type.__name__)
            if issubclass(exc_type, db_exc_type):
                dj_exc_value = dj_exc_type(*exc_value.args)
                # Only set the 'errors_occurred' flag for errors that may make
                # the connection unusable.
                if dj_exc_type not in (DataError, IntegrityError):
                    self.wrapper.errors_occurred = True
>               raise dj_exc_value.with_traceback(traceback) from exc_value

.venv/lib/python3.13.../django/db/utils.py:91: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <DatabaseWrapper vendor='postgresql' alias='default'>

    def _commit(self):
        if self.connection is not None:
            with debug_transaction(self, "COMMIT"), self.wrap_database_errors:
>               return self.connection.commit()

.venv/lib/python3.13.../backends/base/base.py:303: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <psycopg.Connection [BAD] at 0x7f0761bc4b90>

    def commit(self) -> None:
        """Commit any pending transaction to the database."""
        with self.lock:
>           self.wait(self._commit_gen())

.venv/lib/python3.13............/site-packages/psycopg/connection.py:262: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <psycopg.Connection [BAD] at 0x7f0761bc4b90>
gen = <generator object BaseConnection._commit_gen at 0x7f0770b55180>
interval = 0.1

    def wait(self, gen: PQGen[RV], interval: float | None = _WAIT_INTERVAL) -> RV:
        """
        Consume a generator operating on the connection.
    
        The function must be used on generators that don't change connection
        fd (i.e. not on connect and reset).
        """
        try:
>           return waiting.wait(gen, self.pgconn.socket, interval=interval)

.venv/lib/python3.13............/site-packages/psycopg/connection.py:414: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

>   ???

psycopg_c/_psycopg/waiting.pyx:213: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <psycopg.Connection [BAD] at 0x7f0761bc4b90>

    def _commit_gen(self) -> PQGen[None]:
        """Generator implementing `Connection.commit()`."""
        if self._num_transactions:
            raise e.ProgrammingError(
                "Explicit commit() forbidden within a Transaction "
                "context. (Transaction will be automatically committed "
                "on successful exit from context.)"
            )
        if self._tpc:
            raise e.ProgrammingError(
                "commit() cannot be used during a two-phase transaction"
            )
        if self.pgconn.transaction_status == IDLE:
            return
    
>       yield from self._exec_command(b"COMMIT")

.venv/lib/python3.13............/site-packages/psycopg/_connection_base.py:580: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <psycopg.Connection [BAD] at 0x7f0761bc4b90>, command = b'COMMIT'
result_format = <Format.TEXT: 0>

    def _exec_command(
        self, command: Query, result_format: pq.Format = TEXT
    ) -> PQGen[PGresult | None]:
        """
        Generator to send a command and receive the result to the backend.
    
        Only used to implement internal commands such as "commit", with eventual
        arguments bound client-side. The cursor can do more complex stuff.
        """
        self._check_connection_ok()
    
        if isinstance(command, str):
            command = command.encode(self.pgconn._encoding)
        elif isinstance(command, Composable):
            command = command.as_bytes(self)
    
        if self._pipeline:
            cmd = partial(
                self.pgconn.send_query_params,
                command,
                None,
                result_format=result_format,
            )
            self._pipeline.command_queue.append(cmd)
            self._pipeline.result_queue.append(None)
            return None
    
        # Unless needed, use the simple query protocol, e.g. to interact with
        # pgbouncer. In pipeline mode we always use the advanced query protocol
        # instead, see #350
        if result_format == TEXT:
            self.pgconn.send_query(command)
        else:
            self.pgconn.send_query_params(command, None, result_format=result_format)
    
        result: PGresult = (yield from generators.execute(self.pgconn))[-1]
        if result.status != COMMAND_OK and result.status != TUPLES_OK:
            if result.status == FATAL_ERROR:
>               raise e.error_from_result(result, encoding=self.pgconn._encoding)
E               django.db.utils.IntegrityError: insert or update on table "authentik_core_token" violates foreign key constraint "authentik_core_token_user_id_479d5b79_fk_authentik_core_user_id"
E               DETAIL:  Key (user_id)=(2) is not present in table "authentik_core_user".

.venv/lib/python3.13............/site-packages/psycopg/_connection_base.py:478: IntegrityError
authentik.tenants.tests.test_recovery.TestRecovery::test_create_key_invalid
Stack Traces | 11.1s run time
self = <DatabaseWrapper vendor='postgresql' alias='default'>

    def _commit(self):
        if self.connection is not None:
            with debug_transaction(self, "COMMIT"), self.wrap_database_errors:
>               return self.connection.commit()

.venv/lib/python3.13.../backends/base/base.py:303: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <psycopg.Connection [BAD] at 0x7f0770db89b0>

    def commit(self) -> None:
        """Commit any pending transaction to the database."""
        with self.lock:
>           self.wait(self._commit_gen())

.venv/lib/python3.13............/site-packages/psycopg/connection.py:262: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <psycopg.Connection [BAD] at 0x7f0770db89b0>
gen = <generator object BaseConnection._commit_gen at 0x7f0761894b80>
interval = 0.1

    def wait(self, gen: PQGen[RV], interval: float | None = _WAIT_INTERVAL) -> RV:
        """
        Consume a generator operating on the connection.
    
        The function must be used on generators that don't change connection
        fd (i.e. not on connect and reset).
        """
        try:
>           return waiting.wait(gen, self.pgconn.socket, interval=interval)

.venv/lib/python3.13............/site-packages/psycopg/connection.py:414: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

>   ???

psycopg_c/_psycopg/waiting.pyx:213: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <psycopg.Connection [BAD] at 0x7f0770db89b0>

    def _commit_gen(self) -> PQGen[None]:
        """Generator implementing `Connection.commit()`."""
        if self._num_transactions:
            raise e.ProgrammingError(
                "Explicit commit() forbidden within a Transaction "
                "context. (Transaction will be automatically committed "
                "on successful exit from context.)"
            )
        if self._tpc:
            raise e.ProgrammingError(
                "commit() cannot be used during a two-phase transaction"
            )
        if self.pgconn.transaction_status == IDLE:
            return
    
>       yield from self._exec_command(b"COMMIT")

.venv/lib/python3.13............/site-packages/psycopg/_connection_base.py:580: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <psycopg.Connection [BAD] at 0x7f0770db89b0>, command = b'COMMIT'
result_format = <Format.TEXT: 0>

    def _exec_command(
        self, command: Query, result_format: pq.Format = TEXT
    ) -> PQGen[PGresult | None]:
        """
        Generator to send a command and receive the result to the backend.
    
        Only used to implement internal commands such as "commit", with eventual
        arguments bound client-side. The cursor can do more complex stuff.
        """
        self._check_connection_ok()
    
        if isinstance(command, str):
            command = command.encode(self.pgconn._encoding)
        elif isinstance(command, Composable):
            command = command.as_bytes(self)
    
        if self._pipeline:
            cmd = partial(
                self.pgconn.send_query_params,
                command,
                None,
                result_format=result_format,
            )
            self._pipeline.command_queue.append(cmd)
            self._pipeline.result_queue.append(None)
            return None
    
        # Unless needed, use the simple query protocol, e.g. to interact with
        # pgbouncer. In pipeline mode we always use the advanced query protocol
        # instead, see #350
        if result_format == TEXT:
            self.pgconn.send_query(command)
        else:
            self.pgconn.send_query_params(command, None, result_format=result_format)
    
        result: PGresult = (yield from generators.execute(self.pgconn))[-1]
        if result.status != COMMAND_OK and result.status != TUPLES_OK:
            if result.status == FATAL_ERROR:
>               raise e.error_from_result(result, encoding=self.pgconn._encoding)
E               psycopg.errors.ForeignKeyViolation: insert or update on table "authentik_core_token" violates foreign key constraint "authentik_core_token_user_id_479d5b79_fk_authentik_core_user_id"
E               DETAIL:  Key (user_id)=(2) is not present in table "authentik_core_user".

.venv/lib/python3.13............/site-packages/psycopg/_connection_base.py:478: ForeignKeyViolation

The above exception was the direct cause of the following exception:

self = <unittest.case._Outcome object at 0x7f0763fad160>
test_case = <authentik.tenants.tests.test_recovery.TestRecovery testMethod=test_create_key_invalid>
subTest = False

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

.../hostedtoolcache/Python/3.13.7........./x64/lib/python3.13/unittest/case.py:58: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <authentik.tenants.tests.test_recovery.TestRecovery testMethod=test_create_key_invalid>
result = <TestCaseFunction test_create_key_invalid>

    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()

.../hostedtoolcache/Python/3.13.7........./x64/lib/python3.13/unittest/case.py:647: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <authentik.tenants.tests.test_recovery.TestRecovery testMethod=test_create_key_invalid>

    def _callSetUp(self):
>       self.setUp()

.../hostedtoolcache/Python/3.13.7........./x64/lib/python3.13/unittest/case.py:603: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <authentik.tenants.tests.test_recovery.TestRecovery testMethod=test_create_key_invalid>

    def setUp(self):
>       super().setUp()

.../tenants/tests/test_recovery.py:22: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <authentik.tenants.tests.test_recovery.TestRecovery testMethod=test_create_key_invalid>

    def setUp(self):
        with schema_context(get_public_schema_name()):
>           Tenant.objects.update_or_create(
                defaults={"name": "Template", "ready": False},
                schema_name=get_tenant_base_schema(),
            )

.../tenants/tests/utils.py:24: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <django.db.models.manager.Manager object at 0x7f076362d5d0>, args = ()
kwargs = {'defaults': {'name': 'Template', 'ready': False}, 'schema_name': 'template'}

    @wraps(method)
    def manager_method(self, *args, **kwargs):
>       return getattr(self.get_queryset(), name)(*args, **kwargs)

.venv/lib/python3.13.../db/models/manager.py:87: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <QuerySet [<Tenant: Tenant Default>]>
defaults = {'name': 'Template', 'ready': False}
create_defaults = {'name': 'Template', 'ready': False}
kwargs = {'schema_name': 'template'}
update_defaults = {'name': 'Template', 'ready': False}
obj = <Tenant: Tenant Template>, created = True

    def update_or_create(self, defaults=None, create_defaults=None, **kwargs):
        """
        Look up an object with the given kwargs, updating one with defaults
        if it exists, otherwise create a new one. Optionally, an object can
        be created with different values than defaults by using
        create_defaults.
        Return a tuple (object, created), where created is a boolean
        specifying whether an object was created.
        """
        update_defaults = defaults or {}
        if create_defaults is None:
            create_defaults = update_defaults
    
        self._for_write = True
>       with transaction.atomic(using=self.db):

.venv/lib/python3.13.../db/models/query.py:985: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <django.db.transaction.Atomic object at 0x7f0763930600>, exc_type = None
exc_value = None, traceback = None

    def __exit__(self, exc_type, exc_value, traceback):
        connection = get_connection(self.using)
    
        if connection.in_atomic_block:
            connection.atomic_blocks.pop()
    
        if connection.savepoint_ids:
            sid = connection.savepoint_ids.pop()
        else:
            # Prematurely unset this flag to allow using commit or rollback.
            connection.in_atomic_block = False
    
        try:
            if connection.closed_in_transaction:
                # The database will perform a rollback by itself.
                # Wait until we exit the outermost block.
                pass
    
            elif exc_type is None and not connection.needs_rollback:
                if connection.in_atomic_block:
                    # Release savepoint if there is one
                    if sid is not None:
                        try:
                            connection.savepoint_commit(sid)
                        except DatabaseError:
                            try:
                                connection.savepoint_rollback(sid)
                                # The savepoint won't be reused. Release it to
                                # minimize overhead for the database server.
                                connection.savepoint_commit(sid)
                            except Error:
                                # If rolling back to a savepoint fails, mark for
                                # rollback at a higher level and avoid shadowing
                                # the original exception.
                                connection.needs_rollback = True
                            raise
                else:
                    # Commit transaction
                    try:
>                       connection.commit()

.venv/lib/python3.13.../django/db/transaction.py:263: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

args = (<DatabaseWrapper vendor='postgresql' alias='default'>,), kwargs = {}

    @wraps(func)
    def inner(*args, **kwargs):
        # Detect a running event loop in this thread.
        try:
            get_running_loop()
        except RuntimeError:
            pass
        else:
            if not os.environ.get("DJANGO_ALLOW_ASYNC_UNSAFE"):
                raise SynchronousOnlyOperation(message)
        # Pass onward.
>       return func(*args, **kwargs)

.venv/lib/python3.13.../django/utils/asyncio.py:26: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <DatabaseWrapper vendor='postgresql' alias='default'>

    @async_unsafe
    def commit(self):
        """Commit a transaction and reset the dirty flag."""
        self.validate_thread_sharing()
        self.validate_no_atomic_block()
>       self._commit()

.venv/lib/python3.13.../backends/base/base.py:327: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <DatabaseWrapper vendor='postgresql' alias='default'>

    def _commit(self):
        if self.connection is not None:
>           with debug_transaction(self, "COMMIT"), self.wrap_database_errors:

.venv/lib/python3.13.../backends/base/base.py:302: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <django.db.utils.DatabaseErrorWrapper object at 0x7f0774909010>
exc_type = <class 'psycopg.errors.ForeignKeyViolation'>
exc_value = ForeignKeyViolation('insert or update on table "authentik_core_token" violates foreign key constraint "authentik_core_...ser_id_479d5b79_fk_authentik_core_user_id"\nDETAIL:  Key (user_id)=(2) is not present in table "authentik_core_user".')
traceback = <traceback object at 0x7f0768990d40>

    def __exit__(self, exc_type, exc_value, traceback):
        if exc_type is None:
            return
        for dj_exc_type in (
            DataError,
            OperationalError,
            IntegrityError,
            InternalError,
            ProgrammingError,
            NotSupportedError,
            DatabaseError,
            InterfaceError,
            Error,
        ):
            db_exc_type = getattr(self.wrapper.Database, dj_exc_type.__name__)
            if issubclass(exc_type, db_exc_type):
                dj_exc_value = dj_exc_type(*exc_value.args)
                # Only set the 'errors_occurred' flag for errors that may make
                # the connection unusable.
                if dj_exc_type not in (DataError, IntegrityError):
                    self.wrapper.errors_occurred = True
>               raise dj_exc_value.with_traceback(traceback) from exc_value

.venv/lib/python3.13.../django/db/utils.py:91: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <DatabaseWrapper vendor='postgresql' alias='default'>

    def _commit(self):
        if self.connection is not None:
            with debug_transaction(self, "COMMIT"), self.wrap_database_errors:
>               return self.connection.commit()

.venv/lib/python3.13.../backends/base/base.py:303: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <psycopg.Connection [BAD] at 0x7f0770db89b0>

    def commit(self) -> None:
        """Commit any pending transaction to the database."""
        with self.lock:
>           self.wait(self._commit_gen())

.venv/lib/python3.13............/site-packages/psycopg/connection.py:262: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <psycopg.Connection [BAD] at 0x7f0770db89b0>
gen = <generator object BaseConnection._commit_gen at 0x7f0761894b80>
interval = 0.1

    def wait(self, gen: PQGen[RV], interval: float | None = _WAIT_INTERVAL) -> RV:
        """
        Consume a generator operating on the connection.
    
        The function must be used on generators that don't change connection
        fd (i.e. not on connect and reset).
        """
        try:
>           return waiting.wait(gen, self.pgconn.socket, interval=interval)

.venv/lib/python3.13............/site-packages/psycopg/connection.py:414: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

>   ???

psycopg_c/_psycopg/waiting.pyx:213: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <psycopg.Connection [BAD] at 0x7f0770db89b0>

    def _commit_gen(self) -> PQGen[None]:
        """Generator implementing `Connection.commit()`."""
        if self._num_transactions:
            raise e.ProgrammingError(
                "Explicit commit() forbidden within a Transaction "
                "context. (Transaction will be automatically committed "
                "on successful exit from context.)"
            )
        if self._tpc:
            raise e.ProgrammingError(
                "commit() cannot be used during a two-phase transaction"
            )
        if self.pgconn.transaction_status == IDLE:
            return
    
>       yield from self._exec_command(b"COMMIT")

.venv/lib/python3.13............/site-packages/psycopg/_connection_base.py:580: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <psycopg.Connection [BAD] at 0x7f0770db89b0>, command = b'COMMIT'
result_format = <Format.TEXT: 0>

    def _exec_command(
        self, command: Query, result_format: pq.Format = TEXT
    ) -> PQGen[PGresult | None]:
        """
        Generator to send a command and receive the result to the backend.
    
        Only used to implement internal commands such as "commit", with eventual
        arguments bound client-side. The cursor can do more complex stuff.
        """
        self._check_connection_ok()
    
        if isinstance(command, str):
            command = command.encode(self.pgconn._encoding)
        elif isinstance(command, Composable):
            command = command.as_bytes(self)
    
        if self._pipeline:
            cmd = partial(
                self.pgconn.send_query_params,
                command,
                None,
                result_format=result_format,
            )
            self._pipeline.command_queue.append(cmd)
            self._pipeline.result_queue.append(None)
            return None
    
        # Unless needed, use the simple query protocol, e.g. to interact with
        # pgbouncer. In pipeline mode we always use the advanced query protocol
        # instead, see #350
        if result_format == TEXT:
            self.pgconn.send_query(command)
        else:
            self.pgconn.send_query_params(command, None, result_format=result_format)
    
        result: PGresult = (yield from generators.execute(self.pgconn))[-1]
        if result.status != COMMAND_OK and result.status != TUPLES_OK:
            if result.status == FATAL_ERROR:
>               raise e.error_from_result(result, encoding=self.pgconn._encoding)
E               django.db.utils.IntegrityError: insert or update on table "authentik_core_token" violates foreign key constraint "authentik_core_token_user_id_479d5b79_fk_authentik_core_user_id"
E               DETAIL:  Key (user_id)=(2) is not present in table "authentik_core_user".

.venv/lib/python3.13............/site-packages/psycopg/_connection_base.py:478: IntegrityError
authentik.tenants.tests.test_api.TestAPI::test_no_api_key_configured
Stack Traces | 11.3s run time
self = <DatabaseWrapper vendor='postgresql' alias='default'>

    def _commit(self):
        if self.connection is not None:
            with debug_transaction(self, "COMMIT"), self.wrap_database_errors:
>               return self.connection.commit()

.venv/lib/python3.13.../backends/base/base.py:303: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <psycopg.Connection [BAD] at 0x7f0761af7110>

    def commit(self) -> None:
        """Commit any pending transaction to the database."""
        with self.lock:
>           self.wait(self._commit_gen())

.venv/lib/python3.13............/site-packages/psycopg/connection.py:262: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <psycopg.Connection [BAD] at 0x7f0761af7110>
gen = <generator object BaseConnection._commit_gen at 0x7f0770b54040>
interval = 0.1

    def wait(self, gen: PQGen[RV], interval: float | None = _WAIT_INTERVAL) -> RV:
        """
        Consume a generator operating on the connection.
    
        The function must be used on generators that don't change connection
        fd (i.e. not on connect and reset).
        """
        try:
>           return waiting.wait(gen, self.pgconn.socket, interval=interval)

.venv/lib/python3.13............/site-packages/psycopg/connection.py:414: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

>   ???

psycopg_c/_psycopg/waiting.pyx:213: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <psycopg.Connection [BAD] at 0x7f0761af7110>

    def _commit_gen(self) -> PQGen[None]:
        """Generator implementing `Connection.commit()`."""
        if self._num_transactions:
            raise e.ProgrammingError(
                "Explicit commit() forbidden within a Transaction "
                "context. (Transaction will be automatically committed "
                "on successful exit from context.)"
            )
        if self._tpc:
            raise e.ProgrammingError(
                "commit() cannot be used during a two-phase transaction"
            )
        if self.pgconn.transaction_status == IDLE:
            return
    
>       yield from self._exec_command(b"COMMIT")

.venv/lib/python3.13............/site-packages/psycopg/_connection_base.py:580: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <psycopg.Connection [BAD] at 0x7f0761af7110>, command = b'COMMIT'
result_format = <Format.TEXT: 0>

    def _exec_command(
        self, command: Query, result_format: pq.Format = TEXT
    ) -> PQGen[PGresult | None]:
        """
        Generator to send a command and receive the result to the backend.
    
        Only used to implement internal commands such as "commit", with eventual
        arguments bound client-side. The cursor can do more complex stuff.
        """
        self._check_connection_ok()
    
        if isinstance(command, str):
            command = command.encode(self.pgconn._encoding)
        elif isinstance(command, Composable):
            command = command.as_bytes(self)
    
        if self._pipeline:
            cmd = partial(
                self.pgconn.send_query_params,
                command,
                None,
                result_format=result_format,
            )
            self._pipeline.command_queue.append(cmd)
            self._pipeline.result_queue.append(None)
            return None
    
        # Unless needed, use the simple query protocol, e.g. to interact with
        # pgbouncer. In pipeline mode we always use the advanced query protocol
        # instead, see #350
        if result_format == TEXT:
            self.pgconn.send_query(command)
        else:
            self.pgconn.send_query_params(command, None, result_format=result_format)
    
        result: PGresult = (yield from generators.execute(self.pgconn))[-1]
        if result.status != COMMAND_OK and result.status != TUPLES_OK:
            if result.status == FATAL_ERROR:
>               raise e.error_from_result(result, encoding=self.pgconn._encoding)
E               psycopg.errors.ForeignKeyViolation: insert or update on table "authentik_core_token" violates foreign key constraint "authentik_core_token_user_id_479d5b79_fk_authentik_core_user_id"
E               DETAIL:  Key (user_id)=(2) is not present in table "authentik_core_user".

.venv/lib/python3.13............/site-packages/psycopg/_connection_base.py:478: ForeignKeyViolation

The above exception was the direct cause of the following exception:

self = <unittest.case._Outcome object at 0x7f07681d4d70>
test_case = <authentik.tenants.tests.test_api.TestAPI testMethod=test_no_api_key_configured>
subTest = False

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

.../hostedtoolcache/Python/3.13.7........./x64/lib/python3.13/unittest/case.py:58: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <authentik.tenants.tests.test_api.TestAPI testMethod=test_no_api_key_configured>
result = <TestCaseFunction test_no_api_key_configured>

    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()

.../hostedtoolcache/Python/3.13.7........./x64/lib/python3.13/unittest/case.py:647: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <authentik.tenants.tests.test_api.TestAPI testMethod=test_no_api_key_configured>

    def _callSetUp(self):
>       self.setUp()

.../hostedtoolcache/Python/3.13.7........./x64/lib/python3.13/unittest/case.py:603: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <authentik.tenants.tests.test_api.TestAPI testMethod=test_no_api_key_configured>

    def setUp(self):
        with schema_context(get_public_schema_name()):
>           Tenant.objects.update_or_create(
                defaults={"name": "Template", "ready": False},
                schema_name=get_tenant_base_schema(),
            )

.../tenants/tests/utils.py:24: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <django.db.models.manager.Manager object at 0x7f076362d5d0>, args = ()
kwargs = {'defaults': {'name': 'Template', 'ready': False}, 'schema_name': 'template'}

    @wraps(method)
    def manager_method(self, *args, **kwargs):
>       return getattr(self.get_queryset(), name)(*args, **kwargs)

.venv/lib/python3.13.../db/models/manager.py:87: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <QuerySet [<Tenant: Tenant Default>]>
defaults = {'name': 'Template', 'ready': False}
create_defaults = {'name': 'Template', 'ready': False}
kwargs = {'schema_name': 'template'}
update_defaults = {'name': 'Template', 'ready': False}
obj = <Tenant: Tenant Template>, created = True

    def update_or_create(self, defaults=None, create_defaults=None, **kwargs):
        """
        Look up an object with the given kwargs, updating one with defaults
        if it exists, otherwise create a new one. Optionally, an object can
        be created with different values than defaults by using
        create_defaults.
        Return a tuple (object, created), where created is a boolean
        specifying whether an object was created.
        """
        update_defaults = defaults or {}
        if create_defaults is None:
            create_defaults = update_defaults
    
        self._for_write = True
>       with transaction.atomic(using=self.db):

.venv/lib/python3.13.../db/models/query.py:985: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <django.db.transaction.Atomic object at 0x7f0769b7df60>, exc_type = None
exc_value = None, traceback = None

    def __exit__(self, exc_type, exc_value, traceback):
        connection = get_connection(self.using)
    
        if connection.in_atomic_block:
            connection.atomic_blocks.pop()
    
        if connection.savepoint_ids:
            sid = connection.savepoint_ids.pop()
        else:
            # Prematurely unset this flag to allow using commit or rollback.
            connection.in_atomic_block = False
    
        try:
            if connection.closed_in_transaction:
                # The database will perform a rollback by itself.
                # Wait until we exit the outermost block.
                pass
    
            elif exc_type is None and not connection.needs_rollback:
                if connection.in_atomic_block:
                    # Release savepoint if there is one
                    if sid is not None:
                        try:
                            connection.savepoint_commit(sid)
                        except DatabaseError:
                            try:
                                connection.savepoint_rollback(sid)
                                # The savepoint won't be reused. Release it to
                                # minimize overhead for the database server.
                                connection.savepoint_commit(sid)
                            except Error:
                                # If rolling back to a savepoint fails, mark for
                                # rollback at a higher level and avoid shadowing
                                # the original exception.
                                connection.needs_rollback = True
                            raise
                else:
                    # Commit transaction
                    try:
>                       connection.commit()

.venv/lib/python3.13.../django/db/transaction.py:263: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

args = (<DatabaseWrapper vendor='postgresql' alias='default'>,), kwargs = {}

    @wraps(func)
    def inner(*args, **kwargs):
        # Detect a running event loop in this thread.
        try:
            get_running_loop()
        except RuntimeError:
            pass
        else:
            if not os.environ.get("DJANGO_ALLOW_ASYNC_UNSAFE"):
                raise SynchronousOnlyOperation(message)
        # Pass onward.
>       return func(*args, **kwargs)

.venv/lib/python3.13.../django/utils/asyncio.py:26: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <DatabaseWrapper vendor='postgresql' alias='default'>

    @async_unsafe
    def commit(self):
        """Commit a transaction and reset the dirty flag."""
        self.validate_thread_sharing()
        self.validate_no_atomic_block()
>       self._commit()

.venv/lib/python3.13.../backends/base/base.py:327: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <DatabaseWrapper vendor='postgresql' alias='default'>

    def _commit(self):
        if self.connection is not None:
>           with debug_transaction(self, "COMMIT"), self.wrap_database_errors:

.venv/lib/python3.13.../backends/base/base.py:302: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <django.db.utils.DatabaseErrorWrapper object at 0x7f0774909010>
exc_type = <class 'psycopg.errors.ForeignKeyViolation'>
exc_value = ForeignKeyViolation('insert or update on table "authentik_core_token" violates foreign key constraint "authentik_core_...ser_id_479d5b79_fk_authentik_core_user_id"\nDETAIL:  Key (user_id)=(2) is not present in table "authentik_core_user".')
traceback = <traceback object at 0x7f0762a8e600>

    def __exit__(self, exc_type, exc_value, traceback):
        if exc_type is None:
            return
        for dj_exc_type in (
            DataError,
            OperationalError,
            IntegrityError,
            InternalError,
            ProgrammingError,
            NotSupportedError,
            DatabaseError,
            InterfaceError,
            Error,
        ):
            db_exc_type = getattr(self.wrapper.Database, dj_exc_type.__name__)
            if issubclass(exc_type, db_exc_type):
                dj_exc_value = dj_exc_type(*exc_value.args)
                # Only set the 'errors_occurred' flag for errors that may make
                # the connection unusable.
                if dj_exc_type not in (DataError, IntegrityError):
                    self.wrapper.errors_occurred = True
>               raise dj_exc_value.with_traceback(traceback) from exc_value

.venv/lib/python3.13.../django/db/utils.py:91: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <DatabaseWrapper vendor='postgresql' alias='default'>

    def _commit(self):
        if self.connection is not None:
            with debug_transaction(self, "COMMIT"), self.wrap_database_errors:
>               return self.connection.commit()

.venv/lib/python3.13.../backends/base/base.py:303: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <psycopg.Connection [BAD] at 0x7f0761af7110>

    def commit(self) -> None:
        """Commit any pending transaction to the database."""
        with self.lock:
>           self.wait(self._commit_gen())

.venv/lib/python3.13............/site-packages/psycopg/connection.py:262: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <psycopg.Connection [BAD] at 0x7f0761af7110>
gen = <generator object BaseConnection._commit_gen at 0x7f0770b54040>
interval = 0.1

    def wait(self, gen: PQGen[RV], interval: float | None = _WAIT_INTERVAL) -> RV:
        """
        Consume a generator operating on the connection.
    
        The function must be used on generators that don't change connection
        fd (i.e. not on connect and reset).
        """
        try:
>           return waiting.wait(gen, self.pgconn.socket, interval=interval)

.venv/lib/python3.13............/site-packages/psycopg/connection.py:414: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

>   ???

psycopg_c/_psycopg/waiting.pyx:213: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <psycopg.Connection [BAD] at 0x7f0761af7110>

    def _commit_gen(self) -> PQGen[None]:
        """Generator implementing `Connection.commit()`."""
        if self._num_transactions:
            raise e.ProgrammingError(
                "Explicit commit() forbidden within a Transaction "
                "context. (Transaction will be automatically committed "
                "on successful exit from context.)"
            )
        if self._tpc:
            raise e.ProgrammingError(
                "commit() cannot be used during a two-phase transaction"
            )
        if self.pgconn.transaction_status == IDLE:
            return
    
>       yield from self._exec_command(b"COMMIT")

.venv/lib/python3.13............/site-packages/psycopg/_connection_base.py:580: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <psycopg.Connection [BAD] at 0x7f0761af7110>, command = b'COMMIT'
result_format = <Format.TEXT: 0>

    def _exec_command(
        self, command: Query, result_format: pq.Format = TEXT
    ) -> PQGen[PGresult | None]:
        """
        Generator to send a command and receive the result to the backend.
    
        Only used to implement internal commands such as "commit", with eventual
        arguments bound client-side. The cursor can do more complex stuff.
        """
        self._check_connection_ok()
    
        if isinstance(command, str):
            command = command.encode(self.pgconn._encoding)
        elif isinstance(command, Composable):
            command = command.as_bytes(self)
    
        if self._pipeline:
            cmd = partial(
                self.pgconn.send_query_params,
                command,
                None,
                result_format=result_format,
            )
            self._pipeline.command_queue.append(cmd)
            self._pipeline.result_queue.append(None)
            return None
    
        # Unless needed, use the simple query protocol, e.g. to interact with
        # pgbouncer. In pipeline mode we always use the advanced query protocol
        # instead, see #350
        if result_format == TEXT:
            self.pgconn.send_query(command)
        else:
            self.pgconn.send_query_params(command, None, result_format=result_format)
    
        result: PGresult = (yield from generators.execute(self.pgconn))[-1]
        if result.status != COMMAND_OK and result.status != TUPLES_OK:
            if result.status == FATAL_ERROR:
>               raise e.error_from_result(result, encoding=self.pgconn._encoding)
E               django.db.utils.IntegrityError: insert or update on table "authentik_core_token" violates foreign key constraint "authentik_core_token_user_id_479d5b79_fk_authentik_core_user_id"
E               DETAIL:  Key (user_id)=(2) is not present in table "authentik_core_user".

.venv/lib/python3.13............/site-packages/psycopg/_connection_base.py:478: IntegrityError

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

a
Signed-off-by: Dominic R <dominic@sdko.org>
@github-actions
Copy link
Contributor

github-actions bot commented Oct 29, 2025

authentik PR Installation instructions

Instructions for docker-compose

Add the following block to your .env file:

AUTHENTIK_IMAGE=ghcr.io/goauthentik/dev-server
AUTHENTIK_TAG=gh-12f69883c56b1d2f416e528b1b24e4083670ed33
AUTHENTIK_OUTPOSTS__CONTAINER_IMAGE_BASE=ghcr.io/goauthentik/dev-%(type)s:gh-%(build_hash)s

Afterwards, run the upgrade commands from the latest release notes.

Instructions for Kubernetes

Add the following block to your values.yml file:

authentik:
    outposts:
        container_image_base: ghcr.io/goauthentik/dev-%(type)s:gh-%(build_hash)s
global:
    image:
        repository: ghcr.io/goauthentik/dev-server
        tag: gh-12f69883c56b1d2f416e528b1b24e4083670ed33

Afterwards, run the upgrade commands from the latest release notes.

@dominic-r dominic-r marked this pull request as ready for review October 29, 2025 23:57
@dominic-r dominic-r requested a review from a team as a code owner October 29, 2025 23:57
@dominic-r dominic-r requested a review from BeryJu October 29, 2025 23:57
@dominic-r
Copy link
Member Author

Failing CI depends on: #17806

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
@BeryJu BeryJu changed the title outposts: fix outposts: update permissions more eagerly Oct 30, 2025
@github-project-automation github-project-automation bot moved this from Needs review to In Progress in authentik Core Oct 30, 2025
@BeryJu BeryJu merged commit ec00a91 into main Oct 30, 2025
122 of 133 checks passed
@BeryJu BeryJu deleted the sdko/fix-embedded-outpost-env-var branch October 30, 2025 17:33
@github-project-automation github-project-automation bot moved this from In Progress to Done in authentik Core Oct 30, 2025
authentik-automation bot pushed a commit that referenced this pull request Oct 30, 2025
* wip

* wip

* a

* a

Signed-off-by: Dominic R <dominic@sdko.org>

* rm

* this

* rm test files

* cover one more case

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

---------

Signed-off-by: Dominic R <dominic@sdko.org>
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens Langhammer <jens@goauthentik.io>
@authentik-automation
Copy link
Contributor

🍒 Cherry-pick to version-2025.10 created: #17841

BeryJu added a commit that referenced this pull request Oct 30, 2025
…ion-2025.10) (#17841)

outposts: update permissions more eagerly (#17783)

* wip

* wip

* a

* a



* rm

* this

* rm test files

* cover one more case



---------

Signed-off-by: Dominic R <dominic@sdko.org>
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Dominic R <dominic@sdko.org>
Co-authored-by: Jens Langhammer <jens@goauthentik.io>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:backend backport/version-2025.10 Add this label to PRs to backport changes to version-2025.10

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

Outposts not working with 2025.10.0

2 participants