3855 lines
164 KiB
Plaintext
3855 lines
164 KiB
Plaintext
EEEEEEEEEEE..... [100%]
|
|
=================================== ERRORS ====================================
|
|
_____________________ ERROR at setup of test_health_live ______________________
|
|
|
|
self = <sqlalchemy.engine.base.Connection object at 0x0000019AEB75EB10>
|
|
dialect = <sqlalchemy.dialects.sqlite.pysqlite.SQLiteDialect_pysqlite object at 0x0000019AEB7B6990>
|
|
context = <sqlalchemy.dialects.sqlite.base.SQLiteExecutionContext object at 0x0000019AEB67BD10>
|
|
statement = <sqlalchemy.dialects.sqlite.base.SQLiteCompiler object at 0x0000019AEB776D50>
|
|
parameters = [('SUPER_ADMIN',)]
|
|
|
|
def _exec_single_context(
|
|
self,
|
|
dialect: Dialect,
|
|
context: ExecutionContext,
|
|
statement: Union[str, Compiled],
|
|
parameters: Optional[_AnyMultiExecuteParams],
|
|
) -> CursorResult[Any]:
|
|
"""continue the _execute_context() method for a single DBAPI
|
|
cursor.execute() or cursor.executemany() call.
|
|
|
|
"""
|
|
if dialect.bind_typing is BindTyping.SETINPUTSIZES:
|
|
generic_setinputsizes = context._prepare_set_input_sizes()
|
|
|
|
if generic_setinputsizes:
|
|
try:
|
|
dialect.do_set_input_sizes(
|
|
context.cursor, generic_setinputsizes, context
|
|
)
|
|
except BaseException as e:
|
|
self._handle_dbapi_exception(
|
|
e, str(statement), parameters, None, context
|
|
)
|
|
|
|
cursor, str_statement, parameters = (
|
|
context.cursor,
|
|
context.statement,
|
|
context.parameters,
|
|
)
|
|
|
|
effective_parameters: Optional[_AnyExecuteParams]
|
|
|
|
if not context.executemany:
|
|
effective_parameters = parameters[0]
|
|
else:
|
|
effective_parameters = parameters
|
|
|
|
if self._has_events or self.engine._has_events:
|
|
for fn in self.dispatch.before_cursor_execute:
|
|
str_statement, effective_parameters = fn(
|
|
self,
|
|
cursor,
|
|
str_statement,
|
|
effective_parameters,
|
|
context,
|
|
context.executemany,
|
|
)
|
|
|
|
if self._echo:
|
|
self._log_info(str_statement)
|
|
|
|
stats = context._get_cache_stats()
|
|
|
|
if not self.engine.hide_parameters:
|
|
self._log_info(
|
|
"[%s] %r",
|
|
stats,
|
|
sql_util._repr_params(
|
|
effective_parameters,
|
|
batches=10,
|
|
ismulti=context.executemany,
|
|
),
|
|
)
|
|
else:
|
|
self._log_info(
|
|
"[%s] [SQL parameters hidden due to hide_parameters=True]",
|
|
stats,
|
|
)
|
|
|
|
evt_handled: bool = False
|
|
try:
|
|
if context.execute_style is ExecuteStyle.EXECUTEMANY:
|
|
effective_parameters = cast(
|
|
"_CoreMultiExecuteParams", effective_parameters
|
|
)
|
|
if self.dialect._has_events:
|
|
for fn in self.dialect.dispatch.do_executemany:
|
|
if fn(
|
|
cursor,
|
|
str_statement,
|
|
effective_parameters,
|
|
context,
|
|
):
|
|
evt_handled = True
|
|
break
|
|
if not evt_handled:
|
|
self.dialect.do_executemany(
|
|
cursor,
|
|
str_statement,
|
|
effective_parameters,
|
|
context,
|
|
)
|
|
elif not effective_parameters and context.no_parameters:
|
|
if self.dialect._has_events:
|
|
for fn in self.dialect.dispatch.do_execute_no_params:
|
|
if fn(cursor, str_statement, context):
|
|
evt_handled = True
|
|
break
|
|
if not evt_handled:
|
|
self.dialect.do_execute_no_params(
|
|
cursor, str_statement, context
|
|
)
|
|
else:
|
|
effective_parameters = cast(
|
|
"_CoreSingleExecuteParams", effective_parameters
|
|
)
|
|
if self.dialect._has_events:
|
|
for fn in self.dialect.dispatch.do_execute:
|
|
if fn(
|
|
cursor,
|
|
str_statement,
|
|
effective_parameters,
|
|
context,
|
|
):
|
|
evt_handled = True
|
|
break
|
|
if not evt_handled:
|
|
> self.dialect.do_execute(
|
|
cursor, str_statement, effective_parameters, context
|
|
)
|
|
|
|
.venv\Lib\site-packages\sqlalchemy\engine\base.py:1969:
|
|
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
|
|
|
|
self = <sqlalchemy.dialects.sqlite.pysqlite.SQLiteDialect_pysqlite object at 0x0000019AEB7B6990>
|
|
cursor = <sqlite3.Cursor object at 0x0000019AEB7F77C0>
|
|
statement = 'SELECT users.id \nFROM users \nWHERE users.role = ?'
|
|
parameters = ('SUPER_ADMIN',)
|
|
context = <sqlalchemy.dialects.sqlite.base.SQLiteExecutionContext object at 0x0000019AEB67BD10>
|
|
|
|
def do_execute(self, cursor, statement, parameters, context=None):
|
|
> cursor.execute(statement, parameters)
|
|
E sqlite3.OperationalError: no such table: users
|
|
|
|
.venv\Lib\site-packages\sqlalchemy\engine\default.py:952: OperationalError
|
|
|
|
The above exception was the direct cause of the following exception:
|
|
|
|
db_session = <sqlalchemy.orm.session.Session object at 0x0000019AEB6EB620>
|
|
monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x0000019AEB75DCD0>
|
|
|
|
@pytest.fixture()
|
|
def client(db_session, monkeypatch):
|
|
"""TestClient wired to the fixture DB; dynsec calls are faked out."""
|
|
# Point the app's session factory at the test engine.
|
|
monkeypatch.setattr(db_module, "_SessionFactory", None)
|
|
monkeypatch.setattr(
|
|
db_module, "_engine", db_session.get_bind()
|
|
)
|
|
|
|
# Fake dynsec so no broker is needed for API tests.
|
|
class FakeDynsec:
|
|
def __init__(self):
|
|
self.clients: dict[str, dict] = {}
|
|
self.roles: dict[str, dict] = {}
|
|
self.groups: dict[str, dict] = {}
|
|
|
|
def _command(self, command, **kwargs):
|
|
return {"success": True}
|
|
|
|
def create_client(self, username, password, **kwargs):
|
|
self.clients[username] = {"username": username, "enabled": True, **kwargs}
|
|
|
|
def list_clients(self):
|
|
return list(self.clients.values())
|
|
|
|
def delete_client(self, username):
|
|
self.clients.pop(username, None)
|
|
|
|
def disable_client(self, username):
|
|
self.clients[username]["enabled"] = False
|
|
|
|
def enable_client(self, username):
|
|
self.clients[username]["enabled"] = True
|
|
|
|
def set_client_password(self, username, password):
|
|
pass
|
|
|
|
def get_client(self, username):
|
|
return self.clients[username]
|
|
|
|
def create_group(self, groupname, roles=None):
|
|
self.groups[groupname] = {"groupname": groupname, "roles": roles or []}
|
|
|
|
def list_groups(self):
|
|
return list(self.groups.values())
|
|
|
|
def delete_group(self, groupname):
|
|
self.groups.pop(groupname, None)
|
|
|
|
def create_role(self, rolename, acls=None):
|
|
self.roles[rolename] = {"rolename": rolename, "acls": acls or []}
|
|
|
|
def list_roles(self):
|
|
return list(self.roles.values())
|
|
|
|
def delete_role(self, rolename):
|
|
self.roles.pop(rolename, None)
|
|
|
|
def set_role_acls(self, rolename, acls):
|
|
self.roles[rolename]["acls"] = acls
|
|
|
|
def get_role(self, rolename):
|
|
return self.roles[rolename]
|
|
|
|
def connect(self):
|
|
pass
|
|
|
|
def close(self):
|
|
pass
|
|
|
|
fake = FakeDynsec()
|
|
monkeypatch.setattr(main_module, "dynsec_client", fake)
|
|
|
|
from app.routers import mqtt as mqtt_router
|
|
|
|
monkeypatch.setattr(mqtt_router, "dynsec_client", fake)
|
|
|
|
> with TestClient(main_module.app) as test_client:
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
|
|
tests\conftest.py:132:
|
|
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
|
|
.venv\Lib\site-packages\starlette\testclient.py:698: in __enter__
|
|
portal.call(self.wait_startup)
|
|
.venv\Lib\site-packages\anyio\from_thread.py:340: in call
|
|
return cast(T_Retval, self.start_task_soon(func, *args).result())
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
..\..\..\..\AppData\Local\Python\pythoncore-3.14-64\Lib\concurrent\futures\_base.py:450: in result
|
|
return self.__get_result()
|
|
^^^^^^^^^^^^^^^^^^^
|
|
..\..\..\..\AppData\Local\Python\pythoncore-3.14-64\Lib\concurrent\futures\_base.py:395: in __get_result
|
|
raise self._exception
|
|
.venv\Lib\site-packages\anyio\from_thread.py:265: in _call_func
|
|
retval = await retval_or_awaitable
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
.venv\Lib\site-packages\starlette\testclient.py:733: in wait_startup
|
|
await receive()
|
|
.venv\Lib\site-packages\starlette\testclient.py:724: in receive
|
|
self.task.result()
|
|
..\..\..\..\AppData\Local\Python\pythoncore-3.14-64\Lib\concurrent\futures\_base.py:443: in result
|
|
return self.__get_result()
|
|
^^^^^^^^^^^^^^^^^^^
|
|
..\..\..\..\AppData\Local\Python\pythoncore-3.14-64\Lib\concurrent\futures\_base.py:395: in __get_result
|
|
raise self._exception
|
|
.venv\Lib\site-packages\anyio\from_thread.py:265: in _call_func
|
|
retval = await retval_or_awaitable
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
.venv\Lib\site-packages\starlette\testclient.py:714: in lifespan
|
|
await self.app(scope, self.stream_receive.receive, self.stream_send.send)
|
|
.venv\Lib\site-packages\fastapi\applications.py:1163: in __call__
|
|
await super().__call__(scope, receive, send)
|
|
.venv\Lib\site-packages\starlette\applications.py:96: in __call__
|
|
await self.middleware_stack(scope, receive, send)
|
|
.venv\Lib\site-packages\starlette\middleware\errors.py:151: in __call__
|
|
await self.app(scope, receive, send)
|
|
.venv\Lib\site-packages\starlette\middleware\base.py:103: in __call__
|
|
await self.app(scope, receive, send)
|
|
.venv\Lib\site-packages\starlette\middleware\cors.py:80: in __call__
|
|
await self.app(scope, receive, send)
|
|
.venv\Lib\site-packages\starlette\middleware\exceptions.py:49: in __call__
|
|
await self.app(scope, receive, send)
|
|
.venv\Lib\site-packages\fastapi\middleware\asyncexitstack.py:18: in __call__
|
|
await self.app(scope, receive, send)
|
|
.venv\Lib\site-packages\starlette\routing.py:670: in __call__
|
|
await self.middleware_stack(scope, receive, send)
|
|
.venv\Lib\site-packages\fastapi\routing.py:2726: in app
|
|
await self.lifespan(scope, receive, send)
|
|
.venv\Lib\site-packages\starlette\routing.py:648: in lifespan
|
|
async with self.lifespan_context(app) as maybe_state:
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
..\..\..\..\AppData\Local\Python\pythoncore-3.14-64\Lib\contextlib.py:214: in __aenter__
|
|
return await anext(self.gen)
|
|
^^^^^^^^^^^^^^^^^^^^^
|
|
.venv\Lib\site-packages\fastapi\routing.py:240: in merged_lifespan
|
|
async with original_context(app) as maybe_original_state:
|
|
^^^^^^^^^^^^^^^^^^^^^
|
|
..\..\..\..\AppData\Local\Python\pythoncore-3.14-64\Lib\contextlib.py:214: in __aenter__
|
|
return await anext(self.gen)
|
|
^^^^^^^^^^^^^^^^^^^^^
|
|
.venv\Lib\site-packages\fastapi\routing.py:240: in merged_lifespan
|
|
async with original_context(app) as maybe_original_state:
|
|
^^^^^^^^^^^^^^^^^^^^^
|
|
..\..\..\..\AppData\Local\Python\pythoncore-3.14-64\Lib\contextlib.py:214: in __aenter__
|
|
return await anext(self.gen)
|
|
^^^^^^^^^^^^^^^^^^^^^
|
|
.venv\Lib\site-packages\fastapi\routing.py:240: in merged_lifespan
|
|
async with original_context(app) as maybe_original_state:
|
|
^^^^^^^^^^^^^^^^^^^^^
|
|
..\..\..\..\AppData\Local\Python\pythoncore-3.14-64\Lib\contextlib.py:214: in __aenter__
|
|
return await anext(self.gen)
|
|
^^^^^^^^^^^^^^^^^^^^^
|
|
.venv\Lib\site-packages\fastapi\routing.py:240: in merged_lifespan
|
|
async with original_context(app) as maybe_original_state:
|
|
^^^^^^^^^^^^^^^^^^^^^
|
|
..\..\..\..\AppData\Local\Python\pythoncore-3.14-64\Lib\contextlib.py:214: in __aenter__
|
|
return await anext(self.gen)
|
|
^^^^^^^^^^^^^^^^^^^^^
|
|
app\main.py:65: in lifespan
|
|
_bootstrap_admin()
|
|
app\main.py:34: in _bootstrap_admin
|
|
has_admin = session.scalar(
|
|
.venv\Lib\site-packages\sqlalchemy\orm\session.py:2421: in scalar
|
|
return self._execute_internal(
|
|
.venv\Lib\site-packages\sqlalchemy\orm\session.py:2271: in _execute_internal
|
|
result: Result[Any] = compile_state_cls.orm_execute_statement(
|
|
.venv\Lib\site-packages\sqlalchemy\orm\context.py:306: in orm_execute_statement
|
|
result = conn.execute(
|
|
.venv\Lib\site-packages\sqlalchemy\engine\base.py:1421: in execute
|
|
return meth(
|
|
.venv\Lib\site-packages\sqlalchemy\sql\elements.py:526: in _execute_on_connection
|
|
return connection._execute_clauseelement(
|
|
.venv\Lib\site-packages\sqlalchemy\engine\base.py:1643: in _execute_clauseelement
|
|
ret = self._execute_context(
|
|
.venv\Lib\site-packages\sqlalchemy\engine\base.py:1848: in _execute_context
|
|
return self._exec_single_context(
|
|
.venv\Lib\site-packages\sqlalchemy\engine\base.py:1988: in _exec_single_context
|
|
self._handle_dbapi_exception(
|
|
.venv\Lib\site-packages\sqlalchemy\engine\base.py:2365: in _handle_dbapi_exception
|
|
raise sqlalchemy_exception.with_traceback(exc_info[2]) from e
|
|
.venv\Lib\site-packages\sqlalchemy\engine\base.py:1969: in _exec_single_context
|
|
self.dialect.do_execute(
|
|
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
|
|
|
|
self = <sqlalchemy.dialects.sqlite.pysqlite.SQLiteDialect_pysqlite object at 0x0000019AEB7B6990>
|
|
cursor = <sqlite3.Cursor object at 0x0000019AEB7F77C0>
|
|
statement = 'SELECT users.id \nFROM users \nWHERE users.role = ?'
|
|
parameters = ('SUPER_ADMIN',)
|
|
context = <sqlalchemy.dialects.sqlite.base.SQLiteExecutionContext object at 0x0000019AEB67BD10>
|
|
|
|
def do_execute(self, cursor, statement, parameters, context=None):
|
|
> cursor.execute(statement, parameters)
|
|
E sqlalchemy.exc.OperationalError: (sqlite3.OperationalError) no such table: users
|
|
E [SQL: SELECT users.id
|
|
E FROM users
|
|
E WHERE users.role = ?]
|
|
E [parameters: ('SUPER_ADMIN',)]
|
|
E (Background on this error at: https://sqlalche.me/e/20/e3q8)
|
|
|
|
.venv\Lib\site-packages\sqlalchemy\engine\default.py:952: OperationalError
|
|
____________ ERROR at setup of test_login_sets_cookie_and_session _____________
|
|
|
|
self = <sqlalchemy.engine.base.Connection object at 0x0000019AEBAB5E50>
|
|
dialect = <sqlalchemy.dialects.sqlite.pysqlite.SQLiteDialect_pysqlite object at 0x0000019AEBDC6710>
|
|
context = <sqlalchemy.dialects.sqlite.base.SQLiteExecutionContext object at 0x0000019AEB896450>
|
|
statement = <sqlalchemy.dialects.sqlite.base.SQLiteCompiler object at 0x0000019AEBAC9050>
|
|
parameters = [('SUPER_ADMIN',)]
|
|
|
|
def _exec_single_context(
|
|
self,
|
|
dialect: Dialect,
|
|
context: ExecutionContext,
|
|
statement: Union[str, Compiled],
|
|
parameters: Optional[_AnyMultiExecuteParams],
|
|
) -> CursorResult[Any]:
|
|
"""continue the _execute_context() method for a single DBAPI
|
|
cursor.execute() or cursor.executemany() call.
|
|
|
|
"""
|
|
if dialect.bind_typing is BindTyping.SETINPUTSIZES:
|
|
generic_setinputsizes = context._prepare_set_input_sizes()
|
|
|
|
if generic_setinputsizes:
|
|
try:
|
|
dialect.do_set_input_sizes(
|
|
context.cursor, generic_setinputsizes, context
|
|
)
|
|
except BaseException as e:
|
|
self._handle_dbapi_exception(
|
|
e, str(statement), parameters, None, context
|
|
)
|
|
|
|
cursor, str_statement, parameters = (
|
|
context.cursor,
|
|
context.statement,
|
|
context.parameters,
|
|
)
|
|
|
|
effective_parameters: Optional[_AnyExecuteParams]
|
|
|
|
if not context.executemany:
|
|
effective_parameters = parameters[0]
|
|
else:
|
|
effective_parameters = parameters
|
|
|
|
if self._has_events or self.engine._has_events:
|
|
for fn in self.dispatch.before_cursor_execute:
|
|
str_statement, effective_parameters = fn(
|
|
self,
|
|
cursor,
|
|
str_statement,
|
|
effective_parameters,
|
|
context,
|
|
context.executemany,
|
|
)
|
|
|
|
if self._echo:
|
|
self._log_info(str_statement)
|
|
|
|
stats = context._get_cache_stats()
|
|
|
|
if not self.engine.hide_parameters:
|
|
self._log_info(
|
|
"[%s] %r",
|
|
stats,
|
|
sql_util._repr_params(
|
|
effective_parameters,
|
|
batches=10,
|
|
ismulti=context.executemany,
|
|
),
|
|
)
|
|
else:
|
|
self._log_info(
|
|
"[%s] [SQL parameters hidden due to hide_parameters=True]",
|
|
stats,
|
|
)
|
|
|
|
evt_handled: bool = False
|
|
try:
|
|
if context.execute_style is ExecuteStyle.EXECUTEMANY:
|
|
effective_parameters = cast(
|
|
"_CoreMultiExecuteParams", effective_parameters
|
|
)
|
|
if self.dialect._has_events:
|
|
for fn in self.dialect.dispatch.do_executemany:
|
|
if fn(
|
|
cursor,
|
|
str_statement,
|
|
effective_parameters,
|
|
context,
|
|
):
|
|
evt_handled = True
|
|
break
|
|
if not evt_handled:
|
|
self.dialect.do_executemany(
|
|
cursor,
|
|
str_statement,
|
|
effective_parameters,
|
|
context,
|
|
)
|
|
elif not effective_parameters and context.no_parameters:
|
|
if self.dialect._has_events:
|
|
for fn in self.dialect.dispatch.do_execute_no_params:
|
|
if fn(cursor, str_statement, context):
|
|
evt_handled = True
|
|
break
|
|
if not evt_handled:
|
|
self.dialect.do_execute_no_params(
|
|
cursor, str_statement, context
|
|
)
|
|
else:
|
|
effective_parameters = cast(
|
|
"_CoreSingleExecuteParams", effective_parameters
|
|
)
|
|
if self.dialect._has_events:
|
|
for fn in self.dialect.dispatch.do_execute:
|
|
if fn(
|
|
cursor,
|
|
str_statement,
|
|
effective_parameters,
|
|
context,
|
|
):
|
|
evt_handled = True
|
|
break
|
|
if not evt_handled:
|
|
> self.dialect.do_execute(
|
|
cursor, str_statement, effective_parameters, context
|
|
)
|
|
|
|
.venv\Lib\site-packages\sqlalchemy\engine\base.py:1969:
|
|
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
|
|
|
|
self = <sqlalchemy.dialects.sqlite.pysqlite.SQLiteDialect_pysqlite object at 0x0000019AEBDC6710>
|
|
cursor = <sqlite3.Cursor object at 0x0000019AEE5A98C0>
|
|
statement = 'SELECT users.id \nFROM users \nWHERE users.role = ?'
|
|
parameters = ('SUPER_ADMIN',)
|
|
context = <sqlalchemy.dialects.sqlite.base.SQLiteExecutionContext object at 0x0000019AEB896450>
|
|
|
|
def do_execute(self, cursor, statement, parameters, context=None):
|
|
> cursor.execute(statement, parameters)
|
|
E sqlite3.OperationalError: no such table: users
|
|
|
|
.venv\Lib\site-packages\sqlalchemy\engine\default.py:952: OperationalError
|
|
|
|
The above exception was the direct cause of the following exception:
|
|
|
|
db_session = <sqlalchemy.orm.session.Session object at 0x0000019AEB7D52B0>
|
|
monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x0000019AEB75F490>
|
|
|
|
@pytest.fixture()
|
|
def client(db_session, monkeypatch):
|
|
"""TestClient wired to the fixture DB; dynsec calls are faked out."""
|
|
# Point the app's session factory at the test engine.
|
|
monkeypatch.setattr(db_module, "_SessionFactory", None)
|
|
monkeypatch.setattr(
|
|
db_module, "_engine", db_session.get_bind()
|
|
)
|
|
|
|
# Fake dynsec so no broker is needed for API tests.
|
|
class FakeDynsec:
|
|
def __init__(self):
|
|
self.clients: dict[str, dict] = {}
|
|
self.roles: dict[str, dict] = {}
|
|
self.groups: dict[str, dict] = {}
|
|
|
|
def _command(self, command, **kwargs):
|
|
return {"success": True}
|
|
|
|
def create_client(self, username, password, **kwargs):
|
|
self.clients[username] = {"username": username, "enabled": True, **kwargs}
|
|
|
|
def list_clients(self):
|
|
return list(self.clients.values())
|
|
|
|
def delete_client(self, username):
|
|
self.clients.pop(username, None)
|
|
|
|
def disable_client(self, username):
|
|
self.clients[username]["enabled"] = False
|
|
|
|
def enable_client(self, username):
|
|
self.clients[username]["enabled"] = True
|
|
|
|
def set_client_password(self, username, password):
|
|
pass
|
|
|
|
def get_client(self, username):
|
|
return self.clients[username]
|
|
|
|
def create_group(self, groupname, roles=None):
|
|
self.groups[groupname] = {"groupname": groupname, "roles": roles or []}
|
|
|
|
def list_groups(self):
|
|
return list(self.groups.values())
|
|
|
|
def delete_group(self, groupname):
|
|
self.groups.pop(groupname, None)
|
|
|
|
def create_role(self, rolename, acls=None):
|
|
self.roles[rolename] = {"rolename": rolename, "acls": acls or []}
|
|
|
|
def list_roles(self):
|
|
return list(self.roles.values())
|
|
|
|
def delete_role(self, rolename):
|
|
self.roles.pop(rolename, None)
|
|
|
|
def set_role_acls(self, rolename, acls):
|
|
self.roles[rolename]["acls"] = acls
|
|
|
|
def get_role(self, rolename):
|
|
return self.roles[rolename]
|
|
|
|
def connect(self):
|
|
pass
|
|
|
|
def close(self):
|
|
pass
|
|
|
|
fake = FakeDynsec()
|
|
monkeypatch.setattr(main_module, "dynsec_client", fake)
|
|
|
|
from app.routers import mqtt as mqtt_router
|
|
|
|
monkeypatch.setattr(mqtt_router, "dynsec_client", fake)
|
|
|
|
> with TestClient(main_module.app) as test_client:
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
|
|
tests\conftest.py:132:
|
|
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
|
|
.venv\Lib\site-packages\starlette\testclient.py:698: in __enter__
|
|
portal.call(self.wait_startup)
|
|
.venv\Lib\site-packages\anyio\from_thread.py:340: in call
|
|
return cast(T_Retval, self.start_task_soon(func, *args).result())
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
..\..\..\..\AppData\Local\Python\pythoncore-3.14-64\Lib\concurrent\futures\_base.py:450: in result
|
|
return self.__get_result()
|
|
^^^^^^^^^^^^^^^^^^^
|
|
..\..\..\..\AppData\Local\Python\pythoncore-3.14-64\Lib\concurrent\futures\_base.py:395: in __get_result
|
|
raise self._exception
|
|
.venv\Lib\site-packages\anyio\from_thread.py:265: in _call_func
|
|
retval = await retval_or_awaitable
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
.venv\Lib\site-packages\starlette\testclient.py:733: in wait_startup
|
|
await receive()
|
|
.venv\Lib\site-packages\starlette\testclient.py:724: in receive
|
|
self.task.result()
|
|
..\..\..\..\AppData\Local\Python\pythoncore-3.14-64\Lib\concurrent\futures\_base.py:443: in result
|
|
return self.__get_result()
|
|
^^^^^^^^^^^^^^^^^^^
|
|
..\..\..\..\AppData\Local\Python\pythoncore-3.14-64\Lib\concurrent\futures\_base.py:395: in __get_result
|
|
raise self._exception
|
|
.venv\Lib\site-packages\anyio\from_thread.py:265: in _call_func
|
|
retval = await retval_or_awaitable
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
.venv\Lib\site-packages\starlette\testclient.py:714: in lifespan
|
|
await self.app(scope, self.stream_receive.receive, self.stream_send.send)
|
|
.venv\Lib\site-packages\fastapi\applications.py:1163: in __call__
|
|
await super().__call__(scope, receive, send)
|
|
.venv\Lib\site-packages\starlette\applications.py:96: in __call__
|
|
await self.middleware_stack(scope, receive, send)
|
|
.venv\Lib\site-packages\starlette\middleware\errors.py:151: in __call__
|
|
await self.app(scope, receive, send)
|
|
.venv\Lib\site-packages\starlette\middleware\base.py:103: in __call__
|
|
await self.app(scope, receive, send)
|
|
.venv\Lib\site-packages\starlette\middleware\cors.py:80: in __call__
|
|
await self.app(scope, receive, send)
|
|
.venv\Lib\site-packages\starlette\middleware\exceptions.py:49: in __call__
|
|
await self.app(scope, receive, send)
|
|
.venv\Lib\site-packages\fastapi\middleware\asyncexitstack.py:18: in __call__
|
|
await self.app(scope, receive, send)
|
|
.venv\Lib\site-packages\starlette\routing.py:670: in __call__
|
|
await self.middleware_stack(scope, receive, send)
|
|
.venv\Lib\site-packages\fastapi\routing.py:2726: in app
|
|
await self.lifespan(scope, receive, send)
|
|
.venv\Lib\site-packages\starlette\routing.py:648: in lifespan
|
|
async with self.lifespan_context(app) as maybe_state:
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
..\..\..\..\AppData\Local\Python\pythoncore-3.14-64\Lib\contextlib.py:214: in __aenter__
|
|
return await anext(self.gen)
|
|
^^^^^^^^^^^^^^^^^^^^^
|
|
.venv\Lib\site-packages\fastapi\routing.py:240: in merged_lifespan
|
|
async with original_context(app) as maybe_original_state:
|
|
^^^^^^^^^^^^^^^^^^^^^
|
|
..\..\..\..\AppData\Local\Python\pythoncore-3.14-64\Lib\contextlib.py:214: in __aenter__
|
|
return await anext(self.gen)
|
|
^^^^^^^^^^^^^^^^^^^^^
|
|
.venv\Lib\site-packages\fastapi\routing.py:240: in merged_lifespan
|
|
async with original_context(app) as maybe_original_state:
|
|
^^^^^^^^^^^^^^^^^^^^^
|
|
..\..\..\..\AppData\Local\Python\pythoncore-3.14-64\Lib\contextlib.py:214: in __aenter__
|
|
return await anext(self.gen)
|
|
^^^^^^^^^^^^^^^^^^^^^
|
|
.venv\Lib\site-packages\fastapi\routing.py:240: in merged_lifespan
|
|
async with original_context(app) as maybe_original_state:
|
|
^^^^^^^^^^^^^^^^^^^^^
|
|
..\..\..\..\AppData\Local\Python\pythoncore-3.14-64\Lib\contextlib.py:214: in __aenter__
|
|
return await anext(self.gen)
|
|
^^^^^^^^^^^^^^^^^^^^^
|
|
.venv\Lib\site-packages\fastapi\routing.py:240: in merged_lifespan
|
|
async with original_context(app) as maybe_original_state:
|
|
^^^^^^^^^^^^^^^^^^^^^
|
|
..\..\..\..\AppData\Local\Python\pythoncore-3.14-64\Lib\contextlib.py:214: in __aenter__
|
|
return await anext(self.gen)
|
|
^^^^^^^^^^^^^^^^^^^^^
|
|
app\main.py:65: in lifespan
|
|
_bootstrap_admin()
|
|
app\main.py:34: in _bootstrap_admin
|
|
has_admin = session.scalar(
|
|
.venv\Lib\site-packages\sqlalchemy\orm\session.py:2421: in scalar
|
|
return self._execute_internal(
|
|
.venv\Lib\site-packages\sqlalchemy\orm\session.py:2271: in _execute_internal
|
|
result: Result[Any] = compile_state_cls.orm_execute_statement(
|
|
.venv\Lib\site-packages\sqlalchemy\orm\context.py:306: in orm_execute_statement
|
|
result = conn.execute(
|
|
.venv\Lib\site-packages\sqlalchemy\engine\base.py:1421: in execute
|
|
return meth(
|
|
.venv\Lib\site-packages\sqlalchemy\sql\elements.py:526: in _execute_on_connection
|
|
return connection._execute_clauseelement(
|
|
.venv\Lib\site-packages\sqlalchemy\engine\base.py:1643: in _execute_clauseelement
|
|
ret = self._execute_context(
|
|
.venv\Lib\site-packages\sqlalchemy\engine\base.py:1848: in _execute_context
|
|
return self._exec_single_context(
|
|
.venv\Lib\site-packages\sqlalchemy\engine\base.py:1988: in _exec_single_context
|
|
self._handle_dbapi_exception(
|
|
.venv\Lib\site-packages\sqlalchemy\engine\base.py:2365: in _handle_dbapi_exception
|
|
raise sqlalchemy_exception.with_traceback(exc_info[2]) from e
|
|
.venv\Lib\site-packages\sqlalchemy\engine\base.py:1969: in _exec_single_context
|
|
self.dialect.do_execute(
|
|
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
|
|
|
|
self = <sqlalchemy.dialects.sqlite.pysqlite.SQLiteDialect_pysqlite object at 0x0000019AEBDC6710>
|
|
cursor = <sqlite3.Cursor object at 0x0000019AEE5A98C0>
|
|
statement = 'SELECT users.id \nFROM users \nWHERE users.role = ?'
|
|
parameters = ('SUPER_ADMIN',)
|
|
context = <sqlalchemy.dialects.sqlite.base.SQLiteExecutionContext object at 0x0000019AEB896450>
|
|
|
|
def do_execute(self, cursor, statement, parameters, context=None):
|
|
> cursor.execute(statement, parameters)
|
|
E sqlalchemy.exc.OperationalError: (sqlite3.OperationalError) no such table: users
|
|
E [SQL: SELECT users.id
|
|
E FROM users
|
|
E WHERE users.role = ?]
|
|
E [parameters: ('SUPER_ADMIN',)]
|
|
E (Background on this error at: https://sqlalche.me/e/20/e3q8)
|
|
|
|
.venv\Lib\site-packages\sqlalchemy\engine\default.py:952: OperationalError
|
|
_____________ ERROR at setup of test_login_wrong_password_audited _____________
|
|
|
|
self = <sqlalchemy.engine.base.Connection object at 0x0000019AEB7CE3F0>
|
|
dialect = <sqlalchemy.dialects.sqlite.pysqlite.SQLiteDialect_pysqlite object at 0x0000019AEB7B7250>
|
|
context = <sqlalchemy.dialects.sqlite.base.SQLiteExecutionContext object at 0x0000019AEB896210>
|
|
statement = <sqlalchemy.dialects.sqlite.base.SQLiteCompiler object at 0x0000019AEBACA550>
|
|
parameters = [('SUPER_ADMIN',)]
|
|
|
|
def _exec_single_context(
|
|
self,
|
|
dialect: Dialect,
|
|
context: ExecutionContext,
|
|
statement: Union[str, Compiled],
|
|
parameters: Optional[_AnyMultiExecuteParams],
|
|
) -> CursorResult[Any]:
|
|
"""continue the _execute_context() method for a single DBAPI
|
|
cursor.execute() or cursor.executemany() call.
|
|
|
|
"""
|
|
if dialect.bind_typing is BindTyping.SETINPUTSIZES:
|
|
generic_setinputsizes = context._prepare_set_input_sizes()
|
|
|
|
if generic_setinputsizes:
|
|
try:
|
|
dialect.do_set_input_sizes(
|
|
context.cursor, generic_setinputsizes, context
|
|
)
|
|
except BaseException as e:
|
|
self._handle_dbapi_exception(
|
|
e, str(statement), parameters, None, context
|
|
)
|
|
|
|
cursor, str_statement, parameters = (
|
|
context.cursor,
|
|
context.statement,
|
|
context.parameters,
|
|
)
|
|
|
|
effective_parameters: Optional[_AnyExecuteParams]
|
|
|
|
if not context.executemany:
|
|
effective_parameters = parameters[0]
|
|
else:
|
|
effective_parameters = parameters
|
|
|
|
if self._has_events or self.engine._has_events:
|
|
for fn in self.dispatch.before_cursor_execute:
|
|
str_statement, effective_parameters = fn(
|
|
self,
|
|
cursor,
|
|
str_statement,
|
|
effective_parameters,
|
|
context,
|
|
context.executemany,
|
|
)
|
|
|
|
if self._echo:
|
|
self._log_info(str_statement)
|
|
|
|
stats = context._get_cache_stats()
|
|
|
|
if not self.engine.hide_parameters:
|
|
self._log_info(
|
|
"[%s] %r",
|
|
stats,
|
|
sql_util._repr_params(
|
|
effective_parameters,
|
|
batches=10,
|
|
ismulti=context.executemany,
|
|
),
|
|
)
|
|
else:
|
|
self._log_info(
|
|
"[%s] [SQL parameters hidden due to hide_parameters=True]",
|
|
stats,
|
|
)
|
|
|
|
evt_handled: bool = False
|
|
try:
|
|
if context.execute_style is ExecuteStyle.EXECUTEMANY:
|
|
effective_parameters = cast(
|
|
"_CoreMultiExecuteParams", effective_parameters
|
|
)
|
|
if self.dialect._has_events:
|
|
for fn in self.dialect.dispatch.do_executemany:
|
|
if fn(
|
|
cursor,
|
|
str_statement,
|
|
effective_parameters,
|
|
context,
|
|
):
|
|
evt_handled = True
|
|
break
|
|
if not evt_handled:
|
|
self.dialect.do_executemany(
|
|
cursor,
|
|
str_statement,
|
|
effective_parameters,
|
|
context,
|
|
)
|
|
elif not effective_parameters and context.no_parameters:
|
|
if self.dialect._has_events:
|
|
for fn in self.dialect.dispatch.do_execute_no_params:
|
|
if fn(cursor, str_statement, context):
|
|
evt_handled = True
|
|
break
|
|
if not evt_handled:
|
|
self.dialect.do_execute_no_params(
|
|
cursor, str_statement, context
|
|
)
|
|
else:
|
|
effective_parameters = cast(
|
|
"_CoreSingleExecuteParams", effective_parameters
|
|
)
|
|
if self.dialect._has_events:
|
|
for fn in self.dialect.dispatch.do_execute:
|
|
if fn(
|
|
cursor,
|
|
str_statement,
|
|
effective_parameters,
|
|
context,
|
|
):
|
|
evt_handled = True
|
|
break
|
|
if not evt_handled:
|
|
> self.dialect.do_execute(
|
|
cursor, str_statement, effective_parameters, context
|
|
)
|
|
|
|
.venv\Lib\site-packages\sqlalchemy\engine\base.py:1969:
|
|
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
|
|
|
|
self = <sqlalchemy.dialects.sqlite.pysqlite.SQLiteDialect_pysqlite object at 0x0000019AEB7B7250>
|
|
cursor = <sqlite3.Cursor object at 0x0000019AEBC6F740>
|
|
statement = 'SELECT users.id \nFROM users \nWHERE users.role = ?'
|
|
parameters = ('SUPER_ADMIN',)
|
|
context = <sqlalchemy.dialects.sqlite.base.SQLiteExecutionContext object at 0x0000019AEB896210>
|
|
|
|
def do_execute(self, cursor, statement, parameters, context=None):
|
|
> cursor.execute(statement, parameters)
|
|
E sqlite3.OperationalError: no such table: users
|
|
|
|
.venv\Lib\site-packages\sqlalchemy\engine\default.py:952: OperationalError
|
|
|
|
The above exception was the direct cause of the following exception:
|
|
|
|
db_session = <sqlalchemy.orm.session.Session object at 0x0000019AEB7D7E00>
|
|
monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x0000019AEB73E450>
|
|
|
|
@pytest.fixture()
|
|
def client(db_session, monkeypatch):
|
|
"""TestClient wired to the fixture DB; dynsec calls are faked out."""
|
|
# Point the app's session factory at the test engine.
|
|
monkeypatch.setattr(db_module, "_SessionFactory", None)
|
|
monkeypatch.setattr(
|
|
db_module, "_engine", db_session.get_bind()
|
|
)
|
|
|
|
# Fake dynsec so no broker is needed for API tests.
|
|
class FakeDynsec:
|
|
def __init__(self):
|
|
self.clients: dict[str, dict] = {}
|
|
self.roles: dict[str, dict] = {}
|
|
self.groups: dict[str, dict] = {}
|
|
|
|
def _command(self, command, **kwargs):
|
|
return {"success": True}
|
|
|
|
def create_client(self, username, password, **kwargs):
|
|
self.clients[username] = {"username": username, "enabled": True, **kwargs}
|
|
|
|
def list_clients(self):
|
|
return list(self.clients.values())
|
|
|
|
def delete_client(self, username):
|
|
self.clients.pop(username, None)
|
|
|
|
def disable_client(self, username):
|
|
self.clients[username]["enabled"] = False
|
|
|
|
def enable_client(self, username):
|
|
self.clients[username]["enabled"] = True
|
|
|
|
def set_client_password(self, username, password):
|
|
pass
|
|
|
|
def get_client(self, username):
|
|
return self.clients[username]
|
|
|
|
def create_group(self, groupname, roles=None):
|
|
self.groups[groupname] = {"groupname": groupname, "roles": roles or []}
|
|
|
|
def list_groups(self):
|
|
return list(self.groups.values())
|
|
|
|
def delete_group(self, groupname):
|
|
self.groups.pop(groupname, None)
|
|
|
|
def create_role(self, rolename, acls=None):
|
|
self.roles[rolename] = {"rolename": rolename, "acls": acls or []}
|
|
|
|
def list_roles(self):
|
|
return list(self.roles.values())
|
|
|
|
def delete_role(self, rolename):
|
|
self.roles.pop(rolename, None)
|
|
|
|
def set_role_acls(self, rolename, acls):
|
|
self.roles[rolename]["acls"] = acls
|
|
|
|
def get_role(self, rolename):
|
|
return self.roles[rolename]
|
|
|
|
def connect(self):
|
|
pass
|
|
|
|
def close(self):
|
|
pass
|
|
|
|
fake = FakeDynsec()
|
|
monkeypatch.setattr(main_module, "dynsec_client", fake)
|
|
|
|
from app.routers import mqtt as mqtt_router
|
|
|
|
monkeypatch.setattr(mqtt_router, "dynsec_client", fake)
|
|
|
|
> with TestClient(main_module.app) as test_client:
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
|
|
tests\conftest.py:132:
|
|
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
|
|
.venv\Lib\site-packages\starlette\testclient.py:698: in __enter__
|
|
portal.call(self.wait_startup)
|
|
.venv\Lib\site-packages\anyio\from_thread.py:340: in call
|
|
return cast(T_Retval, self.start_task_soon(func, *args).result())
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
..\..\..\..\AppData\Local\Python\pythoncore-3.14-64\Lib\concurrent\futures\_base.py:450: in result
|
|
return self.__get_result()
|
|
^^^^^^^^^^^^^^^^^^^
|
|
..\..\..\..\AppData\Local\Python\pythoncore-3.14-64\Lib\concurrent\futures\_base.py:395: in __get_result
|
|
raise self._exception
|
|
.venv\Lib\site-packages\anyio\from_thread.py:265: in _call_func
|
|
retval = await retval_or_awaitable
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
.venv\Lib\site-packages\starlette\testclient.py:733: in wait_startup
|
|
await receive()
|
|
.venv\Lib\site-packages\starlette\testclient.py:724: in receive
|
|
self.task.result()
|
|
..\..\..\..\AppData\Local\Python\pythoncore-3.14-64\Lib\concurrent\futures\_base.py:443: in result
|
|
return self.__get_result()
|
|
^^^^^^^^^^^^^^^^^^^
|
|
..\..\..\..\AppData\Local\Python\pythoncore-3.14-64\Lib\concurrent\futures\_base.py:395: in __get_result
|
|
raise self._exception
|
|
.venv\Lib\site-packages\anyio\from_thread.py:265: in _call_func
|
|
retval = await retval_or_awaitable
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
.venv\Lib\site-packages\starlette\testclient.py:714: in lifespan
|
|
await self.app(scope, self.stream_receive.receive, self.stream_send.send)
|
|
.venv\Lib\site-packages\fastapi\applications.py:1163: in __call__
|
|
await super().__call__(scope, receive, send)
|
|
.venv\Lib\site-packages\starlette\applications.py:96: in __call__
|
|
await self.middleware_stack(scope, receive, send)
|
|
.venv\Lib\site-packages\starlette\middleware\errors.py:151: in __call__
|
|
await self.app(scope, receive, send)
|
|
.venv\Lib\site-packages\starlette\middleware\base.py:103: in __call__
|
|
await self.app(scope, receive, send)
|
|
.venv\Lib\site-packages\starlette\middleware\cors.py:80: in __call__
|
|
await self.app(scope, receive, send)
|
|
.venv\Lib\site-packages\starlette\middleware\exceptions.py:49: in __call__
|
|
await self.app(scope, receive, send)
|
|
.venv\Lib\site-packages\fastapi\middleware\asyncexitstack.py:18: in __call__
|
|
await self.app(scope, receive, send)
|
|
.venv\Lib\site-packages\starlette\routing.py:670: in __call__
|
|
await self.middleware_stack(scope, receive, send)
|
|
.venv\Lib\site-packages\fastapi\routing.py:2726: in app
|
|
await self.lifespan(scope, receive, send)
|
|
.venv\Lib\site-packages\starlette\routing.py:648: in lifespan
|
|
async with self.lifespan_context(app) as maybe_state:
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
..\..\..\..\AppData\Local\Python\pythoncore-3.14-64\Lib\contextlib.py:214: in __aenter__
|
|
return await anext(self.gen)
|
|
^^^^^^^^^^^^^^^^^^^^^
|
|
.venv\Lib\site-packages\fastapi\routing.py:240: in merged_lifespan
|
|
async with original_context(app) as maybe_original_state:
|
|
^^^^^^^^^^^^^^^^^^^^^
|
|
..\..\..\..\AppData\Local\Python\pythoncore-3.14-64\Lib\contextlib.py:214: in __aenter__
|
|
return await anext(self.gen)
|
|
^^^^^^^^^^^^^^^^^^^^^
|
|
.venv\Lib\site-packages\fastapi\routing.py:240: in merged_lifespan
|
|
async with original_context(app) as maybe_original_state:
|
|
^^^^^^^^^^^^^^^^^^^^^
|
|
..\..\..\..\AppData\Local\Python\pythoncore-3.14-64\Lib\contextlib.py:214: in __aenter__
|
|
return await anext(self.gen)
|
|
^^^^^^^^^^^^^^^^^^^^^
|
|
.venv\Lib\site-packages\fastapi\routing.py:240: in merged_lifespan
|
|
async with original_context(app) as maybe_original_state:
|
|
^^^^^^^^^^^^^^^^^^^^^
|
|
..\..\..\..\AppData\Local\Python\pythoncore-3.14-64\Lib\contextlib.py:214: in __aenter__
|
|
return await anext(self.gen)
|
|
^^^^^^^^^^^^^^^^^^^^^
|
|
.venv\Lib\site-packages\fastapi\routing.py:240: in merged_lifespan
|
|
async with original_context(app) as maybe_original_state:
|
|
^^^^^^^^^^^^^^^^^^^^^
|
|
..\..\..\..\AppData\Local\Python\pythoncore-3.14-64\Lib\contextlib.py:214: in __aenter__
|
|
return await anext(self.gen)
|
|
^^^^^^^^^^^^^^^^^^^^^
|
|
app\main.py:65: in lifespan
|
|
_bootstrap_admin()
|
|
app\main.py:34: in _bootstrap_admin
|
|
has_admin = session.scalar(
|
|
.venv\Lib\site-packages\sqlalchemy\orm\session.py:2421: in scalar
|
|
return self._execute_internal(
|
|
.venv\Lib\site-packages\sqlalchemy\orm\session.py:2271: in _execute_internal
|
|
result: Result[Any] = compile_state_cls.orm_execute_statement(
|
|
.venv\Lib\site-packages\sqlalchemy\orm\context.py:306: in orm_execute_statement
|
|
result = conn.execute(
|
|
.venv\Lib\site-packages\sqlalchemy\engine\base.py:1421: in execute
|
|
return meth(
|
|
.venv\Lib\site-packages\sqlalchemy\sql\elements.py:526: in _execute_on_connection
|
|
return connection._execute_clauseelement(
|
|
.venv\Lib\site-packages\sqlalchemy\engine\base.py:1643: in _execute_clauseelement
|
|
ret = self._execute_context(
|
|
.venv\Lib\site-packages\sqlalchemy\engine\base.py:1848: in _execute_context
|
|
return self._exec_single_context(
|
|
.venv\Lib\site-packages\sqlalchemy\engine\base.py:1988: in _exec_single_context
|
|
self._handle_dbapi_exception(
|
|
.venv\Lib\site-packages\sqlalchemy\engine\base.py:2365: in _handle_dbapi_exception
|
|
raise sqlalchemy_exception.with_traceback(exc_info[2]) from e
|
|
.venv\Lib\site-packages\sqlalchemy\engine\base.py:1969: in _exec_single_context
|
|
self.dialect.do_execute(
|
|
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
|
|
|
|
self = <sqlalchemy.dialects.sqlite.pysqlite.SQLiteDialect_pysqlite object at 0x0000019AEB7B7250>
|
|
cursor = <sqlite3.Cursor object at 0x0000019AEBC6F740>
|
|
statement = 'SELECT users.id \nFROM users \nWHERE users.role = ?'
|
|
parameters = ('SUPER_ADMIN',)
|
|
context = <sqlalchemy.dialects.sqlite.base.SQLiteExecutionContext object at 0x0000019AEB896210>
|
|
|
|
def do_execute(self, cursor, statement, parameters, context=None):
|
|
> cursor.execute(statement, parameters)
|
|
E sqlalchemy.exc.OperationalError: (sqlite3.OperationalError) no such table: users
|
|
E [SQL: SELECT users.id
|
|
E FROM users
|
|
E WHERE users.role = ?]
|
|
E [parameters: ('SUPER_ADMIN',)]
|
|
E (Background on this error at: https://sqlalche.me/e/20/e3q8)
|
|
|
|
.venv\Lib\site-packages\sqlalchemy\engine\default.py:952: OperationalError
|
|
_____________ ERROR at setup of test_unauthenticated_api_rejected _____________
|
|
|
|
self = <sqlalchemy.engine.base.Connection object at 0x0000019AEBACDB80>
|
|
dialect = <sqlalchemy.dialects.sqlite.pysqlite.SQLiteDialect_pysqlite object at 0x0000019AEB7B6210>
|
|
context = <sqlalchemy.dialects.sqlite.base.SQLiteExecutionContext object at 0x0000019AEB895C10>
|
|
statement = <sqlalchemy.dialects.sqlite.base.SQLiteCompiler object at 0x0000019AEBAC9C50>
|
|
parameters = [('SUPER_ADMIN',)]
|
|
|
|
def _exec_single_context(
|
|
self,
|
|
dialect: Dialect,
|
|
context: ExecutionContext,
|
|
statement: Union[str, Compiled],
|
|
parameters: Optional[_AnyMultiExecuteParams],
|
|
) -> CursorResult[Any]:
|
|
"""continue the _execute_context() method for a single DBAPI
|
|
cursor.execute() or cursor.executemany() call.
|
|
|
|
"""
|
|
if dialect.bind_typing is BindTyping.SETINPUTSIZES:
|
|
generic_setinputsizes = context._prepare_set_input_sizes()
|
|
|
|
if generic_setinputsizes:
|
|
try:
|
|
dialect.do_set_input_sizes(
|
|
context.cursor, generic_setinputsizes, context
|
|
)
|
|
except BaseException as e:
|
|
self._handle_dbapi_exception(
|
|
e, str(statement), parameters, None, context
|
|
)
|
|
|
|
cursor, str_statement, parameters = (
|
|
context.cursor,
|
|
context.statement,
|
|
context.parameters,
|
|
)
|
|
|
|
effective_parameters: Optional[_AnyExecuteParams]
|
|
|
|
if not context.executemany:
|
|
effective_parameters = parameters[0]
|
|
else:
|
|
effective_parameters = parameters
|
|
|
|
if self._has_events or self.engine._has_events:
|
|
for fn in self.dispatch.before_cursor_execute:
|
|
str_statement, effective_parameters = fn(
|
|
self,
|
|
cursor,
|
|
str_statement,
|
|
effective_parameters,
|
|
context,
|
|
context.executemany,
|
|
)
|
|
|
|
if self._echo:
|
|
self._log_info(str_statement)
|
|
|
|
stats = context._get_cache_stats()
|
|
|
|
if not self.engine.hide_parameters:
|
|
self._log_info(
|
|
"[%s] %r",
|
|
stats,
|
|
sql_util._repr_params(
|
|
effective_parameters,
|
|
batches=10,
|
|
ismulti=context.executemany,
|
|
),
|
|
)
|
|
else:
|
|
self._log_info(
|
|
"[%s] [SQL parameters hidden due to hide_parameters=True]",
|
|
stats,
|
|
)
|
|
|
|
evt_handled: bool = False
|
|
try:
|
|
if context.execute_style is ExecuteStyle.EXECUTEMANY:
|
|
effective_parameters = cast(
|
|
"_CoreMultiExecuteParams", effective_parameters
|
|
)
|
|
if self.dialect._has_events:
|
|
for fn in self.dialect.dispatch.do_executemany:
|
|
if fn(
|
|
cursor,
|
|
str_statement,
|
|
effective_parameters,
|
|
context,
|
|
):
|
|
evt_handled = True
|
|
break
|
|
if not evt_handled:
|
|
self.dialect.do_executemany(
|
|
cursor,
|
|
str_statement,
|
|
effective_parameters,
|
|
context,
|
|
)
|
|
elif not effective_parameters and context.no_parameters:
|
|
if self.dialect._has_events:
|
|
for fn in self.dialect.dispatch.do_execute_no_params:
|
|
if fn(cursor, str_statement, context):
|
|
evt_handled = True
|
|
break
|
|
if not evt_handled:
|
|
self.dialect.do_execute_no_params(
|
|
cursor, str_statement, context
|
|
)
|
|
else:
|
|
effective_parameters = cast(
|
|
"_CoreSingleExecuteParams", effective_parameters
|
|
)
|
|
if self.dialect._has_events:
|
|
for fn in self.dialect.dispatch.do_execute:
|
|
if fn(
|
|
cursor,
|
|
str_statement,
|
|
effective_parameters,
|
|
context,
|
|
):
|
|
evt_handled = True
|
|
break
|
|
if not evt_handled:
|
|
> self.dialect.do_execute(
|
|
cursor, str_statement, effective_parameters, context
|
|
)
|
|
|
|
.venv\Lib\site-packages\sqlalchemy\engine\base.py:1969:
|
|
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
|
|
|
|
self = <sqlalchemy.dialects.sqlite.pysqlite.SQLiteDialect_pysqlite object at 0x0000019AEB7B6210>
|
|
cursor = <sqlite3.Cursor object at 0x0000019AED6F5840>
|
|
statement = 'SELECT users.id \nFROM users \nWHERE users.role = ?'
|
|
parameters = ('SUPER_ADMIN',)
|
|
context = <sqlalchemy.dialects.sqlite.base.SQLiteExecutionContext object at 0x0000019AEB895C10>
|
|
|
|
def do_execute(self, cursor, statement, parameters, context=None):
|
|
> cursor.execute(statement, parameters)
|
|
E sqlite3.OperationalError: no such table: users
|
|
|
|
.venv\Lib\site-packages\sqlalchemy\engine\default.py:952: OperationalError
|
|
|
|
The above exception was the direct cause of the following exception:
|
|
|
|
db_session = <sqlalchemy.orm.session.Session object at 0x0000019AEB7D5940>
|
|
monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x0000019AEBA88160>
|
|
|
|
@pytest.fixture()
|
|
def client(db_session, monkeypatch):
|
|
"""TestClient wired to the fixture DB; dynsec calls are faked out."""
|
|
# Point the app's session factory at the test engine.
|
|
monkeypatch.setattr(db_module, "_SessionFactory", None)
|
|
monkeypatch.setattr(
|
|
db_module, "_engine", db_session.get_bind()
|
|
)
|
|
|
|
# Fake dynsec so no broker is needed for API tests.
|
|
class FakeDynsec:
|
|
def __init__(self):
|
|
self.clients: dict[str, dict] = {}
|
|
self.roles: dict[str, dict] = {}
|
|
self.groups: dict[str, dict] = {}
|
|
|
|
def _command(self, command, **kwargs):
|
|
return {"success": True}
|
|
|
|
def create_client(self, username, password, **kwargs):
|
|
self.clients[username] = {"username": username, "enabled": True, **kwargs}
|
|
|
|
def list_clients(self):
|
|
return list(self.clients.values())
|
|
|
|
def delete_client(self, username):
|
|
self.clients.pop(username, None)
|
|
|
|
def disable_client(self, username):
|
|
self.clients[username]["enabled"] = False
|
|
|
|
def enable_client(self, username):
|
|
self.clients[username]["enabled"] = True
|
|
|
|
def set_client_password(self, username, password):
|
|
pass
|
|
|
|
def get_client(self, username):
|
|
return self.clients[username]
|
|
|
|
def create_group(self, groupname, roles=None):
|
|
self.groups[groupname] = {"groupname": groupname, "roles": roles or []}
|
|
|
|
def list_groups(self):
|
|
return list(self.groups.values())
|
|
|
|
def delete_group(self, groupname):
|
|
self.groups.pop(groupname, None)
|
|
|
|
def create_role(self, rolename, acls=None):
|
|
self.roles[rolename] = {"rolename": rolename, "acls": acls or []}
|
|
|
|
def list_roles(self):
|
|
return list(self.roles.values())
|
|
|
|
def delete_role(self, rolename):
|
|
self.roles.pop(rolename, None)
|
|
|
|
def set_role_acls(self, rolename, acls):
|
|
self.roles[rolename]["acls"] = acls
|
|
|
|
def get_role(self, rolename):
|
|
return self.roles[rolename]
|
|
|
|
def connect(self):
|
|
pass
|
|
|
|
def close(self):
|
|
pass
|
|
|
|
fake = FakeDynsec()
|
|
monkeypatch.setattr(main_module, "dynsec_client", fake)
|
|
|
|
from app.routers import mqtt as mqtt_router
|
|
|
|
monkeypatch.setattr(mqtt_router, "dynsec_client", fake)
|
|
|
|
> with TestClient(main_module.app) as test_client:
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
|
|
tests\conftest.py:132:
|
|
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
|
|
.venv\Lib\site-packages\starlette\testclient.py:698: in __enter__
|
|
portal.call(self.wait_startup)
|
|
.venv\Lib\site-packages\anyio\from_thread.py:340: in call
|
|
return cast(T_Retval, self.start_task_soon(func, *args).result())
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
..\..\..\..\AppData\Local\Python\pythoncore-3.14-64\Lib\concurrent\futures\_base.py:450: in result
|
|
return self.__get_result()
|
|
^^^^^^^^^^^^^^^^^^^
|
|
..\..\..\..\AppData\Local\Python\pythoncore-3.14-64\Lib\concurrent\futures\_base.py:395: in __get_result
|
|
raise self._exception
|
|
.venv\Lib\site-packages\anyio\from_thread.py:265: in _call_func
|
|
retval = await retval_or_awaitable
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
.venv\Lib\site-packages\starlette\testclient.py:733: in wait_startup
|
|
await receive()
|
|
.venv\Lib\site-packages\starlette\testclient.py:724: in receive
|
|
self.task.result()
|
|
..\..\..\..\AppData\Local\Python\pythoncore-3.14-64\Lib\concurrent\futures\_base.py:443: in result
|
|
return self.__get_result()
|
|
^^^^^^^^^^^^^^^^^^^
|
|
..\..\..\..\AppData\Local\Python\pythoncore-3.14-64\Lib\concurrent\futures\_base.py:395: in __get_result
|
|
raise self._exception
|
|
.venv\Lib\site-packages\anyio\from_thread.py:265: in _call_func
|
|
retval = await retval_or_awaitable
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
.venv\Lib\site-packages\starlette\testclient.py:714: in lifespan
|
|
await self.app(scope, self.stream_receive.receive, self.stream_send.send)
|
|
.venv\Lib\site-packages\fastapi\applications.py:1163: in __call__
|
|
await super().__call__(scope, receive, send)
|
|
.venv\Lib\site-packages\starlette\applications.py:96: in __call__
|
|
await self.middleware_stack(scope, receive, send)
|
|
.venv\Lib\site-packages\starlette\middleware\errors.py:151: in __call__
|
|
await self.app(scope, receive, send)
|
|
.venv\Lib\site-packages\starlette\middleware\base.py:103: in __call__
|
|
await self.app(scope, receive, send)
|
|
.venv\Lib\site-packages\starlette\middleware\cors.py:80: in __call__
|
|
await self.app(scope, receive, send)
|
|
.venv\Lib\site-packages\starlette\middleware\exceptions.py:49: in __call__
|
|
await self.app(scope, receive, send)
|
|
.venv\Lib\site-packages\fastapi\middleware\asyncexitstack.py:18: in __call__
|
|
await self.app(scope, receive, send)
|
|
.venv\Lib\site-packages\starlette\routing.py:670: in __call__
|
|
await self.middleware_stack(scope, receive, send)
|
|
.venv\Lib\site-packages\fastapi\routing.py:2726: in app
|
|
await self.lifespan(scope, receive, send)
|
|
.venv\Lib\site-packages\starlette\routing.py:648: in lifespan
|
|
async with self.lifespan_context(app) as maybe_state:
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
..\..\..\..\AppData\Local\Python\pythoncore-3.14-64\Lib\contextlib.py:214: in __aenter__
|
|
return await anext(self.gen)
|
|
^^^^^^^^^^^^^^^^^^^^^
|
|
.venv\Lib\site-packages\fastapi\routing.py:240: in merged_lifespan
|
|
async with original_context(app) as maybe_original_state:
|
|
^^^^^^^^^^^^^^^^^^^^^
|
|
..\..\..\..\AppData\Local\Python\pythoncore-3.14-64\Lib\contextlib.py:214: in __aenter__
|
|
return await anext(self.gen)
|
|
^^^^^^^^^^^^^^^^^^^^^
|
|
.venv\Lib\site-packages\fastapi\routing.py:240: in merged_lifespan
|
|
async with original_context(app) as maybe_original_state:
|
|
^^^^^^^^^^^^^^^^^^^^^
|
|
..\..\..\..\AppData\Local\Python\pythoncore-3.14-64\Lib\contextlib.py:214: in __aenter__
|
|
return await anext(self.gen)
|
|
^^^^^^^^^^^^^^^^^^^^^
|
|
.venv\Lib\site-packages\fastapi\routing.py:240: in merged_lifespan
|
|
async with original_context(app) as maybe_original_state:
|
|
^^^^^^^^^^^^^^^^^^^^^
|
|
..\..\..\..\AppData\Local\Python\pythoncore-3.14-64\Lib\contextlib.py:214: in __aenter__
|
|
return await anext(self.gen)
|
|
^^^^^^^^^^^^^^^^^^^^^
|
|
.venv\Lib\site-packages\fastapi\routing.py:240: in merged_lifespan
|
|
async with original_context(app) as maybe_original_state:
|
|
^^^^^^^^^^^^^^^^^^^^^
|
|
..\..\..\..\AppData\Local\Python\pythoncore-3.14-64\Lib\contextlib.py:214: in __aenter__
|
|
return await anext(self.gen)
|
|
^^^^^^^^^^^^^^^^^^^^^
|
|
app\main.py:65: in lifespan
|
|
_bootstrap_admin()
|
|
app\main.py:34: in _bootstrap_admin
|
|
has_admin = session.scalar(
|
|
.venv\Lib\site-packages\sqlalchemy\orm\session.py:2421: in scalar
|
|
return self._execute_internal(
|
|
.venv\Lib\site-packages\sqlalchemy\orm\session.py:2271: in _execute_internal
|
|
result: Result[Any] = compile_state_cls.orm_execute_statement(
|
|
.venv\Lib\site-packages\sqlalchemy\orm\context.py:306: in orm_execute_statement
|
|
result = conn.execute(
|
|
.venv\Lib\site-packages\sqlalchemy\engine\base.py:1421: in execute
|
|
return meth(
|
|
.venv\Lib\site-packages\sqlalchemy\sql\elements.py:526: in _execute_on_connection
|
|
return connection._execute_clauseelement(
|
|
.venv\Lib\site-packages\sqlalchemy\engine\base.py:1643: in _execute_clauseelement
|
|
ret = self._execute_context(
|
|
.venv\Lib\site-packages\sqlalchemy\engine\base.py:1848: in _execute_context
|
|
return self._exec_single_context(
|
|
.venv\Lib\site-packages\sqlalchemy\engine\base.py:1988: in _exec_single_context
|
|
self._handle_dbapi_exception(
|
|
.venv\Lib\site-packages\sqlalchemy\engine\base.py:2365: in _handle_dbapi_exception
|
|
raise sqlalchemy_exception.with_traceback(exc_info[2]) from e
|
|
.venv\Lib\site-packages\sqlalchemy\engine\base.py:1969: in _exec_single_context
|
|
self.dialect.do_execute(
|
|
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
|
|
|
|
self = <sqlalchemy.dialects.sqlite.pysqlite.SQLiteDialect_pysqlite object at 0x0000019AEB7B6210>
|
|
cursor = <sqlite3.Cursor object at 0x0000019AED6F5840>
|
|
statement = 'SELECT users.id \nFROM users \nWHERE users.role = ?'
|
|
parameters = ('SUPER_ADMIN',)
|
|
context = <sqlalchemy.dialects.sqlite.base.SQLiteExecutionContext object at 0x0000019AEB895C10>
|
|
|
|
def do_execute(self, cursor, statement, parameters, context=None):
|
|
> cursor.execute(statement, parameters)
|
|
E sqlalchemy.exc.OperationalError: (sqlite3.OperationalError) no such table: users
|
|
E [SQL: SELECT users.id
|
|
E FROM users
|
|
E WHERE users.role = ?]
|
|
E [parameters: ('SUPER_ADMIN',)]
|
|
E (Background on this error at: https://sqlalche.me/e/20/e3q8)
|
|
|
|
.venv\Lib\site-packages\sqlalchemy\engine\default.py:952: OperationalError
|
|
____________ ERROR at setup of test_auditor_cannot_create_clients _____________
|
|
|
|
self = <sqlalchemy.engine.base.Connection object at 0x0000019AEBD814F0>
|
|
dialect = <sqlalchemy.dialects.sqlite.pysqlite.SQLiteDialect_pysqlite object at 0x0000019AEB7B6FD0>
|
|
context = <sqlalchemy.dialects.sqlite.base.SQLiteExecutionContext object at 0x0000019AEB896F90>
|
|
statement = <sqlalchemy.dialects.sqlite.base.SQLiteCompiler object at 0x0000019AEBDDD550>
|
|
parameters = [('SUPER_ADMIN',)]
|
|
|
|
def _exec_single_context(
|
|
self,
|
|
dialect: Dialect,
|
|
context: ExecutionContext,
|
|
statement: Union[str, Compiled],
|
|
parameters: Optional[_AnyMultiExecuteParams],
|
|
) -> CursorResult[Any]:
|
|
"""continue the _execute_context() method for a single DBAPI
|
|
cursor.execute() or cursor.executemany() call.
|
|
|
|
"""
|
|
if dialect.bind_typing is BindTyping.SETINPUTSIZES:
|
|
generic_setinputsizes = context._prepare_set_input_sizes()
|
|
|
|
if generic_setinputsizes:
|
|
try:
|
|
dialect.do_set_input_sizes(
|
|
context.cursor, generic_setinputsizes, context
|
|
)
|
|
except BaseException as e:
|
|
self._handle_dbapi_exception(
|
|
e, str(statement), parameters, None, context
|
|
)
|
|
|
|
cursor, str_statement, parameters = (
|
|
context.cursor,
|
|
context.statement,
|
|
context.parameters,
|
|
)
|
|
|
|
effective_parameters: Optional[_AnyExecuteParams]
|
|
|
|
if not context.executemany:
|
|
effective_parameters = parameters[0]
|
|
else:
|
|
effective_parameters = parameters
|
|
|
|
if self._has_events or self.engine._has_events:
|
|
for fn in self.dispatch.before_cursor_execute:
|
|
str_statement, effective_parameters = fn(
|
|
self,
|
|
cursor,
|
|
str_statement,
|
|
effective_parameters,
|
|
context,
|
|
context.executemany,
|
|
)
|
|
|
|
if self._echo:
|
|
self._log_info(str_statement)
|
|
|
|
stats = context._get_cache_stats()
|
|
|
|
if not self.engine.hide_parameters:
|
|
self._log_info(
|
|
"[%s] %r",
|
|
stats,
|
|
sql_util._repr_params(
|
|
effective_parameters,
|
|
batches=10,
|
|
ismulti=context.executemany,
|
|
),
|
|
)
|
|
else:
|
|
self._log_info(
|
|
"[%s] [SQL parameters hidden due to hide_parameters=True]",
|
|
stats,
|
|
)
|
|
|
|
evt_handled: bool = False
|
|
try:
|
|
if context.execute_style is ExecuteStyle.EXECUTEMANY:
|
|
effective_parameters = cast(
|
|
"_CoreMultiExecuteParams", effective_parameters
|
|
)
|
|
if self.dialect._has_events:
|
|
for fn in self.dialect.dispatch.do_executemany:
|
|
if fn(
|
|
cursor,
|
|
str_statement,
|
|
effective_parameters,
|
|
context,
|
|
):
|
|
evt_handled = True
|
|
break
|
|
if not evt_handled:
|
|
self.dialect.do_executemany(
|
|
cursor,
|
|
str_statement,
|
|
effective_parameters,
|
|
context,
|
|
)
|
|
elif not effective_parameters and context.no_parameters:
|
|
if self.dialect._has_events:
|
|
for fn in self.dialect.dispatch.do_execute_no_params:
|
|
if fn(cursor, str_statement, context):
|
|
evt_handled = True
|
|
break
|
|
if not evt_handled:
|
|
self.dialect.do_execute_no_params(
|
|
cursor, str_statement, context
|
|
)
|
|
else:
|
|
effective_parameters = cast(
|
|
"_CoreSingleExecuteParams", effective_parameters
|
|
)
|
|
if self.dialect._has_events:
|
|
for fn in self.dialect.dispatch.do_execute:
|
|
if fn(
|
|
cursor,
|
|
str_statement,
|
|
effective_parameters,
|
|
context,
|
|
):
|
|
evt_handled = True
|
|
break
|
|
if not evt_handled:
|
|
> self.dialect.do_execute(
|
|
cursor, str_statement, effective_parameters, context
|
|
)
|
|
|
|
.venv\Lib\site-packages\sqlalchemy\engine\base.py:1969:
|
|
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
|
|
|
|
self = <sqlalchemy.dialects.sqlite.pysqlite.SQLiteDialect_pysqlite object at 0x0000019AEB7B6FD0>
|
|
cursor = <sqlite3.Cursor object at 0x0000019AEB7F7840>
|
|
statement = 'SELECT users.id \nFROM users \nWHERE users.role = ?'
|
|
parameters = ('SUPER_ADMIN',)
|
|
context = <sqlalchemy.dialects.sqlite.base.SQLiteExecutionContext object at 0x0000019AEB896F90>
|
|
|
|
def do_execute(self, cursor, statement, parameters, context=None):
|
|
> cursor.execute(statement, parameters)
|
|
E sqlite3.OperationalError: no such table: users
|
|
|
|
.venv\Lib\site-packages\sqlalchemy\engine\default.py:952: OperationalError
|
|
|
|
The above exception was the direct cause of the following exception:
|
|
|
|
db_session = <sqlalchemy.orm.session.Session object at 0x0000019AEB7D5E80>
|
|
monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x0000019AEBA8A8B0>
|
|
|
|
@pytest.fixture()
|
|
def client(db_session, monkeypatch):
|
|
"""TestClient wired to the fixture DB; dynsec calls are faked out."""
|
|
# Point the app's session factory at the test engine.
|
|
monkeypatch.setattr(db_module, "_SessionFactory", None)
|
|
monkeypatch.setattr(
|
|
db_module, "_engine", db_session.get_bind()
|
|
)
|
|
|
|
# Fake dynsec so no broker is needed for API tests.
|
|
class FakeDynsec:
|
|
def __init__(self):
|
|
self.clients: dict[str, dict] = {}
|
|
self.roles: dict[str, dict] = {}
|
|
self.groups: dict[str, dict] = {}
|
|
|
|
def _command(self, command, **kwargs):
|
|
return {"success": True}
|
|
|
|
def create_client(self, username, password, **kwargs):
|
|
self.clients[username] = {"username": username, "enabled": True, **kwargs}
|
|
|
|
def list_clients(self):
|
|
return list(self.clients.values())
|
|
|
|
def delete_client(self, username):
|
|
self.clients.pop(username, None)
|
|
|
|
def disable_client(self, username):
|
|
self.clients[username]["enabled"] = False
|
|
|
|
def enable_client(self, username):
|
|
self.clients[username]["enabled"] = True
|
|
|
|
def set_client_password(self, username, password):
|
|
pass
|
|
|
|
def get_client(self, username):
|
|
return self.clients[username]
|
|
|
|
def create_group(self, groupname, roles=None):
|
|
self.groups[groupname] = {"groupname": groupname, "roles": roles or []}
|
|
|
|
def list_groups(self):
|
|
return list(self.groups.values())
|
|
|
|
def delete_group(self, groupname):
|
|
self.groups.pop(groupname, None)
|
|
|
|
def create_role(self, rolename, acls=None):
|
|
self.roles[rolename] = {"rolename": rolename, "acls": acls or []}
|
|
|
|
def list_roles(self):
|
|
return list(self.roles.values())
|
|
|
|
def delete_role(self, rolename):
|
|
self.roles.pop(rolename, None)
|
|
|
|
def set_role_acls(self, rolename, acls):
|
|
self.roles[rolename]["acls"] = acls
|
|
|
|
def get_role(self, rolename):
|
|
return self.roles[rolename]
|
|
|
|
def connect(self):
|
|
pass
|
|
|
|
def close(self):
|
|
pass
|
|
|
|
fake = FakeDynsec()
|
|
monkeypatch.setattr(main_module, "dynsec_client", fake)
|
|
|
|
from app.routers import mqtt as mqtt_router
|
|
|
|
monkeypatch.setattr(mqtt_router, "dynsec_client", fake)
|
|
|
|
> with TestClient(main_module.app) as test_client:
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
|
|
tests\conftest.py:132:
|
|
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
|
|
.venv\Lib\site-packages\starlette\testclient.py:698: in __enter__
|
|
portal.call(self.wait_startup)
|
|
.venv\Lib\site-packages\anyio\from_thread.py:340: in call
|
|
return cast(T_Retval, self.start_task_soon(func, *args).result())
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
..\..\..\..\AppData\Local\Python\pythoncore-3.14-64\Lib\concurrent\futures\_base.py:450: in result
|
|
return self.__get_result()
|
|
^^^^^^^^^^^^^^^^^^^
|
|
..\..\..\..\AppData\Local\Python\pythoncore-3.14-64\Lib\concurrent\futures\_base.py:395: in __get_result
|
|
raise self._exception
|
|
.venv\Lib\site-packages\anyio\from_thread.py:265: in _call_func
|
|
retval = await retval_or_awaitable
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
.venv\Lib\site-packages\starlette\testclient.py:733: in wait_startup
|
|
await receive()
|
|
.venv\Lib\site-packages\starlette\testclient.py:724: in receive
|
|
self.task.result()
|
|
..\..\..\..\AppData\Local\Python\pythoncore-3.14-64\Lib\concurrent\futures\_base.py:443: in result
|
|
return self.__get_result()
|
|
^^^^^^^^^^^^^^^^^^^
|
|
..\..\..\..\AppData\Local\Python\pythoncore-3.14-64\Lib\concurrent\futures\_base.py:395: in __get_result
|
|
raise self._exception
|
|
.venv\Lib\site-packages\anyio\from_thread.py:265: in _call_func
|
|
retval = await retval_or_awaitable
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
.venv\Lib\site-packages\starlette\testclient.py:714: in lifespan
|
|
await self.app(scope, self.stream_receive.receive, self.stream_send.send)
|
|
.venv\Lib\site-packages\fastapi\applications.py:1163: in __call__
|
|
await super().__call__(scope, receive, send)
|
|
.venv\Lib\site-packages\starlette\applications.py:96: in __call__
|
|
await self.middleware_stack(scope, receive, send)
|
|
.venv\Lib\site-packages\starlette\middleware\errors.py:151: in __call__
|
|
await self.app(scope, receive, send)
|
|
.venv\Lib\site-packages\starlette\middleware\base.py:103: in __call__
|
|
await self.app(scope, receive, send)
|
|
.venv\Lib\site-packages\starlette\middleware\cors.py:80: in __call__
|
|
await self.app(scope, receive, send)
|
|
.venv\Lib\site-packages\starlette\middleware\exceptions.py:49: in __call__
|
|
await self.app(scope, receive, send)
|
|
.venv\Lib\site-packages\fastapi\middleware\asyncexitstack.py:18: in __call__
|
|
await self.app(scope, receive, send)
|
|
.venv\Lib\site-packages\starlette\routing.py:670: in __call__
|
|
await self.middleware_stack(scope, receive, send)
|
|
.venv\Lib\site-packages\fastapi\routing.py:2726: in app
|
|
await self.lifespan(scope, receive, send)
|
|
.venv\Lib\site-packages\starlette\routing.py:648: in lifespan
|
|
async with self.lifespan_context(app) as maybe_state:
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
..\..\..\..\AppData\Local\Python\pythoncore-3.14-64\Lib\contextlib.py:214: in __aenter__
|
|
return await anext(self.gen)
|
|
^^^^^^^^^^^^^^^^^^^^^
|
|
.venv\Lib\site-packages\fastapi\routing.py:240: in merged_lifespan
|
|
async with original_context(app) as maybe_original_state:
|
|
^^^^^^^^^^^^^^^^^^^^^
|
|
..\..\..\..\AppData\Local\Python\pythoncore-3.14-64\Lib\contextlib.py:214: in __aenter__
|
|
return await anext(self.gen)
|
|
^^^^^^^^^^^^^^^^^^^^^
|
|
.venv\Lib\site-packages\fastapi\routing.py:240: in merged_lifespan
|
|
async with original_context(app) as maybe_original_state:
|
|
^^^^^^^^^^^^^^^^^^^^^
|
|
..\..\..\..\AppData\Local\Python\pythoncore-3.14-64\Lib\contextlib.py:214: in __aenter__
|
|
return await anext(self.gen)
|
|
^^^^^^^^^^^^^^^^^^^^^
|
|
.venv\Lib\site-packages\fastapi\routing.py:240: in merged_lifespan
|
|
async with original_context(app) as maybe_original_state:
|
|
^^^^^^^^^^^^^^^^^^^^^
|
|
..\..\..\..\AppData\Local\Python\pythoncore-3.14-64\Lib\contextlib.py:214: in __aenter__
|
|
return await anext(self.gen)
|
|
^^^^^^^^^^^^^^^^^^^^^
|
|
.venv\Lib\site-packages\fastapi\routing.py:240: in merged_lifespan
|
|
async with original_context(app) as maybe_original_state:
|
|
^^^^^^^^^^^^^^^^^^^^^
|
|
..\..\..\..\AppData\Local\Python\pythoncore-3.14-64\Lib\contextlib.py:214: in __aenter__
|
|
return await anext(self.gen)
|
|
^^^^^^^^^^^^^^^^^^^^^
|
|
app\main.py:65: in lifespan
|
|
_bootstrap_admin()
|
|
app\main.py:34: in _bootstrap_admin
|
|
has_admin = session.scalar(
|
|
.venv\Lib\site-packages\sqlalchemy\orm\session.py:2421: in scalar
|
|
return self._execute_internal(
|
|
.venv\Lib\site-packages\sqlalchemy\orm\session.py:2271: in _execute_internal
|
|
result: Result[Any] = compile_state_cls.orm_execute_statement(
|
|
.venv\Lib\site-packages\sqlalchemy\orm\context.py:306: in orm_execute_statement
|
|
result = conn.execute(
|
|
.venv\Lib\site-packages\sqlalchemy\engine\base.py:1421: in execute
|
|
return meth(
|
|
.venv\Lib\site-packages\sqlalchemy\sql\elements.py:526: in _execute_on_connection
|
|
return connection._execute_clauseelement(
|
|
.venv\Lib\site-packages\sqlalchemy\engine\base.py:1643: in _execute_clauseelement
|
|
ret = self._execute_context(
|
|
.venv\Lib\site-packages\sqlalchemy\engine\base.py:1848: in _execute_context
|
|
return self._exec_single_context(
|
|
.venv\Lib\site-packages\sqlalchemy\engine\base.py:1988: in _exec_single_context
|
|
self._handle_dbapi_exception(
|
|
.venv\Lib\site-packages\sqlalchemy\engine\base.py:2365: in _handle_dbapi_exception
|
|
raise sqlalchemy_exception.with_traceback(exc_info[2]) from e
|
|
.venv\Lib\site-packages\sqlalchemy\engine\base.py:1969: in _exec_single_context
|
|
self.dialect.do_execute(
|
|
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
|
|
|
|
self = <sqlalchemy.dialects.sqlite.pysqlite.SQLiteDialect_pysqlite object at 0x0000019AEB7B6FD0>
|
|
cursor = <sqlite3.Cursor object at 0x0000019AEB7F7840>
|
|
statement = 'SELECT users.id \nFROM users \nWHERE users.role = ?'
|
|
parameters = ('SUPER_ADMIN',)
|
|
context = <sqlalchemy.dialects.sqlite.base.SQLiteExecutionContext object at 0x0000019AEB896F90>
|
|
|
|
def do_execute(self, cursor, statement, parameters, context=None):
|
|
> cursor.execute(statement, parameters)
|
|
E sqlalchemy.exc.OperationalError: (sqlite3.OperationalError) no such table: users
|
|
E [SQL: SELECT users.id
|
|
E FROM users
|
|
E WHERE users.role = ?]
|
|
E [parameters: ('SUPER_ADMIN',)]
|
|
E (Background on this error at: https://sqlalche.me/e/20/e3q8)
|
|
|
|
.venv\Lib\site-packages\sqlalchemy\engine\default.py:952: OperationalError
|
|
_________ ERROR at setup of test_create_client_returns_password_once __________
|
|
|
|
self = <sqlalchemy.engine.base.Connection object at 0x0000019AEBDD64E0>
|
|
dialect = <sqlalchemy.dialects.sqlite.pysqlite.SQLiteDialect_pysqlite object at 0x0000019AEBDA20D0>
|
|
context = <sqlalchemy.dialects.sqlite.base.SQLiteExecutionContext object at 0x0000019AEB897290>
|
|
statement = <sqlalchemy.dialects.sqlite.base.SQLiteCompiler object at 0x0000019AEBDDDF50>
|
|
parameters = [('SUPER_ADMIN',)]
|
|
|
|
def _exec_single_context(
|
|
self,
|
|
dialect: Dialect,
|
|
context: ExecutionContext,
|
|
statement: Union[str, Compiled],
|
|
parameters: Optional[_AnyMultiExecuteParams],
|
|
) -> CursorResult[Any]:
|
|
"""continue the _execute_context() method for a single DBAPI
|
|
cursor.execute() or cursor.executemany() call.
|
|
|
|
"""
|
|
if dialect.bind_typing is BindTyping.SETINPUTSIZES:
|
|
generic_setinputsizes = context._prepare_set_input_sizes()
|
|
|
|
if generic_setinputsizes:
|
|
try:
|
|
dialect.do_set_input_sizes(
|
|
context.cursor, generic_setinputsizes, context
|
|
)
|
|
except BaseException as e:
|
|
self._handle_dbapi_exception(
|
|
e, str(statement), parameters, None, context
|
|
)
|
|
|
|
cursor, str_statement, parameters = (
|
|
context.cursor,
|
|
context.statement,
|
|
context.parameters,
|
|
)
|
|
|
|
effective_parameters: Optional[_AnyExecuteParams]
|
|
|
|
if not context.executemany:
|
|
effective_parameters = parameters[0]
|
|
else:
|
|
effective_parameters = parameters
|
|
|
|
if self._has_events or self.engine._has_events:
|
|
for fn in self.dispatch.before_cursor_execute:
|
|
str_statement, effective_parameters = fn(
|
|
self,
|
|
cursor,
|
|
str_statement,
|
|
effective_parameters,
|
|
context,
|
|
context.executemany,
|
|
)
|
|
|
|
if self._echo:
|
|
self._log_info(str_statement)
|
|
|
|
stats = context._get_cache_stats()
|
|
|
|
if not self.engine.hide_parameters:
|
|
self._log_info(
|
|
"[%s] %r",
|
|
stats,
|
|
sql_util._repr_params(
|
|
effective_parameters,
|
|
batches=10,
|
|
ismulti=context.executemany,
|
|
),
|
|
)
|
|
else:
|
|
self._log_info(
|
|
"[%s] [SQL parameters hidden due to hide_parameters=True]",
|
|
stats,
|
|
)
|
|
|
|
evt_handled: bool = False
|
|
try:
|
|
if context.execute_style is ExecuteStyle.EXECUTEMANY:
|
|
effective_parameters = cast(
|
|
"_CoreMultiExecuteParams", effective_parameters
|
|
)
|
|
if self.dialect._has_events:
|
|
for fn in self.dialect.dispatch.do_executemany:
|
|
if fn(
|
|
cursor,
|
|
str_statement,
|
|
effective_parameters,
|
|
context,
|
|
):
|
|
evt_handled = True
|
|
break
|
|
if not evt_handled:
|
|
self.dialect.do_executemany(
|
|
cursor,
|
|
str_statement,
|
|
effective_parameters,
|
|
context,
|
|
)
|
|
elif not effective_parameters and context.no_parameters:
|
|
if self.dialect._has_events:
|
|
for fn in self.dialect.dispatch.do_execute_no_params:
|
|
if fn(cursor, str_statement, context):
|
|
evt_handled = True
|
|
break
|
|
if not evt_handled:
|
|
self.dialect.do_execute_no_params(
|
|
cursor, str_statement, context
|
|
)
|
|
else:
|
|
effective_parameters = cast(
|
|
"_CoreSingleExecuteParams", effective_parameters
|
|
)
|
|
if self.dialect._has_events:
|
|
for fn in self.dialect.dispatch.do_execute:
|
|
if fn(
|
|
cursor,
|
|
str_statement,
|
|
effective_parameters,
|
|
context,
|
|
):
|
|
evt_handled = True
|
|
break
|
|
if not evt_handled:
|
|
> self.dialect.do_execute(
|
|
cursor, str_statement, effective_parameters, context
|
|
)
|
|
|
|
.venv\Lib\site-packages\sqlalchemy\engine\base.py:1969:
|
|
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
|
|
|
|
self = <sqlalchemy.dialects.sqlite.pysqlite.SQLiteDialect_pysqlite object at 0x0000019AEBDA20D0>
|
|
cursor = <sqlite3.Cursor object at 0x0000019AEC086F40>
|
|
statement = 'SELECT users.id \nFROM users \nWHERE users.role = ?'
|
|
parameters = ('SUPER_ADMIN',)
|
|
context = <sqlalchemy.dialects.sqlite.base.SQLiteExecutionContext object at 0x0000019AEB897290>
|
|
|
|
def do_execute(self, cursor, statement, parameters, context=None):
|
|
> cursor.execute(statement, parameters)
|
|
E sqlite3.OperationalError: no such table: users
|
|
|
|
.venv\Lib\site-packages\sqlalchemy\engine\default.py:952: OperationalError
|
|
|
|
The above exception was the direct cause of the following exception:
|
|
|
|
db_session = <sqlalchemy.orm.session.Session object at 0x0000019AEB7D5D30>
|
|
monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x0000019AEBDDDE50>
|
|
|
|
@pytest.fixture()
|
|
def client(db_session, monkeypatch):
|
|
"""TestClient wired to the fixture DB; dynsec calls are faked out."""
|
|
# Point the app's session factory at the test engine.
|
|
monkeypatch.setattr(db_module, "_SessionFactory", None)
|
|
monkeypatch.setattr(
|
|
db_module, "_engine", db_session.get_bind()
|
|
)
|
|
|
|
# Fake dynsec so no broker is needed for API tests.
|
|
class FakeDynsec:
|
|
def __init__(self):
|
|
self.clients: dict[str, dict] = {}
|
|
self.roles: dict[str, dict] = {}
|
|
self.groups: dict[str, dict] = {}
|
|
|
|
def _command(self, command, **kwargs):
|
|
return {"success": True}
|
|
|
|
def create_client(self, username, password, **kwargs):
|
|
self.clients[username] = {"username": username, "enabled": True, **kwargs}
|
|
|
|
def list_clients(self):
|
|
return list(self.clients.values())
|
|
|
|
def delete_client(self, username):
|
|
self.clients.pop(username, None)
|
|
|
|
def disable_client(self, username):
|
|
self.clients[username]["enabled"] = False
|
|
|
|
def enable_client(self, username):
|
|
self.clients[username]["enabled"] = True
|
|
|
|
def set_client_password(self, username, password):
|
|
pass
|
|
|
|
def get_client(self, username):
|
|
return self.clients[username]
|
|
|
|
def create_group(self, groupname, roles=None):
|
|
self.groups[groupname] = {"groupname": groupname, "roles": roles or []}
|
|
|
|
def list_groups(self):
|
|
return list(self.groups.values())
|
|
|
|
def delete_group(self, groupname):
|
|
self.groups.pop(groupname, None)
|
|
|
|
def create_role(self, rolename, acls=None):
|
|
self.roles[rolename] = {"rolename": rolename, "acls": acls or []}
|
|
|
|
def list_roles(self):
|
|
return list(self.roles.values())
|
|
|
|
def delete_role(self, rolename):
|
|
self.roles.pop(rolename, None)
|
|
|
|
def set_role_acls(self, rolename, acls):
|
|
self.roles[rolename]["acls"] = acls
|
|
|
|
def get_role(self, rolename):
|
|
return self.roles[rolename]
|
|
|
|
def connect(self):
|
|
pass
|
|
|
|
def close(self):
|
|
pass
|
|
|
|
fake = FakeDynsec()
|
|
monkeypatch.setattr(main_module, "dynsec_client", fake)
|
|
|
|
from app.routers import mqtt as mqtt_router
|
|
|
|
monkeypatch.setattr(mqtt_router, "dynsec_client", fake)
|
|
|
|
> with TestClient(main_module.app) as test_client:
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
|
|
tests\conftest.py:132:
|
|
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
|
|
.venv\Lib\site-packages\starlette\testclient.py:698: in __enter__
|
|
portal.call(self.wait_startup)
|
|
.venv\Lib\site-packages\anyio\from_thread.py:340: in call
|
|
return cast(T_Retval, self.start_task_soon(func, *args).result())
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
..\..\..\..\AppData\Local\Python\pythoncore-3.14-64\Lib\concurrent\futures\_base.py:450: in result
|
|
return self.__get_result()
|
|
^^^^^^^^^^^^^^^^^^^
|
|
..\..\..\..\AppData\Local\Python\pythoncore-3.14-64\Lib\concurrent\futures\_base.py:395: in __get_result
|
|
raise self._exception
|
|
.venv\Lib\site-packages\anyio\from_thread.py:265: in _call_func
|
|
retval = await retval_or_awaitable
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
.venv\Lib\site-packages\starlette\testclient.py:733: in wait_startup
|
|
await receive()
|
|
.venv\Lib\site-packages\starlette\testclient.py:724: in receive
|
|
self.task.result()
|
|
..\..\..\..\AppData\Local\Python\pythoncore-3.14-64\Lib\concurrent\futures\_base.py:443: in result
|
|
return self.__get_result()
|
|
^^^^^^^^^^^^^^^^^^^
|
|
..\..\..\..\AppData\Local\Python\pythoncore-3.14-64\Lib\concurrent\futures\_base.py:395: in __get_result
|
|
raise self._exception
|
|
.venv\Lib\site-packages\anyio\from_thread.py:265: in _call_func
|
|
retval = await retval_or_awaitable
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
.venv\Lib\site-packages\starlette\testclient.py:714: in lifespan
|
|
await self.app(scope, self.stream_receive.receive, self.stream_send.send)
|
|
.venv\Lib\site-packages\fastapi\applications.py:1163: in __call__
|
|
await super().__call__(scope, receive, send)
|
|
.venv\Lib\site-packages\starlette\applications.py:96: in __call__
|
|
await self.middleware_stack(scope, receive, send)
|
|
.venv\Lib\site-packages\starlette\middleware\errors.py:151: in __call__
|
|
await self.app(scope, receive, send)
|
|
.venv\Lib\site-packages\starlette\middleware\base.py:103: in __call__
|
|
await self.app(scope, receive, send)
|
|
.venv\Lib\site-packages\starlette\middleware\cors.py:80: in __call__
|
|
await self.app(scope, receive, send)
|
|
.venv\Lib\site-packages\starlette\middleware\exceptions.py:49: in __call__
|
|
await self.app(scope, receive, send)
|
|
.venv\Lib\site-packages\fastapi\middleware\asyncexitstack.py:18: in __call__
|
|
await self.app(scope, receive, send)
|
|
.venv\Lib\site-packages\starlette\routing.py:670: in __call__
|
|
await self.middleware_stack(scope, receive, send)
|
|
.venv\Lib\site-packages\fastapi\routing.py:2726: in app
|
|
await self.lifespan(scope, receive, send)
|
|
.venv\Lib\site-packages\starlette\routing.py:648: in lifespan
|
|
async with self.lifespan_context(app) as maybe_state:
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
..\..\..\..\AppData\Local\Python\pythoncore-3.14-64\Lib\contextlib.py:214: in __aenter__
|
|
return await anext(self.gen)
|
|
^^^^^^^^^^^^^^^^^^^^^
|
|
.venv\Lib\site-packages\fastapi\routing.py:240: in merged_lifespan
|
|
async with original_context(app) as maybe_original_state:
|
|
^^^^^^^^^^^^^^^^^^^^^
|
|
..\..\..\..\AppData\Local\Python\pythoncore-3.14-64\Lib\contextlib.py:214: in __aenter__
|
|
return await anext(self.gen)
|
|
^^^^^^^^^^^^^^^^^^^^^
|
|
.venv\Lib\site-packages\fastapi\routing.py:240: in merged_lifespan
|
|
async with original_context(app) as maybe_original_state:
|
|
^^^^^^^^^^^^^^^^^^^^^
|
|
..\..\..\..\AppData\Local\Python\pythoncore-3.14-64\Lib\contextlib.py:214: in __aenter__
|
|
return await anext(self.gen)
|
|
^^^^^^^^^^^^^^^^^^^^^
|
|
.venv\Lib\site-packages\fastapi\routing.py:240: in merged_lifespan
|
|
async with original_context(app) as maybe_original_state:
|
|
^^^^^^^^^^^^^^^^^^^^^
|
|
..\..\..\..\AppData\Local\Python\pythoncore-3.14-64\Lib\contextlib.py:214: in __aenter__
|
|
return await anext(self.gen)
|
|
^^^^^^^^^^^^^^^^^^^^^
|
|
.venv\Lib\site-packages\fastapi\routing.py:240: in merged_lifespan
|
|
async with original_context(app) as maybe_original_state:
|
|
^^^^^^^^^^^^^^^^^^^^^
|
|
..\..\..\..\AppData\Local\Python\pythoncore-3.14-64\Lib\contextlib.py:214: in __aenter__
|
|
return await anext(self.gen)
|
|
^^^^^^^^^^^^^^^^^^^^^
|
|
app\main.py:65: in lifespan
|
|
_bootstrap_admin()
|
|
app\main.py:34: in _bootstrap_admin
|
|
has_admin = session.scalar(
|
|
.venv\Lib\site-packages\sqlalchemy\orm\session.py:2421: in scalar
|
|
return self._execute_internal(
|
|
.venv\Lib\site-packages\sqlalchemy\orm\session.py:2271: in _execute_internal
|
|
result: Result[Any] = compile_state_cls.orm_execute_statement(
|
|
.venv\Lib\site-packages\sqlalchemy\orm\context.py:306: in orm_execute_statement
|
|
result = conn.execute(
|
|
.venv\Lib\site-packages\sqlalchemy\engine\base.py:1421: in execute
|
|
return meth(
|
|
.venv\Lib\site-packages\sqlalchemy\sql\elements.py:526: in _execute_on_connection
|
|
return connection._execute_clauseelement(
|
|
.venv\Lib\site-packages\sqlalchemy\engine\base.py:1643: in _execute_clauseelement
|
|
ret = self._execute_context(
|
|
.venv\Lib\site-packages\sqlalchemy\engine\base.py:1848: in _execute_context
|
|
return self._exec_single_context(
|
|
.venv\Lib\site-packages\sqlalchemy\engine\base.py:1988: in _exec_single_context
|
|
self._handle_dbapi_exception(
|
|
.venv\Lib\site-packages\sqlalchemy\engine\base.py:2365: in _handle_dbapi_exception
|
|
raise sqlalchemy_exception.with_traceback(exc_info[2]) from e
|
|
.venv\Lib\site-packages\sqlalchemy\engine\base.py:1969: in _exec_single_context
|
|
self.dialect.do_execute(
|
|
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
|
|
|
|
self = <sqlalchemy.dialects.sqlite.pysqlite.SQLiteDialect_pysqlite object at 0x0000019AEBDA20D0>
|
|
cursor = <sqlite3.Cursor object at 0x0000019AEC086F40>
|
|
statement = 'SELECT users.id \nFROM users \nWHERE users.role = ?'
|
|
parameters = ('SUPER_ADMIN',)
|
|
context = <sqlalchemy.dialects.sqlite.base.SQLiteExecutionContext object at 0x0000019AEB897290>
|
|
|
|
def do_execute(self, cursor, statement, parameters, context=None):
|
|
> cursor.execute(statement, parameters)
|
|
E sqlalchemy.exc.OperationalError: (sqlite3.OperationalError) no such table: users
|
|
E [SQL: SELECT users.id
|
|
E FROM users
|
|
E WHERE users.role = ?]
|
|
E [parameters: ('SUPER_ADMIN',)]
|
|
E (Background on this error at: https://sqlalche.me/e/20/e3q8)
|
|
|
|
.venv\Lib\site-packages\sqlalchemy\engine\default.py:952: OperationalError
|
|
_____________ ERROR at setup of test_disable_enable_delete_client _____________
|
|
|
|
self = <sqlalchemy.engine.base.Connection object at 0x0000019AEBFE65D0>
|
|
dialect = <sqlalchemy.dialects.sqlite.pysqlite.SQLiteDialect_pysqlite object at 0x0000019AEBDA2E90>
|
|
context = <sqlalchemy.dialects.sqlite.base.SQLiteExecutionContext object at 0x0000019AEB897DD0>
|
|
statement = <sqlalchemy.dialects.sqlite.base.SQLiteCompiler object at 0x0000019AEBCE4850>
|
|
parameters = [('SUPER_ADMIN',)]
|
|
|
|
def _exec_single_context(
|
|
self,
|
|
dialect: Dialect,
|
|
context: ExecutionContext,
|
|
statement: Union[str, Compiled],
|
|
parameters: Optional[_AnyMultiExecuteParams],
|
|
) -> CursorResult[Any]:
|
|
"""continue the _execute_context() method for a single DBAPI
|
|
cursor.execute() or cursor.executemany() call.
|
|
|
|
"""
|
|
if dialect.bind_typing is BindTyping.SETINPUTSIZES:
|
|
generic_setinputsizes = context._prepare_set_input_sizes()
|
|
|
|
if generic_setinputsizes:
|
|
try:
|
|
dialect.do_set_input_sizes(
|
|
context.cursor, generic_setinputsizes, context
|
|
)
|
|
except BaseException as e:
|
|
self._handle_dbapi_exception(
|
|
e, str(statement), parameters, None, context
|
|
)
|
|
|
|
cursor, str_statement, parameters = (
|
|
context.cursor,
|
|
context.statement,
|
|
context.parameters,
|
|
)
|
|
|
|
effective_parameters: Optional[_AnyExecuteParams]
|
|
|
|
if not context.executemany:
|
|
effective_parameters = parameters[0]
|
|
else:
|
|
effective_parameters = parameters
|
|
|
|
if self._has_events or self.engine._has_events:
|
|
for fn in self.dispatch.before_cursor_execute:
|
|
str_statement, effective_parameters = fn(
|
|
self,
|
|
cursor,
|
|
str_statement,
|
|
effective_parameters,
|
|
context,
|
|
context.executemany,
|
|
)
|
|
|
|
if self._echo:
|
|
self._log_info(str_statement)
|
|
|
|
stats = context._get_cache_stats()
|
|
|
|
if not self.engine.hide_parameters:
|
|
self._log_info(
|
|
"[%s] %r",
|
|
stats,
|
|
sql_util._repr_params(
|
|
effective_parameters,
|
|
batches=10,
|
|
ismulti=context.executemany,
|
|
),
|
|
)
|
|
else:
|
|
self._log_info(
|
|
"[%s] [SQL parameters hidden due to hide_parameters=True]",
|
|
stats,
|
|
)
|
|
|
|
evt_handled: bool = False
|
|
try:
|
|
if context.execute_style is ExecuteStyle.EXECUTEMANY:
|
|
effective_parameters = cast(
|
|
"_CoreMultiExecuteParams", effective_parameters
|
|
)
|
|
if self.dialect._has_events:
|
|
for fn in self.dialect.dispatch.do_executemany:
|
|
if fn(
|
|
cursor,
|
|
str_statement,
|
|
effective_parameters,
|
|
context,
|
|
):
|
|
evt_handled = True
|
|
break
|
|
if not evt_handled:
|
|
self.dialect.do_executemany(
|
|
cursor,
|
|
str_statement,
|
|
effective_parameters,
|
|
context,
|
|
)
|
|
elif not effective_parameters and context.no_parameters:
|
|
if self.dialect._has_events:
|
|
for fn in self.dialect.dispatch.do_execute_no_params:
|
|
if fn(cursor, str_statement, context):
|
|
evt_handled = True
|
|
break
|
|
if not evt_handled:
|
|
self.dialect.do_execute_no_params(
|
|
cursor, str_statement, context
|
|
)
|
|
else:
|
|
effective_parameters = cast(
|
|
"_CoreSingleExecuteParams", effective_parameters
|
|
)
|
|
if self.dialect._has_events:
|
|
for fn in self.dialect.dispatch.do_execute:
|
|
if fn(
|
|
cursor,
|
|
str_statement,
|
|
effective_parameters,
|
|
context,
|
|
):
|
|
evt_handled = True
|
|
break
|
|
if not evt_handled:
|
|
> self.dialect.do_execute(
|
|
cursor, str_statement, effective_parameters, context
|
|
)
|
|
|
|
.venv\Lib\site-packages\sqlalchemy\engine\base.py:1969:
|
|
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
|
|
|
|
self = <sqlalchemy.dialects.sqlite.pysqlite.SQLiteDialect_pysqlite object at 0x0000019AEBDA2E90>
|
|
cursor = <sqlite3.Cursor object at 0x0000019AEBDB50C0>
|
|
statement = 'SELECT users.id \nFROM users \nWHERE users.role = ?'
|
|
parameters = ('SUPER_ADMIN',)
|
|
context = <sqlalchemy.dialects.sqlite.base.SQLiteExecutionContext object at 0x0000019AEB897DD0>
|
|
|
|
def do_execute(self, cursor, statement, parameters, context=None):
|
|
> cursor.execute(statement, parameters)
|
|
E sqlite3.OperationalError: no such table: users
|
|
|
|
.venv\Lib\site-packages\sqlalchemy\engine\default.py:952: OperationalError
|
|
|
|
The above exception was the direct cause of the following exception:
|
|
|
|
db_session = <sqlalchemy.orm.session.Session object at 0x0000019AEB7D5BE0>
|
|
monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x0000019AEBDDFE50>
|
|
|
|
@pytest.fixture()
|
|
def client(db_session, monkeypatch):
|
|
"""TestClient wired to the fixture DB; dynsec calls are faked out."""
|
|
# Point the app's session factory at the test engine.
|
|
monkeypatch.setattr(db_module, "_SessionFactory", None)
|
|
monkeypatch.setattr(
|
|
db_module, "_engine", db_session.get_bind()
|
|
)
|
|
|
|
# Fake dynsec so no broker is needed for API tests.
|
|
class FakeDynsec:
|
|
def __init__(self):
|
|
self.clients: dict[str, dict] = {}
|
|
self.roles: dict[str, dict] = {}
|
|
self.groups: dict[str, dict] = {}
|
|
|
|
def _command(self, command, **kwargs):
|
|
return {"success": True}
|
|
|
|
def create_client(self, username, password, **kwargs):
|
|
self.clients[username] = {"username": username, "enabled": True, **kwargs}
|
|
|
|
def list_clients(self):
|
|
return list(self.clients.values())
|
|
|
|
def delete_client(self, username):
|
|
self.clients.pop(username, None)
|
|
|
|
def disable_client(self, username):
|
|
self.clients[username]["enabled"] = False
|
|
|
|
def enable_client(self, username):
|
|
self.clients[username]["enabled"] = True
|
|
|
|
def set_client_password(self, username, password):
|
|
pass
|
|
|
|
def get_client(self, username):
|
|
return self.clients[username]
|
|
|
|
def create_group(self, groupname, roles=None):
|
|
self.groups[groupname] = {"groupname": groupname, "roles": roles or []}
|
|
|
|
def list_groups(self):
|
|
return list(self.groups.values())
|
|
|
|
def delete_group(self, groupname):
|
|
self.groups.pop(groupname, None)
|
|
|
|
def create_role(self, rolename, acls=None):
|
|
self.roles[rolename] = {"rolename": rolename, "acls": acls or []}
|
|
|
|
def list_roles(self):
|
|
return list(self.roles.values())
|
|
|
|
def delete_role(self, rolename):
|
|
self.roles.pop(rolename, None)
|
|
|
|
def set_role_acls(self, rolename, acls):
|
|
self.roles[rolename]["acls"] = acls
|
|
|
|
def get_role(self, rolename):
|
|
return self.roles[rolename]
|
|
|
|
def connect(self):
|
|
pass
|
|
|
|
def close(self):
|
|
pass
|
|
|
|
fake = FakeDynsec()
|
|
monkeypatch.setattr(main_module, "dynsec_client", fake)
|
|
|
|
from app.routers import mqtt as mqtt_router
|
|
|
|
monkeypatch.setattr(mqtt_router, "dynsec_client", fake)
|
|
|
|
> with TestClient(main_module.app) as test_client:
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
|
|
tests\conftest.py:132:
|
|
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
|
|
.venv\Lib\site-packages\starlette\testclient.py:698: in __enter__
|
|
portal.call(self.wait_startup)
|
|
.venv\Lib\site-packages\anyio\from_thread.py:340: in call
|
|
return cast(T_Retval, self.start_task_soon(func, *args).result())
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
..\..\..\..\AppData\Local\Python\pythoncore-3.14-64\Lib\concurrent\futures\_base.py:450: in result
|
|
return self.__get_result()
|
|
^^^^^^^^^^^^^^^^^^^
|
|
..\..\..\..\AppData\Local\Python\pythoncore-3.14-64\Lib\concurrent\futures\_base.py:395: in __get_result
|
|
raise self._exception
|
|
.venv\Lib\site-packages\anyio\from_thread.py:265: in _call_func
|
|
retval = await retval_or_awaitable
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
.venv\Lib\site-packages\starlette\testclient.py:733: in wait_startup
|
|
await receive()
|
|
.venv\Lib\site-packages\starlette\testclient.py:724: in receive
|
|
self.task.result()
|
|
..\..\..\..\AppData\Local\Python\pythoncore-3.14-64\Lib\concurrent\futures\_base.py:443: in result
|
|
return self.__get_result()
|
|
^^^^^^^^^^^^^^^^^^^
|
|
..\..\..\..\AppData\Local\Python\pythoncore-3.14-64\Lib\concurrent\futures\_base.py:395: in __get_result
|
|
raise self._exception
|
|
.venv\Lib\site-packages\anyio\from_thread.py:265: in _call_func
|
|
retval = await retval_or_awaitable
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
.venv\Lib\site-packages\starlette\testclient.py:714: in lifespan
|
|
await self.app(scope, self.stream_receive.receive, self.stream_send.send)
|
|
.venv\Lib\site-packages\fastapi\applications.py:1163: in __call__
|
|
await super().__call__(scope, receive, send)
|
|
.venv\Lib\site-packages\starlette\applications.py:96: in __call__
|
|
await self.middleware_stack(scope, receive, send)
|
|
.venv\Lib\site-packages\starlette\middleware\errors.py:151: in __call__
|
|
await self.app(scope, receive, send)
|
|
.venv\Lib\site-packages\starlette\middleware\base.py:103: in __call__
|
|
await self.app(scope, receive, send)
|
|
.venv\Lib\site-packages\starlette\middleware\cors.py:80: in __call__
|
|
await self.app(scope, receive, send)
|
|
.venv\Lib\site-packages\starlette\middleware\exceptions.py:49: in __call__
|
|
await self.app(scope, receive, send)
|
|
.venv\Lib\site-packages\fastapi\middleware\asyncexitstack.py:18: in __call__
|
|
await self.app(scope, receive, send)
|
|
.venv\Lib\site-packages\starlette\routing.py:670: in __call__
|
|
await self.middleware_stack(scope, receive, send)
|
|
.venv\Lib\site-packages\fastapi\routing.py:2726: in app
|
|
await self.lifespan(scope, receive, send)
|
|
.venv\Lib\site-packages\starlette\routing.py:648: in lifespan
|
|
async with self.lifespan_context(app) as maybe_state:
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
..\..\..\..\AppData\Local\Python\pythoncore-3.14-64\Lib\contextlib.py:214: in __aenter__
|
|
return await anext(self.gen)
|
|
^^^^^^^^^^^^^^^^^^^^^
|
|
.venv\Lib\site-packages\fastapi\routing.py:240: in merged_lifespan
|
|
async with original_context(app) as maybe_original_state:
|
|
^^^^^^^^^^^^^^^^^^^^^
|
|
..\..\..\..\AppData\Local\Python\pythoncore-3.14-64\Lib\contextlib.py:214: in __aenter__
|
|
return await anext(self.gen)
|
|
^^^^^^^^^^^^^^^^^^^^^
|
|
.venv\Lib\site-packages\fastapi\routing.py:240: in merged_lifespan
|
|
async with original_context(app) as maybe_original_state:
|
|
^^^^^^^^^^^^^^^^^^^^^
|
|
..\..\..\..\AppData\Local\Python\pythoncore-3.14-64\Lib\contextlib.py:214: in __aenter__
|
|
return await anext(self.gen)
|
|
^^^^^^^^^^^^^^^^^^^^^
|
|
.venv\Lib\site-packages\fastapi\routing.py:240: in merged_lifespan
|
|
async with original_context(app) as maybe_original_state:
|
|
^^^^^^^^^^^^^^^^^^^^^
|
|
..\..\..\..\AppData\Local\Python\pythoncore-3.14-64\Lib\contextlib.py:214: in __aenter__
|
|
return await anext(self.gen)
|
|
^^^^^^^^^^^^^^^^^^^^^
|
|
.venv\Lib\site-packages\fastapi\routing.py:240: in merged_lifespan
|
|
async with original_context(app) as maybe_original_state:
|
|
^^^^^^^^^^^^^^^^^^^^^
|
|
..\..\..\..\AppData\Local\Python\pythoncore-3.14-64\Lib\contextlib.py:214: in __aenter__
|
|
return await anext(self.gen)
|
|
^^^^^^^^^^^^^^^^^^^^^
|
|
app\main.py:65: in lifespan
|
|
_bootstrap_admin()
|
|
app\main.py:34: in _bootstrap_admin
|
|
has_admin = session.scalar(
|
|
.venv\Lib\site-packages\sqlalchemy\orm\session.py:2421: in scalar
|
|
return self._execute_internal(
|
|
.venv\Lib\site-packages\sqlalchemy\orm\session.py:2271: in _execute_internal
|
|
result: Result[Any] = compile_state_cls.orm_execute_statement(
|
|
.venv\Lib\site-packages\sqlalchemy\orm\context.py:306: in orm_execute_statement
|
|
result = conn.execute(
|
|
.venv\Lib\site-packages\sqlalchemy\engine\base.py:1421: in execute
|
|
return meth(
|
|
.venv\Lib\site-packages\sqlalchemy\sql\elements.py:526: in _execute_on_connection
|
|
return connection._execute_clauseelement(
|
|
.venv\Lib\site-packages\sqlalchemy\engine\base.py:1643: in _execute_clauseelement
|
|
ret = self._execute_context(
|
|
.venv\Lib\site-packages\sqlalchemy\engine\base.py:1848: in _execute_context
|
|
return self._exec_single_context(
|
|
.venv\Lib\site-packages\sqlalchemy\engine\base.py:1988: in _exec_single_context
|
|
self._handle_dbapi_exception(
|
|
.venv\Lib\site-packages\sqlalchemy\engine\base.py:2365: in _handle_dbapi_exception
|
|
raise sqlalchemy_exception.with_traceback(exc_info[2]) from e
|
|
.venv\Lib\site-packages\sqlalchemy\engine\base.py:1969: in _exec_single_context
|
|
self.dialect.do_execute(
|
|
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
|
|
|
|
self = <sqlalchemy.dialects.sqlite.pysqlite.SQLiteDialect_pysqlite object at 0x0000019AEBDA2E90>
|
|
cursor = <sqlite3.Cursor object at 0x0000019AEBDB50C0>
|
|
statement = 'SELECT users.id \nFROM users \nWHERE users.role = ?'
|
|
parameters = ('SUPER_ADMIN',)
|
|
context = <sqlalchemy.dialects.sqlite.base.SQLiteExecutionContext object at 0x0000019AEB897DD0>
|
|
|
|
def do_execute(self, cursor, statement, parameters, context=None):
|
|
> cursor.execute(statement, parameters)
|
|
E sqlalchemy.exc.OperationalError: (sqlite3.OperationalError) no such table: users
|
|
E [SQL: SELECT users.id
|
|
E FROM users
|
|
E WHERE users.role = ?]
|
|
E [parameters: ('SUPER_ADMIN',)]
|
|
E (Background on this error at: https://sqlalche.me/e/20/e3q8)
|
|
|
|
.venv\Lib\site-packages\sqlalchemy\engine\default.py:952: OperationalError
|
|
__________________ ERROR at setup of test_role_acl_lifecycle __________________
|
|
|
|
self = <sqlalchemy.engine.base.Connection object at 0x0000019AEBDCA300>
|
|
dialect = <sqlalchemy.dialects.sqlite.pysqlite.SQLiteDialect_pysqlite object at 0x0000019AEBDA3890>
|
|
context = <sqlalchemy.dialects.sqlite.base.SQLiteExecutionContext object at 0x0000019AEB8968D0>
|
|
statement = <sqlalchemy.dialects.sqlite.base.SQLiteCompiler object at 0x0000019AEBCE5F50>
|
|
parameters = [('SUPER_ADMIN',)]
|
|
|
|
def _exec_single_context(
|
|
self,
|
|
dialect: Dialect,
|
|
context: ExecutionContext,
|
|
statement: Union[str, Compiled],
|
|
parameters: Optional[_AnyMultiExecuteParams],
|
|
) -> CursorResult[Any]:
|
|
"""continue the _execute_context() method for a single DBAPI
|
|
cursor.execute() or cursor.executemany() call.
|
|
|
|
"""
|
|
if dialect.bind_typing is BindTyping.SETINPUTSIZES:
|
|
generic_setinputsizes = context._prepare_set_input_sizes()
|
|
|
|
if generic_setinputsizes:
|
|
try:
|
|
dialect.do_set_input_sizes(
|
|
context.cursor, generic_setinputsizes, context
|
|
)
|
|
except BaseException as e:
|
|
self._handle_dbapi_exception(
|
|
e, str(statement), parameters, None, context
|
|
)
|
|
|
|
cursor, str_statement, parameters = (
|
|
context.cursor,
|
|
context.statement,
|
|
context.parameters,
|
|
)
|
|
|
|
effective_parameters: Optional[_AnyExecuteParams]
|
|
|
|
if not context.executemany:
|
|
effective_parameters = parameters[0]
|
|
else:
|
|
effective_parameters = parameters
|
|
|
|
if self._has_events or self.engine._has_events:
|
|
for fn in self.dispatch.before_cursor_execute:
|
|
str_statement, effective_parameters = fn(
|
|
self,
|
|
cursor,
|
|
str_statement,
|
|
effective_parameters,
|
|
context,
|
|
context.executemany,
|
|
)
|
|
|
|
if self._echo:
|
|
self._log_info(str_statement)
|
|
|
|
stats = context._get_cache_stats()
|
|
|
|
if not self.engine.hide_parameters:
|
|
self._log_info(
|
|
"[%s] %r",
|
|
stats,
|
|
sql_util._repr_params(
|
|
effective_parameters,
|
|
batches=10,
|
|
ismulti=context.executemany,
|
|
),
|
|
)
|
|
else:
|
|
self._log_info(
|
|
"[%s] [SQL parameters hidden due to hide_parameters=True]",
|
|
stats,
|
|
)
|
|
|
|
evt_handled: bool = False
|
|
try:
|
|
if context.execute_style is ExecuteStyle.EXECUTEMANY:
|
|
effective_parameters = cast(
|
|
"_CoreMultiExecuteParams", effective_parameters
|
|
)
|
|
if self.dialect._has_events:
|
|
for fn in self.dialect.dispatch.do_executemany:
|
|
if fn(
|
|
cursor,
|
|
str_statement,
|
|
effective_parameters,
|
|
context,
|
|
):
|
|
evt_handled = True
|
|
break
|
|
if not evt_handled:
|
|
self.dialect.do_executemany(
|
|
cursor,
|
|
str_statement,
|
|
effective_parameters,
|
|
context,
|
|
)
|
|
elif not effective_parameters and context.no_parameters:
|
|
if self.dialect._has_events:
|
|
for fn in self.dialect.dispatch.do_execute_no_params:
|
|
if fn(cursor, str_statement, context):
|
|
evt_handled = True
|
|
break
|
|
if not evt_handled:
|
|
self.dialect.do_execute_no_params(
|
|
cursor, str_statement, context
|
|
)
|
|
else:
|
|
effective_parameters = cast(
|
|
"_CoreSingleExecuteParams", effective_parameters
|
|
)
|
|
if self.dialect._has_events:
|
|
for fn in self.dialect.dispatch.do_execute:
|
|
if fn(
|
|
cursor,
|
|
str_statement,
|
|
effective_parameters,
|
|
context,
|
|
):
|
|
evt_handled = True
|
|
break
|
|
if not evt_handled:
|
|
> self.dialect.do_execute(
|
|
cursor, str_statement, effective_parameters, context
|
|
)
|
|
|
|
.venv\Lib\site-packages\sqlalchemy\engine\base.py:1969:
|
|
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
|
|
|
|
self = <sqlalchemy.dialects.sqlite.pysqlite.SQLiteDialect_pysqlite object at 0x0000019AEBDA3890>
|
|
cursor = <sqlite3.Cursor object at 0x0000019AEBAFA8C0>
|
|
statement = 'SELECT users.id \nFROM users \nWHERE users.role = ?'
|
|
parameters = ('SUPER_ADMIN',)
|
|
context = <sqlalchemy.dialects.sqlite.base.SQLiteExecutionContext object at 0x0000019AEB8968D0>
|
|
|
|
def do_execute(self, cursor, statement, parameters, context=None):
|
|
> cursor.execute(statement, parameters)
|
|
E sqlite3.OperationalError: no such table: users
|
|
|
|
.venv\Lib\site-packages\sqlalchemy\engine\default.py:952: OperationalError
|
|
|
|
The above exception was the direct cause of the following exception:
|
|
|
|
db_session = <sqlalchemy.orm.session.Session object at 0x0000019AEB6EB380>
|
|
monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x0000019AEBDC8F50>
|
|
|
|
@pytest.fixture()
|
|
def client(db_session, monkeypatch):
|
|
"""TestClient wired to the fixture DB; dynsec calls are faked out."""
|
|
# Point the app's session factory at the test engine.
|
|
monkeypatch.setattr(db_module, "_SessionFactory", None)
|
|
monkeypatch.setattr(
|
|
db_module, "_engine", db_session.get_bind()
|
|
)
|
|
|
|
# Fake dynsec so no broker is needed for API tests.
|
|
class FakeDynsec:
|
|
def __init__(self):
|
|
self.clients: dict[str, dict] = {}
|
|
self.roles: dict[str, dict] = {}
|
|
self.groups: dict[str, dict] = {}
|
|
|
|
def _command(self, command, **kwargs):
|
|
return {"success": True}
|
|
|
|
def create_client(self, username, password, **kwargs):
|
|
self.clients[username] = {"username": username, "enabled": True, **kwargs}
|
|
|
|
def list_clients(self):
|
|
return list(self.clients.values())
|
|
|
|
def delete_client(self, username):
|
|
self.clients.pop(username, None)
|
|
|
|
def disable_client(self, username):
|
|
self.clients[username]["enabled"] = False
|
|
|
|
def enable_client(self, username):
|
|
self.clients[username]["enabled"] = True
|
|
|
|
def set_client_password(self, username, password):
|
|
pass
|
|
|
|
def get_client(self, username):
|
|
return self.clients[username]
|
|
|
|
def create_group(self, groupname, roles=None):
|
|
self.groups[groupname] = {"groupname": groupname, "roles": roles or []}
|
|
|
|
def list_groups(self):
|
|
return list(self.groups.values())
|
|
|
|
def delete_group(self, groupname):
|
|
self.groups.pop(groupname, None)
|
|
|
|
def create_role(self, rolename, acls=None):
|
|
self.roles[rolename] = {"rolename": rolename, "acls": acls or []}
|
|
|
|
def list_roles(self):
|
|
return list(self.roles.values())
|
|
|
|
def delete_role(self, rolename):
|
|
self.roles.pop(rolename, None)
|
|
|
|
def set_role_acls(self, rolename, acls):
|
|
self.roles[rolename]["acls"] = acls
|
|
|
|
def get_role(self, rolename):
|
|
return self.roles[rolename]
|
|
|
|
def connect(self):
|
|
pass
|
|
|
|
def close(self):
|
|
pass
|
|
|
|
fake = FakeDynsec()
|
|
monkeypatch.setattr(main_module, "dynsec_client", fake)
|
|
|
|
from app.routers import mqtt as mqtt_router
|
|
|
|
monkeypatch.setattr(mqtt_router, "dynsec_client", fake)
|
|
|
|
> with TestClient(main_module.app) as test_client:
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
|
|
tests\conftest.py:132:
|
|
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
|
|
.venv\Lib\site-packages\starlette\testclient.py:698: in __enter__
|
|
portal.call(self.wait_startup)
|
|
.venv\Lib\site-packages\anyio\from_thread.py:340: in call
|
|
return cast(T_Retval, self.start_task_soon(func, *args).result())
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
..\..\..\..\AppData\Local\Python\pythoncore-3.14-64\Lib\concurrent\futures\_base.py:450: in result
|
|
return self.__get_result()
|
|
^^^^^^^^^^^^^^^^^^^
|
|
..\..\..\..\AppData\Local\Python\pythoncore-3.14-64\Lib\concurrent\futures\_base.py:395: in __get_result
|
|
raise self._exception
|
|
.venv\Lib\site-packages\anyio\from_thread.py:265: in _call_func
|
|
retval = await retval_or_awaitable
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
.venv\Lib\site-packages\starlette\testclient.py:733: in wait_startup
|
|
await receive()
|
|
.venv\Lib\site-packages\starlette\testclient.py:724: in receive
|
|
self.task.result()
|
|
..\..\..\..\AppData\Local\Python\pythoncore-3.14-64\Lib\concurrent\futures\_base.py:443: in result
|
|
return self.__get_result()
|
|
^^^^^^^^^^^^^^^^^^^
|
|
..\..\..\..\AppData\Local\Python\pythoncore-3.14-64\Lib\concurrent\futures\_base.py:395: in __get_result
|
|
raise self._exception
|
|
.venv\Lib\site-packages\anyio\from_thread.py:265: in _call_func
|
|
retval = await retval_or_awaitable
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
.venv\Lib\site-packages\starlette\testclient.py:714: in lifespan
|
|
await self.app(scope, self.stream_receive.receive, self.stream_send.send)
|
|
.venv\Lib\site-packages\fastapi\applications.py:1163: in __call__
|
|
await super().__call__(scope, receive, send)
|
|
.venv\Lib\site-packages\starlette\applications.py:96: in __call__
|
|
await self.middleware_stack(scope, receive, send)
|
|
.venv\Lib\site-packages\starlette\middleware\errors.py:151: in __call__
|
|
await self.app(scope, receive, send)
|
|
.venv\Lib\site-packages\starlette\middleware\base.py:103: in __call__
|
|
await self.app(scope, receive, send)
|
|
.venv\Lib\site-packages\starlette\middleware\cors.py:80: in __call__
|
|
await self.app(scope, receive, send)
|
|
.venv\Lib\site-packages\starlette\middleware\exceptions.py:49: in __call__
|
|
await self.app(scope, receive, send)
|
|
.venv\Lib\site-packages\fastapi\middleware\asyncexitstack.py:18: in __call__
|
|
await self.app(scope, receive, send)
|
|
.venv\Lib\site-packages\starlette\routing.py:670: in __call__
|
|
await self.middleware_stack(scope, receive, send)
|
|
.venv\Lib\site-packages\fastapi\routing.py:2726: in app
|
|
await self.lifespan(scope, receive, send)
|
|
.venv\Lib\site-packages\starlette\routing.py:648: in lifespan
|
|
async with self.lifespan_context(app) as maybe_state:
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
..\..\..\..\AppData\Local\Python\pythoncore-3.14-64\Lib\contextlib.py:214: in __aenter__
|
|
return await anext(self.gen)
|
|
^^^^^^^^^^^^^^^^^^^^^
|
|
.venv\Lib\site-packages\fastapi\routing.py:240: in merged_lifespan
|
|
async with original_context(app) as maybe_original_state:
|
|
^^^^^^^^^^^^^^^^^^^^^
|
|
..\..\..\..\AppData\Local\Python\pythoncore-3.14-64\Lib\contextlib.py:214: in __aenter__
|
|
return await anext(self.gen)
|
|
^^^^^^^^^^^^^^^^^^^^^
|
|
.venv\Lib\site-packages\fastapi\routing.py:240: in merged_lifespan
|
|
async with original_context(app) as maybe_original_state:
|
|
^^^^^^^^^^^^^^^^^^^^^
|
|
..\..\..\..\AppData\Local\Python\pythoncore-3.14-64\Lib\contextlib.py:214: in __aenter__
|
|
return await anext(self.gen)
|
|
^^^^^^^^^^^^^^^^^^^^^
|
|
.venv\Lib\site-packages\fastapi\routing.py:240: in merged_lifespan
|
|
async with original_context(app) as maybe_original_state:
|
|
^^^^^^^^^^^^^^^^^^^^^
|
|
..\..\..\..\AppData\Local\Python\pythoncore-3.14-64\Lib\contextlib.py:214: in __aenter__
|
|
return await anext(self.gen)
|
|
^^^^^^^^^^^^^^^^^^^^^
|
|
.venv\Lib\site-packages\fastapi\routing.py:240: in merged_lifespan
|
|
async with original_context(app) as maybe_original_state:
|
|
^^^^^^^^^^^^^^^^^^^^^
|
|
..\..\..\..\AppData\Local\Python\pythoncore-3.14-64\Lib\contextlib.py:214: in __aenter__
|
|
return await anext(self.gen)
|
|
^^^^^^^^^^^^^^^^^^^^^
|
|
app\main.py:65: in lifespan
|
|
_bootstrap_admin()
|
|
app\main.py:34: in _bootstrap_admin
|
|
has_admin = session.scalar(
|
|
.venv\Lib\site-packages\sqlalchemy\orm\session.py:2421: in scalar
|
|
return self._execute_internal(
|
|
.venv\Lib\site-packages\sqlalchemy\orm\session.py:2271: in _execute_internal
|
|
result: Result[Any] = compile_state_cls.orm_execute_statement(
|
|
.venv\Lib\site-packages\sqlalchemy\orm\context.py:306: in orm_execute_statement
|
|
result = conn.execute(
|
|
.venv\Lib\site-packages\sqlalchemy\engine\base.py:1421: in execute
|
|
return meth(
|
|
.venv\Lib\site-packages\sqlalchemy\sql\elements.py:526: in _execute_on_connection
|
|
return connection._execute_clauseelement(
|
|
.venv\Lib\site-packages\sqlalchemy\engine\base.py:1643: in _execute_clauseelement
|
|
ret = self._execute_context(
|
|
.venv\Lib\site-packages\sqlalchemy\engine\base.py:1848: in _execute_context
|
|
return self._exec_single_context(
|
|
.venv\Lib\site-packages\sqlalchemy\engine\base.py:1988: in _exec_single_context
|
|
self._handle_dbapi_exception(
|
|
.venv\Lib\site-packages\sqlalchemy\engine\base.py:2365: in _handle_dbapi_exception
|
|
raise sqlalchemy_exception.with_traceback(exc_info[2]) from e
|
|
.venv\Lib\site-packages\sqlalchemy\engine\base.py:1969: in _exec_single_context
|
|
self.dialect.do_execute(
|
|
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
|
|
|
|
self = <sqlalchemy.dialects.sqlite.pysqlite.SQLiteDialect_pysqlite object at 0x0000019AEBDA3890>
|
|
cursor = <sqlite3.Cursor object at 0x0000019AEBAFA8C0>
|
|
statement = 'SELECT users.id \nFROM users \nWHERE users.role = ?'
|
|
parameters = ('SUPER_ADMIN',)
|
|
context = <sqlalchemy.dialects.sqlite.base.SQLiteExecutionContext object at 0x0000019AEB8968D0>
|
|
|
|
def do_execute(self, cursor, statement, parameters, context=None):
|
|
> cursor.execute(statement, parameters)
|
|
E sqlalchemy.exc.OperationalError: (sqlite3.OperationalError) no such table: users
|
|
E [SQL: SELECT users.id
|
|
E FROM users
|
|
E WHERE users.role = ?]
|
|
E [parameters: ('SUPER_ADMIN',)]
|
|
E (Background on this error at: https://sqlalche.me/e/20/e3q8)
|
|
|
|
.venv\Lib\site-packages\sqlalchemy\engine\default.py:952: OperationalError
|
|
___________________ ERROR at setup of test_group_lifecycle ____________________
|
|
|
|
self = <sqlalchemy.engine.base.Connection object at 0x0000019AEBB4A120>
|
|
dialect = <sqlalchemy.dialects.sqlite.pysqlite.SQLiteDialect_pysqlite object at 0x0000019AEBDA3C50>
|
|
context = <sqlalchemy.dialects.sqlite.base.SQLiteExecutionContext object at 0x0000019AEB896D50>
|
|
statement = <sqlalchemy.dialects.sqlite.base.SQLiteCompiler object at 0x0000019AEBBA0450>
|
|
parameters = [('SUPER_ADMIN',)]
|
|
|
|
def _exec_single_context(
|
|
self,
|
|
dialect: Dialect,
|
|
context: ExecutionContext,
|
|
statement: Union[str, Compiled],
|
|
parameters: Optional[_AnyMultiExecuteParams],
|
|
) -> CursorResult[Any]:
|
|
"""continue the _execute_context() method for a single DBAPI
|
|
cursor.execute() or cursor.executemany() call.
|
|
|
|
"""
|
|
if dialect.bind_typing is BindTyping.SETINPUTSIZES:
|
|
generic_setinputsizes = context._prepare_set_input_sizes()
|
|
|
|
if generic_setinputsizes:
|
|
try:
|
|
dialect.do_set_input_sizes(
|
|
context.cursor, generic_setinputsizes, context
|
|
)
|
|
except BaseException as e:
|
|
self._handle_dbapi_exception(
|
|
e, str(statement), parameters, None, context
|
|
)
|
|
|
|
cursor, str_statement, parameters = (
|
|
context.cursor,
|
|
context.statement,
|
|
context.parameters,
|
|
)
|
|
|
|
effective_parameters: Optional[_AnyExecuteParams]
|
|
|
|
if not context.executemany:
|
|
effective_parameters = parameters[0]
|
|
else:
|
|
effective_parameters = parameters
|
|
|
|
if self._has_events or self.engine._has_events:
|
|
for fn in self.dispatch.before_cursor_execute:
|
|
str_statement, effective_parameters = fn(
|
|
self,
|
|
cursor,
|
|
str_statement,
|
|
effective_parameters,
|
|
context,
|
|
context.executemany,
|
|
)
|
|
|
|
if self._echo:
|
|
self._log_info(str_statement)
|
|
|
|
stats = context._get_cache_stats()
|
|
|
|
if not self.engine.hide_parameters:
|
|
self._log_info(
|
|
"[%s] %r",
|
|
stats,
|
|
sql_util._repr_params(
|
|
effective_parameters,
|
|
batches=10,
|
|
ismulti=context.executemany,
|
|
),
|
|
)
|
|
else:
|
|
self._log_info(
|
|
"[%s] [SQL parameters hidden due to hide_parameters=True]",
|
|
stats,
|
|
)
|
|
|
|
evt_handled: bool = False
|
|
try:
|
|
if context.execute_style is ExecuteStyle.EXECUTEMANY:
|
|
effective_parameters = cast(
|
|
"_CoreMultiExecuteParams", effective_parameters
|
|
)
|
|
if self.dialect._has_events:
|
|
for fn in self.dialect.dispatch.do_executemany:
|
|
if fn(
|
|
cursor,
|
|
str_statement,
|
|
effective_parameters,
|
|
context,
|
|
):
|
|
evt_handled = True
|
|
break
|
|
if not evt_handled:
|
|
self.dialect.do_executemany(
|
|
cursor,
|
|
str_statement,
|
|
effective_parameters,
|
|
context,
|
|
)
|
|
elif not effective_parameters and context.no_parameters:
|
|
if self.dialect._has_events:
|
|
for fn in self.dialect.dispatch.do_execute_no_params:
|
|
if fn(cursor, str_statement, context):
|
|
evt_handled = True
|
|
break
|
|
if not evt_handled:
|
|
self.dialect.do_execute_no_params(
|
|
cursor, str_statement, context
|
|
)
|
|
else:
|
|
effective_parameters = cast(
|
|
"_CoreSingleExecuteParams", effective_parameters
|
|
)
|
|
if self.dialect._has_events:
|
|
for fn in self.dialect.dispatch.do_execute:
|
|
if fn(
|
|
cursor,
|
|
str_statement,
|
|
effective_parameters,
|
|
context,
|
|
):
|
|
evt_handled = True
|
|
break
|
|
if not evt_handled:
|
|
> self.dialect.do_execute(
|
|
cursor, str_statement, effective_parameters, context
|
|
)
|
|
|
|
.venv\Lib\site-packages\sqlalchemy\engine\base.py:1969:
|
|
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
|
|
|
|
self = <sqlalchemy.dialects.sqlite.pysqlite.SQLiteDialect_pysqlite object at 0x0000019AEBDA3C50>
|
|
cursor = <sqlite3.Cursor object at 0x0000019AEE5AABC0>
|
|
statement = 'SELECT users.id \nFROM users \nWHERE users.role = ?'
|
|
parameters = ('SUPER_ADMIN',)
|
|
context = <sqlalchemy.dialects.sqlite.base.SQLiteExecutionContext object at 0x0000019AEB896D50>
|
|
|
|
def do_execute(self, cursor, statement, parameters, context=None):
|
|
> cursor.execute(statement, parameters)
|
|
E sqlite3.OperationalError: no such table: users
|
|
|
|
.venv\Lib\site-packages\sqlalchemy\engine\default.py:952: OperationalError
|
|
|
|
The above exception was the direct cause of the following exception:
|
|
|
|
db_session = <sqlalchemy.orm.session.Session object at 0x0000019AEDC8D2B0>
|
|
monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x0000019AEBB48C80>
|
|
|
|
@pytest.fixture()
|
|
def client(db_session, monkeypatch):
|
|
"""TestClient wired to the fixture DB; dynsec calls are faked out."""
|
|
# Point the app's session factory at the test engine.
|
|
monkeypatch.setattr(db_module, "_SessionFactory", None)
|
|
monkeypatch.setattr(
|
|
db_module, "_engine", db_session.get_bind()
|
|
)
|
|
|
|
# Fake dynsec so no broker is needed for API tests.
|
|
class FakeDynsec:
|
|
def __init__(self):
|
|
self.clients: dict[str, dict] = {}
|
|
self.roles: dict[str, dict] = {}
|
|
self.groups: dict[str, dict] = {}
|
|
|
|
def _command(self, command, **kwargs):
|
|
return {"success": True}
|
|
|
|
def create_client(self, username, password, **kwargs):
|
|
self.clients[username] = {"username": username, "enabled": True, **kwargs}
|
|
|
|
def list_clients(self):
|
|
return list(self.clients.values())
|
|
|
|
def delete_client(self, username):
|
|
self.clients.pop(username, None)
|
|
|
|
def disable_client(self, username):
|
|
self.clients[username]["enabled"] = False
|
|
|
|
def enable_client(self, username):
|
|
self.clients[username]["enabled"] = True
|
|
|
|
def set_client_password(self, username, password):
|
|
pass
|
|
|
|
def get_client(self, username):
|
|
return self.clients[username]
|
|
|
|
def create_group(self, groupname, roles=None):
|
|
self.groups[groupname] = {"groupname": groupname, "roles": roles or []}
|
|
|
|
def list_groups(self):
|
|
return list(self.groups.values())
|
|
|
|
def delete_group(self, groupname):
|
|
self.groups.pop(groupname, None)
|
|
|
|
def create_role(self, rolename, acls=None):
|
|
self.roles[rolename] = {"rolename": rolename, "acls": acls or []}
|
|
|
|
def list_roles(self):
|
|
return list(self.roles.values())
|
|
|
|
def delete_role(self, rolename):
|
|
self.roles.pop(rolename, None)
|
|
|
|
def set_role_acls(self, rolename, acls):
|
|
self.roles[rolename]["acls"] = acls
|
|
|
|
def get_role(self, rolename):
|
|
return self.roles[rolename]
|
|
|
|
def connect(self):
|
|
pass
|
|
|
|
def close(self):
|
|
pass
|
|
|
|
fake = FakeDynsec()
|
|
monkeypatch.setattr(main_module, "dynsec_client", fake)
|
|
|
|
from app.routers import mqtt as mqtt_router
|
|
|
|
monkeypatch.setattr(mqtt_router, "dynsec_client", fake)
|
|
|
|
> with TestClient(main_module.app) as test_client:
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
|
|
tests\conftest.py:132:
|
|
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
|
|
.venv\Lib\site-packages\starlette\testclient.py:698: in __enter__
|
|
portal.call(self.wait_startup)
|
|
.venv\Lib\site-packages\anyio\from_thread.py:340: in call
|
|
return cast(T_Retval, self.start_task_soon(func, *args).result())
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
..\..\..\..\AppData\Local\Python\pythoncore-3.14-64\Lib\concurrent\futures\_base.py:450: in result
|
|
return self.__get_result()
|
|
^^^^^^^^^^^^^^^^^^^
|
|
..\..\..\..\AppData\Local\Python\pythoncore-3.14-64\Lib\concurrent\futures\_base.py:395: in __get_result
|
|
raise self._exception
|
|
.venv\Lib\site-packages\anyio\from_thread.py:265: in _call_func
|
|
retval = await retval_or_awaitable
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
.venv\Lib\site-packages\starlette\testclient.py:733: in wait_startup
|
|
await receive()
|
|
.venv\Lib\site-packages\starlette\testclient.py:724: in receive
|
|
self.task.result()
|
|
..\..\..\..\AppData\Local\Python\pythoncore-3.14-64\Lib\concurrent\futures\_base.py:443: in result
|
|
return self.__get_result()
|
|
^^^^^^^^^^^^^^^^^^^
|
|
..\..\..\..\AppData\Local\Python\pythoncore-3.14-64\Lib\concurrent\futures\_base.py:395: in __get_result
|
|
raise self._exception
|
|
.venv\Lib\site-packages\anyio\from_thread.py:265: in _call_func
|
|
retval = await retval_or_awaitable
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
.venv\Lib\site-packages\starlette\testclient.py:714: in lifespan
|
|
await self.app(scope, self.stream_receive.receive, self.stream_send.send)
|
|
.venv\Lib\site-packages\fastapi\applications.py:1163: in __call__
|
|
await super().__call__(scope, receive, send)
|
|
.venv\Lib\site-packages\starlette\applications.py:96: in __call__
|
|
await self.middleware_stack(scope, receive, send)
|
|
.venv\Lib\site-packages\starlette\middleware\errors.py:151: in __call__
|
|
await self.app(scope, receive, send)
|
|
.venv\Lib\site-packages\starlette\middleware\base.py:103: in __call__
|
|
await self.app(scope, receive, send)
|
|
.venv\Lib\site-packages\starlette\middleware\cors.py:80: in __call__
|
|
await self.app(scope, receive, send)
|
|
.venv\Lib\site-packages\starlette\middleware\exceptions.py:49: in __call__
|
|
await self.app(scope, receive, send)
|
|
.venv\Lib\site-packages\fastapi\middleware\asyncexitstack.py:18: in __call__
|
|
await self.app(scope, receive, send)
|
|
.venv\Lib\site-packages\starlette\routing.py:670: in __call__
|
|
await self.middleware_stack(scope, receive, send)
|
|
.venv\Lib\site-packages\fastapi\routing.py:2726: in app
|
|
await self.lifespan(scope, receive, send)
|
|
.venv\Lib\site-packages\starlette\routing.py:648: in lifespan
|
|
async with self.lifespan_context(app) as maybe_state:
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
..\..\..\..\AppData\Local\Python\pythoncore-3.14-64\Lib\contextlib.py:214: in __aenter__
|
|
return await anext(self.gen)
|
|
^^^^^^^^^^^^^^^^^^^^^
|
|
.venv\Lib\site-packages\fastapi\routing.py:240: in merged_lifespan
|
|
async with original_context(app) as maybe_original_state:
|
|
^^^^^^^^^^^^^^^^^^^^^
|
|
..\..\..\..\AppData\Local\Python\pythoncore-3.14-64\Lib\contextlib.py:214: in __aenter__
|
|
return await anext(self.gen)
|
|
^^^^^^^^^^^^^^^^^^^^^
|
|
.venv\Lib\site-packages\fastapi\routing.py:240: in merged_lifespan
|
|
async with original_context(app) as maybe_original_state:
|
|
^^^^^^^^^^^^^^^^^^^^^
|
|
..\..\..\..\AppData\Local\Python\pythoncore-3.14-64\Lib\contextlib.py:214: in __aenter__
|
|
return await anext(self.gen)
|
|
^^^^^^^^^^^^^^^^^^^^^
|
|
.venv\Lib\site-packages\fastapi\routing.py:240: in merged_lifespan
|
|
async with original_context(app) as maybe_original_state:
|
|
^^^^^^^^^^^^^^^^^^^^^
|
|
..\..\..\..\AppData\Local\Python\pythoncore-3.14-64\Lib\contextlib.py:214: in __aenter__
|
|
return await anext(self.gen)
|
|
^^^^^^^^^^^^^^^^^^^^^
|
|
.venv\Lib\site-packages\fastapi\routing.py:240: in merged_lifespan
|
|
async with original_context(app) as maybe_original_state:
|
|
^^^^^^^^^^^^^^^^^^^^^
|
|
..\..\..\..\AppData\Local\Python\pythoncore-3.14-64\Lib\contextlib.py:214: in __aenter__
|
|
return await anext(self.gen)
|
|
^^^^^^^^^^^^^^^^^^^^^
|
|
app\main.py:65: in lifespan
|
|
_bootstrap_admin()
|
|
app\main.py:34: in _bootstrap_admin
|
|
has_admin = session.scalar(
|
|
.venv\Lib\site-packages\sqlalchemy\orm\session.py:2421: in scalar
|
|
return self._execute_internal(
|
|
.venv\Lib\site-packages\sqlalchemy\orm\session.py:2271: in _execute_internal
|
|
result: Result[Any] = compile_state_cls.orm_execute_statement(
|
|
.venv\Lib\site-packages\sqlalchemy\orm\context.py:306: in orm_execute_statement
|
|
result = conn.execute(
|
|
.venv\Lib\site-packages\sqlalchemy\engine\base.py:1421: in execute
|
|
return meth(
|
|
.venv\Lib\site-packages\sqlalchemy\sql\elements.py:526: in _execute_on_connection
|
|
return connection._execute_clauseelement(
|
|
.venv\Lib\site-packages\sqlalchemy\engine\base.py:1643: in _execute_clauseelement
|
|
ret = self._execute_context(
|
|
.venv\Lib\site-packages\sqlalchemy\engine\base.py:1848: in _execute_context
|
|
return self._exec_single_context(
|
|
.venv\Lib\site-packages\sqlalchemy\engine\base.py:1988: in _exec_single_context
|
|
self._handle_dbapi_exception(
|
|
.venv\Lib\site-packages\sqlalchemy\engine\base.py:2365: in _handle_dbapi_exception
|
|
raise sqlalchemy_exception.with_traceback(exc_info[2]) from e
|
|
.venv\Lib\site-packages\sqlalchemy\engine\base.py:1969: in _exec_single_context
|
|
self.dialect.do_execute(
|
|
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
|
|
|
|
self = <sqlalchemy.dialects.sqlite.pysqlite.SQLiteDialect_pysqlite object at 0x0000019AEBDA3C50>
|
|
cursor = <sqlite3.Cursor object at 0x0000019AEE5AABC0>
|
|
statement = 'SELECT users.id \nFROM users \nWHERE users.role = ?'
|
|
parameters = ('SUPER_ADMIN',)
|
|
context = <sqlalchemy.dialects.sqlite.base.SQLiteExecutionContext object at 0x0000019AEB896D50>
|
|
|
|
def do_execute(self, cursor, statement, parameters, context=None):
|
|
> cursor.execute(statement, parameters)
|
|
E sqlalchemy.exc.OperationalError: (sqlite3.OperationalError) no such table: users
|
|
E [SQL: SELECT users.id
|
|
E FROM users
|
|
E WHERE users.role = ?]
|
|
E [parameters: ('SUPER_ADMIN',)]
|
|
E (Background on this error at: https://sqlalche.me/e/20/e3q8)
|
|
|
|
.venv\Lib\site-packages\sqlalchemy\engine\default.py:952: OperationalError
|
|
___________ ERROR at setup of test_listener_management_and_preview ____________
|
|
|
|
self = <sqlalchemy.engine.base.Connection object at 0x0000019AEBB42030>
|
|
dialect = <sqlalchemy.dialects.sqlite.pysqlite.SQLiteDialect_pysqlite object at 0x0000019AEBDA3D90>
|
|
context = <sqlalchemy.dialects.sqlite.base.SQLiteExecutionContext object at 0x0000019AEE96C650>
|
|
statement = <sqlalchemy.dialects.sqlite.base.SQLiteCompiler object at 0x0000019AEBBA2350>
|
|
parameters = [('SUPER_ADMIN',)]
|
|
|
|
def _exec_single_context(
|
|
self,
|
|
dialect: Dialect,
|
|
context: ExecutionContext,
|
|
statement: Union[str, Compiled],
|
|
parameters: Optional[_AnyMultiExecuteParams],
|
|
) -> CursorResult[Any]:
|
|
"""continue the _execute_context() method for a single DBAPI
|
|
cursor.execute() or cursor.executemany() call.
|
|
|
|
"""
|
|
if dialect.bind_typing is BindTyping.SETINPUTSIZES:
|
|
generic_setinputsizes = context._prepare_set_input_sizes()
|
|
|
|
if generic_setinputsizes:
|
|
try:
|
|
dialect.do_set_input_sizes(
|
|
context.cursor, generic_setinputsizes, context
|
|
)
|
|
except BaseException as e:
|
|
self._handle_dbapi_exception(
|
|
e, str(statement), parameters, None, context
|
|
)
|
|
|
|
cursor, str_statement, parameters = (
|
|
context.cursor,
|
|
context.statement,
|
|
context.parameters,
|
|
)
|
|
|
|
effective_parameters: Optional[_AnyExecuteParams]
|
|
|
|
if not context.executemany:
|
|
effective_parameters = parameters[0]
|
|
else:
|
|
effective_parameters = parameters
|
|
|
|
if self._has_events or self.engine._has_events:
|
|
for fn in self.dispatch.before_cursor_execute:
|
|
str_statement, effective_parameters = fn(
|
|
self,
|
|
cursor,
|
|
str_statement,
|
|
effective_parameters,
|
|
context,
|
|
context.executemany,
|
|
)
|
|
|
|
if self._echo:
|
|
self._log_info(str_statement)
|
|
|
|
stats = context._get_cache_stats()
|
|
|
|
if not self.engine.hide_parameters:
|
|
self._log_info(
|
|
"[%s] %r",
|
|
stats,
|
|
sql_util._repr_params(
|
|
effective_parameters,
|
|
batches=10,
|
|
ismulti=context.executemany,
|
|
),
|
|
)
|
|
else:
|
|
self._log_info(
|
|
"[%s] [SQL parameters hidden due to hide_parameters=True]",
|
|
stats,
|
|
)
|
|
|
|
evt_handled: bool = False
|
|
try:
|
|
if context.execute_style is ExecuteStyle.EXECUTEMANY:
|
|
effective_parameters = cast(
|
|
"_CoreMultiExecuteParams", effective_parameters
|
|
)
|
|
if self.dialect._has_events:
|
|
for fn in self.dialect.dispatch.do_executemany:
|
|
if fn(
|
|
cursor,
|
|
str_statement,
|
|
effective_parameters,
|
|
context,
|
|
):
|
|
evt_handled = True
|
|
break
|
|
if not evt_handled:
|
|
self.dialect.do_executemany(
|
|
cursor,
|
|
str_statement,
|
|
effective_parameters,
|
|
context,
|
|
)
|
|
elif not effective_parameters and context.no_parameters:
|
|
if self.dialect._has_events:
|
|
for fn in self.dialect.dispatch.do_execute_no_params:
|
|
if fn(cursor, str_statement, context):
|
|
evt_handled = True
|
|
break
|
|
if not evt_handled:
|
|
self.dialect.do_execute_no_params(
|
|
cursor, str_statement, context
|
|
)
|
|
else:
|
|
effective_parameters = cast(
|
|
"_CoreSingleExecuteParams", effective_parameters
|
|
)
|
|
if self.dialect._has_events:
|
|
for fn in self.dialect.dispatch.do_execute:
|
|
if fn(
|
|
cursor,
|
|
str_statement,
|
|
effective_parameters,
|
|
context,
|
|
):
|
|
evt_handled = True
|
|
break
|
|
if not evt_handled:
|
|
> self.dialect.do_execute(
|
|
cursor, str_statement, effective_parameters, context
|
|
)
|
|
|
|
.venv\Lib\site-packages\sqlalchemy\engine\base.py:1969:
|
|
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
|
|
|
|
self = <sqlalchemy.dialects.sqlite.pysqlite.SQLiteDialect_pysqlite object at 0x0000019AEBDA3D90>
|
|
cursor = <sqlite3.Cursor object at 0x0000019AED6F4DC0>
|
|
statement = 'SELECT users.id \nFROM users \nWHERE users.role = ?'
|
|
parameters = ('SUPER_ADMIN',)
|
|
context = <sqlalchemy.dialects.sqlite.base.SQLiteExecutionContext object at 0x0000019AEE96C650>
|
|
|
|
def do_execute(self, cursor, statement, parameters, context=None):
|
|
> cursor.execute(statement, parameters)
|
|
E sqlite3.OperationalError: no such table: users
|
|
|
|
.venv\Lib\site-packages\sqlalchemy\engine\default.py:952: OperationalError
|
|
|
|
The above exception was the direct cause of the following exception:
|
|
|
|
db_session = <sqlalchemy.orm.session.Session object at 0x0000019AEDC8D400>
|
|
monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x0000019AEBDCE6D0>
|
|
|
|
@pytest.fixture()
|
|
def client(db_session, monkeypatch):
|
|
"""TestClient wired to the fixture DB; dynsec calls are faked out."""
|
|
# Point the app's session factory at the test engine.
|
|
monkeypatch.setattr(db_module, "_SessionFactory", None)
|
|
monkeypatch.setattr(
|
|
db_module, "_engine", db_session.get_bind()
|
|
)
|
|
|
|
# Fake dynsec so no broker is needed for API tests.
|
|
class FakeDynsec:
|
|
def __init__(self):
|
|
self.clients: dict[str, dict] = {}
|
|
self.roles: dict[str, dict] = {}
|
|
self.groups: dict[str, dict] = {}
|
|
|
|
def _command(self, command, **kwargs):
|
|
return {"success": True}
|
|
|
|
def create_client(self, username, password, **kwargs):
|
|
self.clients[username] = {"username": username, "enabled": True, **kwargs}
|
|
|
|
def list_clients(self):
|
|
return list(self.clients.values())
|
|
|
|
def delete_client(self, username):
|
|
self.clients.pop(username, None)
|
|
|
|
def disable_client(self, username):
|
|
self.clients[username]["enabled"] = False
|
|
|
|
def enable_client(self, username):
|
|
self.clients[username]["enabled"] = True
|
|
|
|
def set_client_password(self, username, password):
|
|
pass
|
|
|
|
def get_client(self, username):
|
|
return self.clients[username]
|
|
|
|
def create_group(self, groupname, roles=None):
|
|
self.groups[groupname] = {"groupname": groupname, "roles": roles or []}
|
|
|
|
def list_groups(self):
|
|
return list(self.groups.values())
|
|
|
|
def delete_group(self, groupname):
|
|
self.groups.pop(groupname, None)
|
|
|
|
def create_role(self, rolename, acls=None):
|
|
self.roles[rolename] = {"rolename": rolename, "acls": acls or []}
|
|
|
|
def list_roles(self):
|
|
return list(self.roles.values())
|
|
|
|
def delete_role(self, rolename):
|
|
self.roles.pop(rolename, None)
|
|
|
|
def set_role_acls(self, rolename, acls):
|
|
self.roles[rolename]["acls"] = acls
|
|
|
|
def get_role(self, rolename):
|
|
return self.roles[rolename]
|
|
|
|
def connect(self):
|
|
pass
|
|
|
|
def close(self):
|
|
pass
|
|
|
|
fake = FakeDynsec()
|
|
monkeypatch.setattr(main_module, "dynsec_client", fake)
|
|
|
|
from app.routers import mqtt as mqtt_router
|
|
|
|
monkeypatch.setattr(mqtt_router, "dynsec_client", fake)
|
|
|
|
> with TestClient(main_module.app) as test_client:
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
|
|
tests\conftest.py:132:
|
|
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
|
|
.venv\Lib\site-packages\starlette\testclient.py:698: in __enter__
|
|
portal.call(self.wait_startup)
|
|
.venv\Lib\site-packages\anyio\from_thread.py:340: in call
|
|
return cast(T_Retval, self.start_task_soon(func, *args).result())
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
..\..\..\..\AppData\Local\Python\pythoncore-3.14-64\Lib\concurrent\futures\_base.py:450: in result
|
|
return self.__get_result()
|
|
^^^^^^^^^^^^^^^^^^^
|
|
..\..\..\..\AppData\Local\Python\pythoncore-3.14-64\Lib\concurrent\futures\_base.py:395: in __get_result
|
|
raise self._exception
|
|
.venv\Lib\site-packages\anyio\from_thread.py:265: in _call_func
|
|
retval = await retval_or_awaitable
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
.venv\Lib\site-packages\starlette\testclient.py:733: in wait_startup
|
|
await receive()
|
|
.venv\Lib\site-packages\starlette\testclient.py:724: in receive
|
|
self.task.result()
|
|
..\..\..\..\AppData\Local\Python\pythoncore-3.14-64\Lib\concurrent\futures\_base.py:443: in result
|
|
return self.__get_result()
|
|
^^^^^^^^^^^^^^^^^^^
|
|
..\..\..\..\AppData\Local\Python\pythoncore-3.14-64\Lib\concurrent\futures\_base.py:395: in __get_result
|
|
raise self._exception
|
|
.venv\Lib\site-packages\anyio\from_thread.py:265: in _call_func
|
|
retval = await retval_or_awaitable
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
.venv\Lib\site-packages\starlette\testclient.py:714: in lifespan
|
|
await self.app(scope, self.stream_receive.receive, self.stream_send.send)
|
|
.venv\Lib\site-packages\fastapi\applications.py:1163: in __call__
|
|
await super().__call__(scope, receive, send)
|
|
.venv\Lib\site-packages\starlette\applications.py:96: in __call__
|
|
await self.middleware_stack(scope, receive, send)
|
|
.venv\Lib\site-packages\starlette\middleware\errors.py:151: in __call__
|
|
await self.app(scope, receive, send)
|
|
.venv\Lib\site-packages\starlette\middleware\base.py:103: in __call__
|
|
await self.app(scope, receive, send)
|
|
.venv\Lib\site-packages\starlette\middleware\cors.py:80: in __call__
|
|
await self.app(scope, receive, send)
|
|
.venv\Lib\site-packages\starlette\middleware\exceptions.py:49: in __call__
|
|
await self.app(scope, receive, send)
|
|
.venv\Lib\site-packages\fastapi\middleware\asyncexitstack.py:18: in __call__
|
|
await self.app(scope, receive, send)
|
|
.venv\Lib\site-packages\starlette\routing.py:670: in __call__
|
|
await self.middleware_stack(scope, receive, send)
|
|
.venv\Lib\site-packages\fastapi\routing.py:2726: in app
|
|
await self.lifespan(scope, receive, send)
|
|
.venv\Lib\site-packages\starlette\routing.py:648: in lifespan
|
|
async with self.lifespan_context(app) as maybe_state:
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
..\..\..\..\AppData\Local\Python\pythoncore-3.14-64\Lib\contextlib.py:214: in __aenter__
|
|
return await anext(self.gen)
|
|
^^^^^^^^^^^^^^^^^^^^^
|
|
.venv\Lib\site-packages\fastapi\routing.py:240: in merged_lifespan
|
|
async with original_context(app) as maybe_original_state:
|
|
^^^^^^^^^^^^^^^^^^^^^
|
|
..\..\..\..\AppData\Local\Python\pythoncore-3.14-64\Lib\contextlib.py:214: in __aenter__
|
|
return await anext(self.gen)
|
|
^^^^^^^^^^^^^^^^^^^^^
|
|
.venv\Lib\site-packages\fastapi\routing.py:240: in merged_lifespan
|
|
async with original_context(app) as maybe_original_state:
|
|
^^^^^^^^^^^^^^^^^^^^^
|
|
..\..\..\..\AppData\Local\Python\pythoncore-3.14-64\Lib\contextlib.py:214: in __aenter__
|
|
return await anext(self.gen)
|
|
^^^^^^^^^^^^^^^^^^^^^
|
|
.venv\Lib\site-packages\fastapi\routing.py:240: in merged_lifespan
|
|
async with original_context(app) as maybe_original_state:
|
|
^^^^^^^^^^^^^^^^^^^^^
|
|
..\..\..\..\AppData\Local\Python\pythoncore-3.14-64\Lib\contextlib.py:214: in __aenter__
|
|
return await anext(self.gen)
|
|
^^^^^^^^^^^^^^^^^^^^^
|
|
.venv\Lib\site-packages\fastapi\routing.py:240: in merged_lifespan
|
|
async with original_context(app) as maybe_original_state:
|
|
^^^^^^^^^^^^^^^^^^^^^
|
|
..\..\..\..\AppData\Local\Python\pythoncore-3.14-64\Lib\contextlib.py:214: in __aenter__
|
|
return await anext(self.gen)
|
|
^^^^^^^^^^^^^^^^^^^^^
|
|
app\main.py:65: in lifespan
|
|
_bootstrap_admin()
|
|
app\main.py:34: in _bootstrap_admin
|
|
has_admin = session.scalar(
|
|
.venv\Lib\site-packages\sqlalchemy\orm\session.py:2421: in scalar
|
|
return self._execute_internal(
|
|
.venv\Lib\site-packages\sqlalchemy\orm\session.py:2271: in _execute_internal
|
|
result: Result[Any] = compile_state_cls.orm_execute_statement(
|
|
.venv\Lib\site-packages\sqlalchemy\orm\context.py:306: in orm_execute_statement
|
|
result = conn.execute(
|
|
.venv\Lib\site-packages\sqlalchemy\engine\base.py:1421: in execute
|
|
return meth(
|
|
.venv\Lib\site-packages\sqlalchemy\sql\elements.py:526: in _execute_on_connection
|
|
return connection._execute_clauseelement(
|
|
.venv\Lib\site-packages\sqlalchemy\engine\base.py:1643: in _execute_clauseelement
|
|
ret = self._execute_context(
|
|
.venv\Lib\site-packages\sqlalchemy\engine\base.py:1848: in _execute_context
|
|
return self._exec_single_context(
|
|
.venv\Lib\site-packages\sqlalchemy\engine\base.py:1988: in _exec_single_context
|
|
self._handle_dbapi_exception(
|
|
.venv\Lib\site-packages\sqlalchemy\engine\base.py:2365: in _handle_dbapi_exception
|
|
raise sqlalchemy_exception.with_traceback(exc_info[2]) from e
|
|
.venv\Lib\site-packages\sqlalchemy\engine\base.py:1969: in _exec_single_context
|
|
self.dialect.do_execute(
|
|
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
|
|
|
|
self = <sqlalchemy.dialects.sqlite.pysqlite.SQLiteDialect_pysqlite object at 0x0000019AEBDA3D90>
|
|
cursor = <sqlite3.Cursor object at 0x0000019AED6F4DC0>
|
|
statement = 'SELECT users.id \nFROM users \nWHERE users.role = ?'
|
|
parameters = ('SUPER_ADMIN',)
|
|
context = <sqlalchemy.dialects.sqlite.base.SQLiteExecutionContext object at 0x0000019AEE96C650>
|
|
|
|
def do_execute(self, cursor, statement, parameters, context=None):
|
|
> cursor.execute(statement, parameters)
|
|
E sqlalchemy.exc.OperationalError: (sqlite3.OperationalError) no such table: users
|
|
E [SQL: SELECT users.id
|
|
E FROM users
|
|
E WHERE users.role = ?]
|
|
E [parameters: ('SUPER_ADMIN',)]
|
|
E (Background on this error at: https://sqlalche.me/e/20/e3q8)
|
|
|
|
.venv\Lib\site-packages\sqlalchemy\engine\default.py:952: OperationalError
|
|
________________ ERROR at setup of test_audit_events_recorded _________________
|
|
|
|
self = <sqlalchemy.engine.base.Connection object at 0x0000019AEBB5EB70>
|
|
dialect = <sqlalchemy.dialects.sqlite.pysqlite.SQLiteDialect_pysqlite object at 0x0000019AEBB80410>
|
|
context = <sqlalchemy.dialects.sqlite.base.SQLiteExecutionContext object at 0x0000019AEB8971D0>
|
|
statement = <sqlalchemy.dialects.sqlite.base.SQLiteCompiler object at 0x0000019AEBBA2F50>
|
|
parameters = [('SUPER_ADMIN',)]
|
|
|
|
def _exec_single_context(
|
|
self,
|
|
dialect: Dialect,
|
|
context: ExecutionContext,
|
|
statement: Union[str, Compiled],
|
|
parameters: Optional[_AnyMultiExecuteParams],
|
|
) -> CursorResult[Any]:
|
|
"""continue the _execute_context() method for a single DBAPI
|
|
cursor.execute() or cursor.executemany() call.
|
|
|
|
"""
|
|
if dialect.bind_typing is BindTyping.SETINPUTSIZES:
|
|
generic_setinputsizes = context._prepare_set_input_sizes()
|
|
|
|
if generic_setinputsizes:
|
|
try:
|
|
dialect.do_set_input_sizes(
|
|
context.cursor, generic_setinputsizes, context
|
|
)
|
|
except BaseException as e:
|
|
self._handle_dbapi_exception(
|
|
e, str(statement), parameters, None, context
|
|
)
|
|
|
|
cursor, str_statement, parameters = (
|
|
context.cursor,
|
|
context.statement,
|
|
context.parameters,
|
|
)
|
|
|
|
effective_parameters: Optional[_AnyExecuteParams]
|
|
|
|
if not context.executemany:
|
|
effective_parameters = parameters[0]
|
|
else:
|
|
effective_parameters = parameters
|
|
|
|
if self._has_events or self.engine._has_events:
|
|
for fn in self.dispatch.before_cursor_execute:
|
|
str_statement, effective_parameters = fn(
|
|
self,
|
|
cursor,
|
|
str_statement,
|
|
effective_parameters,
|
|
context,
|
|
context.executemany,
|
|
)
|
|
|
|
if self._echo:
|
|
self._log_info(str_statement)
|
|
|
|
stats = context._get_cache_stats()
|
|
|
|
if not self.engine.hide_parameters:
|
|
self._log_info(
|
|
"[%s] %r",
|
|
stats,
|
|
sql_util._repr_params(
|
|
effective_parameters,
|
|
batches=10,
|
|
ismulti=context.executemany,
|
|
),
|
|
)
|
|
else:
|
|
self._log_info(
|
|
"[%s] [SQL parameters hidden due to hide_parameters=True]",
|
|
stats,
|
|
)
|
|
|
|
evt_handled: bool = False
|
|
try:
|
|
if context.execute_style is ExecuteStyle.EXECUTEMANY:
|
|
effective_parameters = cast(
|
|
"_CoreMultiExecuteParams", effective_parameters
|
|
)
|
|
if self.dialect._has_events:
|
|
for fn in self.dialect.dispatch.do_executemany:
|
|
if fn(
|
|
cursor,
|
|
str_statement,
|
|
effective_parameters,
|
|
context,
|
|
):
|
|
evt_handled = True
|
|
break
|
|
if not evt_handled:
|
|
self.dialect.do_executemany(
|
|
cursor,
|
|
str_statement,
|
|
effective_parameters,
|
|
context,
|
|
)
|
|
elif not effective_parameters and context.no_parameters:
|
|
if self.dialect._has_events:
|
|
for fn in self.dialect.dispatch.do_execute_no_params:
|
|
if fn(cursor, str_statement, context):
|
|
evt_handled = True
|
|
break
|
|
if not evt_handled:
|
|
self.dialect.do_execute_no_params(
|
|
cursor, str_statement, context
|
|
)
|
|
else:
|
|
effective_parameters = cast(
|
|
"_CoreSingleExecuteParams", effective_parameters
|
|
)
|
|
if self.dialect._has_events:
|
|
for fn in self.dialect.dispatch.do_execute:
|
|
if fn(
|
|
cursor,
|
|
str_statement,
|
|
effective_parameters,
|
|
context,
|
|
):
|
|
evt_handled = True
|
|
break
|
|
if not evt_handled:
|
|
> self.dialect.do_execute(
|
|
cursor, str_statement, effective_parameters, context
|
|
)
|
|
|
|
.venv\Lib\site-packages\sqlalchemy\engine\base.py:1969:
|
|
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
|
|
|
|
self = <sqlalchemy.dialects.sqlite.pysqlite.SQLiteDialect_pysqlite object at 0x0000019AEBB80410>
|
|
cursor = <sqlite3.Cursor object at 0x0000019AEE18D6C0>
|
|
statement = 'SELECT users.id \nFROM users \nWHERE users.role = ?'
|
|
parameters = ('SUPER_ADMIN',)
|
|
context = <sqlalchemy.dialects.sqlite.base.SQLiteExecutionContext object at 0x0000019AEB8971D0>
|
|
|
|
def do_execute(self, cursor, statement, parameters, context=None):
|
|
> cursor.execute(statement, parameters)
|
|
E sqlite3.OperationalError: no such table: users
|
|
|
|
.venv\Lib\site-packages\sqlalchemy\engine\default.py:952: OperationalError
|
|
|
|
The above exception was the direct cause of the following exception:
|
|
|
|
db_session = <sqlalchemy.orm.session.Session object at 0x0000019AEDC8DA90>
|
|
monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x0000019AEBBC2DD0>
|
|
|
|
@pytest.fixture()
|
|
def client(db_session, monkeypatch):
|
|
"""TestClient wired to the fixture DB; dynsec calls are faked out."""
|
|
# Point the app's session factory at the test engine.
|
|
monkeypatch.setattr(db_module, "_SessionFactory", None)
|
|
monkeypatch.setattr(
|
|
db_module, "_engine", db_session.get_bind()
|
|
)
|
|
|
|
# Fake dynsec so no broker is needed for API tests.
|
|
class FakeDynsec:
|
|
def __init__(self):
|
|
self.clients: dict[str, dict] = {}
|
|
self.roles: dict[str, dict] = {}
|
|
self.groups: dict[str, dict] = {}
|
|
|
|
def _command(self, command, **kwargs):
|
|
return {"success": True}
|
|
|
|
def create_client(self, username, password, **kwargs):
|
|
self.clients[username] = {"username": username, "enabled": True, **kwargs}
|
|
|
|
def list_clients(self):
|
|
return list(self.clients.values())
|
|
|
|
def delete_client(self, username):
|
|
self.clients.pop(username, None)
|
|
|
|
def disable_client(self, username):
|
|
self.clients[username]["enabled"] = False
|
|
|
|
def enable_client(self, username):
|
|
self.clients[username]["enabled"] = True
|
|
|
|
def set_client_password(self, username, password):
|
|
pass
|
|
|
|
def get_client(self, username):
|
|
return self.clients[username]
|
|
|
|
def create_group(self, groupname, roles=None):
|
|
self.groups[groupname] = {"groupname": groupname, "roles": roles or []}
|
|
|
|
def list_groups(self):
|
|
return list(self.groups.values())
|
|
|
|
def delete_group(self, groupname):
|
|
self.groups.pop(groupname, None)
|
|
|
|
def create_role(self, rolename, acls=None):
|
|
self.roles[rolename] = {"rolename": rolename, "acls": acls or []}
|
|
|
|
def list_roles(self):
|
|
return list(self.roles.values())
|
|
|
|
def delete_role(self, rolename):
|
|
self.roles.pop(rolename, None)
|
|
|
|
def set_role_acls(self, rolename, acls):
|
|
self.roles[rolename]["acls"] = acls
|
|
|
|
def get_role(self, rolename):
|
|
return self.roles[rolename]
|
|
|
|
def connect(self):
|
|
pass
|
|
|
|
def close(self):
|
|
pass
|
|
|
|
fake = FakeDynsec()
|
|
monkeypatch.setattr(main_module, "dynsec_client", fake)
|
|
|
|
from app.routers import mqtt as mqtt_router
|
|
|
|
monkeypatch.setattr(mqtt_router, "dynsec_client", fake)
|
|
|
|
> with TestClient(main_module.app) as test_client:
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
|
|
tests\conftest.py:132:
|
|
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
|
|
.venv\Lib\site-packages\starlette\testclient.py:698: in __enter__
|
|
portal.call(self.wait_startup)
|
|
.venv\Lib\site-packages\anyio\from_thread.py:340: in call
|
|
return cast(T_Retval, self.start_task_soon(func, *args).result())
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
..\..\..\..\AppData\Local\Python\pythoncore-3.14-64\Lib\concurrent\futures\_base.py:450: in result
|
|
return self.__get_result()
|
|
^^^^^^^^^^^^^^^^^^^
|
|
..\..\..\..\AppData\Local\Python\pythoncore-3.14-64\Lib\concurrent\futures\_base.py:395: in __get_result
|
|
raise self._exception
|
|
.venv\Lib\site-packages\anyio\from_thread.py:265: in _call_func
|
|
retval = await retval_or_awaitable
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
.venv\Lib\site-packages\starlette\testclient.py:733: in wait_startup
|
|
await receive()
|
|
.venv\Lib\site-packages\starlette\testclient.py:724: in receive
|
|
self.task.result()
|
|
..\..\..\..\AppData\Local\Python\pythoncore-3.14-64\Lib\concurrent\futures\_base.py:443: in result
|
|
return self.__get_result()
|
|
^^^^^^^^^^^^^^^^^^^
|
|
..\..\..\..\AppData\Local\Python\pythoncore-3.14-64\Lib\concurrent\futures\_base.py:395: in __get_result
|
|
raise self._exception
|
|
.venv\Lib\site-packages\anyio\from_thread.py:265: in _call_func
|
|
retval = await retval_or_awaitable
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
.venv\Lib\site-packages\starlette\testclient.py:714: in lifespan
|
|
await self.app(scope, self.stream_receive.receive, self.stream_send.send)
|
|
.venv\Lib\site-packages\fastapi\applications.py:1163: in __call__
|
|
await super().__call__(scope, receive, send)
|
|
.venv\Lib\site-packages\starlette\applications.py:96: in __call__
|
|
await self.middleware_stack(scope, receive, send)
|
|
.venv\Lib\site-packages\starlette\middleware\errors.py:151: in __call__
|
|
await self.app(scope, receive, send)
|
|
.venv\Lib\site-packages\starlette\middleware\base.py:103: in __call__
|
|
await self.app(scope, receive, send)
|
|
.venv\Lib\site-packages\starlette\middleware\cors.py:80: in __call__
|
|
await self.app(scope, receive, send)
|
|
.venv\Lib\site-packages\starlette\middleware\exceptions.py:49: in __call__
|
|
await self.app(scope, receive, send)
|
|
.venv\Lib\site-packages\fastapi\middleware\asyncexitstack.py:18: in __call__
|
|
await self.app(scope, receive, send)
|
|
.venv\Lib\site-packages\starlette\routing.py:670: in __call__
|
|
await self.middleware_stack(scope, receive, send)
|
|
.venv\Lib\site-packages\fastapi\routing.py:2726: in app
|
|
await self.lifespan(scope, receive, send)
|
|
.venv\Lib\site-packages\starlette\routing.py:648: in lifespan
|
|
async with self.lifespan_context(app) as maybe_state:
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
..\..\..\..\AppData\Local\Python\pythoncore-3.14-64\Lib\contextlib.py:214: in __aenter__
|
|
return await anext(self.gen)
|
|
^^^^^^^^^^^^^^^^^^^^^
|
|
.venv\Lib\site-packages\fastapi\routing.py:240: in merged_lifespan
|
|
async with original_context(app) as maybe_original_state:
|
|
^^^^^^^^^^^^^^^^^^^^^
|
|
..\..\..\..\AppData\Local\Python\pythoncore-3.14-64\Lib\contextlib.py:214: in __aenter__
|
|
return await anext(self.gen)
|
|
^^^^^^^^^^^^^^^^^^^^^
|
|
.venv\Lib\site-packages\fastapi\routing.py:240: in merged_lifespan
|
|
async with original_context(app) as maybe_original_state:
|
|
^^^^^^^^^^^^^^^^^^^^^
|
|
..\..\..\..\AppData\Local\Python\pythoncore-3.14-64\Lib\contextlib.py:214: in __aenter__
|
|
return await anext(self.gen)
|
|
^^^^^^^^^^^^^^^^^^^^^
|
|
.venv\Lib\site-packages\fastapi\routing.py:240: in merged_lifespan
|
|
async with original_context(app) as maybe_original_state:
|
|
^^^^^^^^^^^^^^^^^^^^^
|
|
..\..\..\..\AppData\Local\Python\pythoncore-3.14-64\Lib\contextlib.py:214: in __aenter__
|
|
return await anext(self.gen)
|
|
^^^^^^^^^^^^^^^^^^^^^
|
|
.venv\Lib\site-packages\fastapi\routing.py:240: in merged_lifespan
|
|
async with original_context(app) as maybe_original_state:
|
|
^^^^^^^^^^^^^^^^^^^^^
|
|
..\..\..\..\AppData\Local\Python\pythoncore-3.14-64\Lib\contextlib.py:214: in __aenter__
|
|
return await anext(self.gen)
|
|
^^^^^^^^^^^^^^^^^^^^^
|
|
app\main.py:65: in lifespan
|
|
_bootstrap_admin()
|
|
app\main.py:34: in _bootstrap_admin
|
|
has_admin = session.scalar(
|
|
.venv\Lib\site-packages\sqlalchemy\orm\session.py:2421: in scalar
|
|
return self._execute_internal(
|
|
.venv\Lib\site-packages\sqlalchemy\orm\session.py:2271: in _execute_internal
|
|
result: Result[Any] = compile_state_cls.orm_execute_statement(
|
|
.venv\Lib\site-packages\sqlalchemy\orm\context.py:306: in orm_execute_statement
|
|
result = conn.execute(
|
|
.venv\Lib\site-packages\sqlalchemy\engine\base.py:1421: in execute
|
|
return meth(
|
|
.venv\Lib\site-packages\sqlalchemy\sql\elements.py:526: in _execute_on_connection
|
|
return connection._execute_clauseelement(
|
|
.venv\Lib\site-packages\sqlalchemy\engine\base.py:1643: in _execute_clauseelement
|
|
ret = self._execute_context(
|
|
.venv\Lib\site-packages\sqlalchemy\engine\base.py:1848: in _execute_context
|
|
return self._exec_single_context(
|
|
.venv\Lib\site-packages\sqlalchemy\engine\base.py:1988: in _exec_single_context
|
|
self._handle_dbapi_exception(
|
|
.venv\Lib\site-packages\sqlalchemy\engine\base.py:2365: in _handle_dbapi_exception
|
|
raise sqlalchemy_exception.with_traceback(exc_info[2]) from e
|
|
.venv\Lib\site-packages\sqlalchemy\engine\base.py:1969: in _exec_single_context
|
|
self.dialect.do_execute(
|
|
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
|
|
|
|
self = <sqlalchemy.dialects.sqlite.pysqlite.SQLiteDialect_pysqlite object at 0x0000019AEBB80410>
|
|
cursor = <sqlite3.Cursor object at 0x0000019AEE18D6C0>
|
|
statement = 'SELECT users.id \nFROM users \nWHERE users.role = ?'
|
|
parameters = ('SUPER_ADMIN',)
|
|
context = <sqlalchemy.dialects.sqlite.base.SQLiteExecutionContext object at 0x0000019AEB8971D0>
|
|
|
|
def do_execute(self, cursor, statement, parameters, context=None):
|
|
> cursor.execute(statement, parameters)
|
|
E sqlalchemy.exc.OperationalError: (sqlite3.OperationalError) no such table: users
|
|
E [SQL: SELECT users.id
|
|
E FROM users
|
|
E WHERE users.role = ?]
|
|
E [parameters: ('SUPER_ADMIN',)]
|
|
E (Background on this error at: https://sqlalche.me/e/20/e3q8)
|
|
|
|
.venv\Lib\site-packages\sqlalchemy\engine\default.py:952: OperationalError
|
|
============================== warnings summary ===============================
|
|
.venv\Lib\site-packages\fastapi\testclient.py:1
|
|
C:\Users\jonas\Documents\Git\mqtt-broker-stack\backend\.venv\Lib\site-packages\fastapi\testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
|
|
from starlette.testclient import TestClient as TestClient # noqa
|
|
|
|
.venv\Lib\site-packages\starlette\testclient.py:53
|
|
C:\Users\jonas\Documents\Git\mqtt-broker-stack\backend\.venv\Lib\site-packages\starlette\testclient.py:53: DeprecationWarning: The anyio.abc.BlockingPortal alias is deprecated, use anyio.from_thread.BlockingPortal instead.
|
|
_PortalFactoryType = Callable[[], AbstractContextManager[anyio.abc.BlockingPortal]]
|
|
|
|
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
|
|
=========================== short test summary info ===========================
|
|
ERROR tests/test_api.py::test_health_live - sqlalchemy.exc.OperationalError: ...
|
|
ERROR tests/test_api.py::test_login_sets_cookie_and_session - sqlalchemy.exc....
|
|
ERROR tests/test_api.py::test_login_wrong_password_audited - sqlalchemy.exc.O...
|
|
ERROR tests/test_api.py::test_unauthenticated_api_rejected - sqlalchemy.exc.O...
|
|
ERROR tests/test_api.py::test_auditor_cannot_create_clients - sqlalchemy.exc....
|
|
ERROR tests/test_api.py::test_create_client_returns_password_once - sqlalchem...
|
|
ERROR tests/test_api.py::test_disable_enable_delete_client - sqlalchemy.exc.O...
|
|
ERROR tests/test_api.py::test_role_acl_lifecycle - sqlalchemy.exc.Operational...
|
|
ERROR tests/test_api.py::test_group_lifecycle - sqlalchemy.exc.OperationalErr...
|
|
ERROR tests/test_api.py::test_listener_management_and_preview - sqlalchemy.ex...
|
|
ERROR tests/test_api.py::test_audit_events_recorded - sqlalchemy.exc.Operatio...
|
|
5 passed, 2 warnings, 11 errors in 11.71s
|
|
|