Cosmic Ray Report
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Add_Sub, occurrence: 0
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -219,7 +219,7 @@
return RedirectResponse(url="/setup", status_code=307)
return Response(
- content='{"app": "stampbot", "version": "' + APP_VERSION + '", "status": "running"}',
+ content='{"app": "stampbot", "version": "' - APP_VERSION + '", "status": "running"}',
media_type="application/json",
)
........................................................................ [ 34%]
.....................F
=================================== FAILURES ===================================
______________________________ test_root_endpoint ______________________________
test_client = <starlette.testclient.TestClient object at 0x7fa14c8a4190>
def test_root_endpoint(test_client: TestClient):
"""Test root endpoint returns basic info."""
> response = test_client.get("/")
^^^^^^^^^^^^^^^^^^^^
tests/test_main.py:44:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:473: in get
return super().get(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1053: in get
return self.request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:445: in request
return super().request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:825: in request
return self.send(request, auth=auth, follow_redirects=follow_redirects)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:914: in send
response = self._send_handling_auth(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:942: in _send_handling_auth
response = self._send_handling_redirects(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:979: in _send_handling_redirects
response = self._send_single_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1014: in _send_single_request
response = transport.handle_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:348: in handle_request
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:345: in handle_request
portal.call(self.app, scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:334: in call
return cast(T_Retval, self.start_task_soon(func, *args).result())
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:443: in result
return self.__get_result()
^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:395: in __get_result
raise self._exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:259: in _call_func
retval = await retval_or_awaitable
^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1135: in __call__
await super().__call__(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:107: in __call__
await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:186: in __call__
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:164: in __call__
await self.app(scope, receive, _send)
stampbot/main.py:205: in __call__
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:191: in __call__
with recv_stream, send_stream, collapse_excgroups():
^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/contextlib.py:162: in __exit__
self.gen.throw(value)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_utils.py:85: in collapse_excgroups
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:193: in __call__
response = await self.dispatch_func(request, call_next)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:120: in metrics_middleware
response = await call_next(request)
^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:168: in call_next
raise app_exc from app_exc.__cause__ or app_exc.__context__
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:144: in coro
await self.app(scope, receive_or_disconnect, send_no_error)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/exceptions.py:63: in __call__
await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/middleware/asyncexitstack.py:18: in __call__
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:716: in __call__
await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:736: in app
await route.handle(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:290: in handle
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:115: in app
await wrap_app_handling_exceptions(app, request)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:101: in app
response = await f(request)
^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:355: in app
raw_response = await run_endpoint_function(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:243: in run_endpoint_function
return await dependant.call(**values)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
@app.get("/")
async def root() -> Response:
"""Root endpoint - redirects to setup if not configured.
Returns:
Redirect to /setup if unconfigured, otherwise JSON status response.
"""
if not is_configured() and settings.setup_enabled:
return RedirectResponse(url="/setup", status_code=307)
return Response(
> content='{"app": "stampbot", "version": "' - APP_VERSION + '", "status": "running"}',
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
media_type="application/json",
)
E TypeError: unsupported operand type(s) for -: 'str' and 'str'
stampbot/main.py:222: TypeError
=========================== short test summary info ============================
FAILED tests/test_main.py::test_root_endpoint - TypeError: unsupported operand type(s) for -: 'str' and 'str'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 93 passed, 3 deselected in 1.13s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Add_Sub, occurrence: 1
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -219,7 +219,7 @@
return RedirectResponse(url="/setup", status_code=307)
return Response(
- content='{"app": "stampbot", "version": "' + APP_VERSION + '", "status": "running"}',
+ content='{"app": "stampbot", "version": "' + APP_VERSION - '", "status": "running"}',
media_type="application/json",
)
........................................................................ [ 34%]
.....................F
=================================== FAILURES ===================================
______________________________ test_root_endpoint ______________________________
test_client = <starlette.testclient.TestClient object at 0x7f328579c190>
def test_root_endpoint(test_client: TestClient):
"""Test root endpoint returns basic info."""
> response = test_client.get("/")
^^^^^^^^^^^^^^^^^^^^
tests/test_main.py:44:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:473: in get
return super().get(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1053: in get
return self.request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:445: in request
return super().request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:825: in request
return self.send(request, auth=auth, follow_redirects=follow_redirects)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:914: in send
response = self._send_handling_auth(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:942: in _send_handling_auth
response = self._send_handling_redirects(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:979: in _send_handling_redirects
response = self._send_single_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1014: in _send_single_request
response = transport.handle_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:348: in handle_request
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:345: in handle_request
portal.call(self.app, scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:334: in call
return cast(T_Retval, self.start_task_soon(func, *args).result())
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:443: in result
return self.__get_result()
^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:395: in __get_result
raise self._exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:259: in _call_func
retval = await retval_or_awaitable
^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1135: in __call__
await super().__call__(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:107: in __call__
await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:186: in __call__
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:164: in __call__
await self.app(scope, receive, _send)
stampbot/main.py:205: in __call__
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:191: in __call__
with recv_stream, send_stream, collapse_excgroups():
^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/contextlib.py:162: in __exit__
self.gen.throw(value)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_utils.py:85: in collapse_excgroups
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:193: in __call__
response = await self.dispatch_func(request, call_next)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:120: in metrics_middleware
response = await call_next(request)
^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:168: in call_next
raise app_exc from app_exc.__cause__ or app_exc.__context__
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:144: in coro
await self.app(scope, receive_or_disconnect, send_no_error)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/exceptions.py:63: in __call__
await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/middleware/asyncexitstack.py:18: in __call__
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:716: in __call__
await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:736: in app
await route.handle(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:290: in handle
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:115: in app
await wrap_app_handling_exceptions(app, request)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:101: in app
response = await f(request)
^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:355: in app
raw_response = await run_endpoint_function(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:243: in run_endpoint_function
return await dependant.call(**values)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
@app.get("/")
async def root() -> Response:
"""Root endpoint - redirects to setup if not configured.
Returns:
Redirect to /setup if unconfigured, otherwise JSON status response.
"""
if not is_configured() and settings.setup_enabled:
return RedirectResponse(url="/setup", status_code=307)
return Response(
> content='{"app": "stampbot", "version": "' + APP_VERSION - '", "status": "running"}',
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
media_type="application/json",
)
E TypeError: unsupported operand type(s) for -: 'str' and 'str'
stampbot/main.py:222: TypeError
=========================== short test summary info ============================
FAILED tests/test_main.py::test_root_endpoint - TypeError: unsupported operand type(s) for -: 'str' and 'str'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 93 passed, 3 deselected in 1.14s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Add_Mul, occurrence: 0
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -219,7 +219,7 @@
return RedirectResponse(url="/setup", status_code=307)
return Response(
- content='{"app": "stampbot", "version": "' + APP_VERSION + '", "status": "running"}',
+ content='{"app": "stampbot", "version": "' * APP_VERSION + '", "status": "running"}',
media_type="application/json",
)
........................................................................ [ 34%]
.....................F
=================================== FAILURES ===================================
______________________________ test_root_endpoint ______________________________
test_client = <starlette.testclient.TestClient object at 0x7f065459c190>
def test_root_endpoint(test_client: TestClient):
"""Test root endpoint returns basic info."""
> response = test_client.get("/")
^^^^^^^^^^^^^^^^^^^^
tests/test_main.py:44:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:473: in get
return super().get(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1053: in get
return self.request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:445: in request
return super().request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:825: in request
return self.send(request, auth=auth, follow_redirects=follow_redirects)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:914: in send
response = self._send_handling_auth(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:942: in _send_handling_auth
response = self._send_handling_redirects(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:979: in _send_handling_redirects
response = self._send_single_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1014: in _send_single_request
response = transport.handle_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:348: in handle_request
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:345: in handle_request
portal.call(self.app, scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:334: in call
return cast(T_Retval, self.start_task_soon(func, *args).result())
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:443: in result
return self.__get_result()
^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:395: in __get_result
raise self._exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:259: in _call_func
retval = await retval_or_awaitable
^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1135: in __call__
await super().__call__(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:107: in __call__
await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:186: in __call__
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:164: in __call__
await self.app(scope, receive, _send)
stampbot/main.py:205: in __call__
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:191: in __call__
with recv_stream, send_stream, collapse_excgroups():
^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/contextlib.py:162: in __exit__
self.gen.throw(value)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_utils.py:85: in collapse_excgroups
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:193: in __call__
response = await self.dispatch_func(request, call_next)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:120: in metrics_middleware
response = await call_next(request)
^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:168: in call_next
raise app_exc from app_exc.__cause__ or app_exc.__context__
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:144: in coro
await self.app(scope, receive_or_disconnect, send_no_error)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/exceptions.py:63: in __call__
await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/middleware/asyncexitstack.py:18: in __call__
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:716: in __call__
await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:736: in app
await route.handle(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:290: in handle
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:115: in app
await wrap_app_handling_exceptions(app, request)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:101: in app
response = await f(request)
^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:355: in app
raw_response = await run_endpoint_function(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:243: in run_endpoint_function
return await dependant.call(**values)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
@app.get("/")
async def root() -> Response:
"""Root endpoint - redirects to setup if not configured.
Returns:
Redirect to /setup if unconfigured, otherwise JSON status response.
"""
if not is_configured() and settings.setup_enabled:
return RedirectResponse(url="/setup", status_code=307)
return Response(
> content='{"app": "stampbot", "version": "' * APP_VERSION + '", "status": "running"}',
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
media_type="application/json",
)
E TypeError: can't multiply sequence by non-int of type 'str'
stampbot/main.py:222: TypeError
=========================== short test summary info ============================
FAILED tests/test_main.py::test_root_endpoint - TypeError: can't multiply sequence by non-int of type 'str'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 93 passed, 3 deselected in 1.20s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Add_Mul, occurrence: 1
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -219,7 +219,7 @@
return RedirectResponse(url="/setup", status_code=307)
return Response(
- content='{"app": "stampbot", "version": "' + APP_VERSION + '", "status": "running"}',
+ content='{"app": "stampbot", "version": "' + APP_VERSION * '", "status": "running"}',
media_type="application/json",
)
........................................................................ [ 34%]
.....................F
=================================== FAILURES ===================================
______________________________ test_root_endpoint ______________________________
test_client = <starlette.testclient.TestClient object at 0x7f0e59eac190>
def test_root_endpoint(test_client: TestClient):
"""Test root endpoint returns basic info."""
> response = test_client.get("/")
^^^^^^^^^^^^^^^^^^^^
tests/test_main.py:44:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:473: in get
return super().get(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1053: in get
return self.request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:445: in request
return super().request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:825: in request
return self.send(request, auth=auth, follow_redirects=follow_redirects)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:914: in send
response = self._send_handling_auth(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:942: in _send_handling_auth
response = self._send_handling_redirects(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:979: in _send_handling_redirects
response = self._send_single_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1014: in _send_single_request
response = transport.handle_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:348: in handle_request
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:345: in handle_request
portal.call(self.app, scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:334: in call
return cast(T_Retval, self.start_task_soon(func, *args).result())
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:443: in result
return self.__get_result()
^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:395: in __get_result
raise self._exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:259: in _call_func
retval = await retval_or_awaitable
^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1135: in __call__
await super().__call__(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:107: in __call__
await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:186: in __call__
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:164: in __call__
await self.app(scope, receive, _send)
stampbot/main.py:205: in __call__
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:191: in __call__
with recv_stream, send_stream, collapse_excgroups():
^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/contextlib.py:162: in __exit__
self.gen.throw(value)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_utils.py:85: in collapse_excgroups
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:193: in __call__
response = await self.dispatch_func(request, call_next)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:120: in metrics_middleware
response = await call_next(request)
^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:168: in call_next
raise app_exc from app_exc.__cause__ or app_exc.__context__
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:144: in coro
await self.app(scope, receive_or_disconnect, send_no_error)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/exceptions.py:63: in __call__
await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/middleware/asyncexitstack.py:18: in __call__
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:716: in __call__
await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:736: in app
await route.handle(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:290: in handle
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:115: in app
await wrap_app_handling_exceptions(app, request)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:101: in app
response = await f(request)
^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:355: in app
raw_response = await run_endpoint_function(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:243: in run_endpoint_function
return await dependant.call(**values)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
@app.get("/")
async def root() -> Response:
"""Root endpoint - redirects to setup if not configured.
Returns:
Redirect to /setup if unconfigured, otherwise JSON status response.
"""
if not is_configured() and settings.setup_enabled:
return RedirectResponse(url="/setup", status_code=307)
return Response(
> content='{"app": "stampbot", "version": "' + APP_VERSION * '", "status": "running"}',
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
media_type="application/json",
)
E TypeError: can't multiply sequence by non-int of type 'str'
stampbot/main.py:222: TypeError
=========================== short test summary info ============================
FAILED tests/test_main.py::test_root_endpoint - TypeError: can't multiply sequence by non-int of type 'str'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 93 passed, 3 deselected in 1.16s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Add_Div, occurrence: 0
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -219,7 +219,7 @@
return RedirectResponse(url="/setup", status_code=307)
return Response(
- content='{"app": "stampbot", "version": "' + APP_VERSION + '", "status": "running"}',
+ content='{"app": "stampbot", "version": "' / APP_VERSION + '", "status": "running"}',
media_type="application/json",
)
........................................................................ [ 34%]
.....................F
=================================== FAILURES ===================================
______________________________ test_root_endpoint ______________________________
test_client = <starlette.testclient.TestClient object at 0x7f2042194190>
def test_root_endpoint(test_client: TestClient):
"""Test root endpoint returns basic info."""
> response = test_client.get("/")
^^^^^^^^^^^^^^^^^^^^
tests/test_main.py:44:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:473: in get
return super().get(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1053: in get
return self.request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:445: in request
return super().request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:825: in request
return self.send(request, auth=auth, follow_redirects=follow_redirects)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:914: in send
response = self._send_handling_auth(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:942: in _send_handling_auth
response = self._send_handling_redirects(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:979: in _send_handling_redirects
response = self._send_single_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1014: in _send_single_request
response = transport.handle_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:348: in handle_request
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:345: in handle_request
portal.call(self.app, scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:334: in call
return cast(T_Retval, self.start_task_soon(func, *args).result())
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:443: in result
return self.__get_result()
^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:395: in __get_result
raise self._exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:259: in _call_func
retval = await retval_or_awaitable
^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1135: in __call__
await super().__call__(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:107: in __call__
await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:186: in __call__
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:164: in __call__
await self.app(scope, receive, _send)
stampbot/main.py:205: in __call__
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:191: in __call__
with recv_stream, send_stream, collapse_excgroups():
^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/contextlib.py:162: in __exit__
self.gen.throw(value)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_utils.py:85: in collapse_excgroups
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:193: in __call__
response = await self.dispatch_func(request, call_next)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:120: in metrics_middleware
response = await call_next(request)
^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:168: in call_next
raise app_exc from app_exc.__cause__ or app_exc.__context__
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:144: in coro
await self.app(scope, receive_or_disconnect, send_no_error)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/exceptions.py:63: in __call__
await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/middleware/asyncexitstack.py:18: in __call__
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:716: in __call__
await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:736: in app
await route.handle(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:290: in handle
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:115: in app
await wrap_app_handling_exceptions(app, request)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:101: in app
response = await f(request)
^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:355: in app
raw_response = await run_endpoint_function(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:243: in run_endpoint_function
return await dependant.call(**values)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
@app.get("/")
async def root() -> Response:
"""Root endpoint - redirects to setup if not configured.
Returns:
Redirect to /setup if unconfigured, otherwise JSON status response.
"""
if not is_configured() and settings.setup_enabled:
return RedirectResponse(url="/setup", status_code=307)
return Response(
> content='{"app": "stampbot", "version": "' / APP_VERSION + '", "status": "running"}',
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
media_type="application/json",
)
E TypeError: unsupported operand type(s) for /: 'str' and 'str'
stampbot/main.py:222: TypeError
=========================== short test summary info ============================
FAILED tests/test_main.py::test_root_endpoint - TypeError: unsupported operand type(s) for /: 'str' and 'str'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 93 passed, 3 deselected in 1.15s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Add_Div, occurrence: 1
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -219,7 +219,7 @@
return RedirectResponse(url="/setup", status_code=307)
return Response(
- content='{"app": "stampbot", "version": "' + APP_VERSION + '", "status": "running"}',
+ content='{"app": "stampbot", "version": "' + APP_VERSION / '", "status": "running"}',
media_type="application/json",
)
........................................................................ [ 34%]
.....................F
=================================== FAILURES ===================================
______________________________ test_root_endpoint ______________________________
test_client = <starlette.testclient.TestClient object at 0x7f49b889c190>
def test_root_endpoint(test_client: TestClient):
"""Test root endpoint returns basic info."""
> response = test_client.get("/")
^^^^^^^^^^^^^^^^^^^^
tests/test_main.py:44:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:473: in get
return super().get(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1053: in get
return self.request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:445: in request
return super().request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:825: in request
return self.send(request, auth=auth, follow_redirects=follow_redirects)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:914: in send
response = self._send_handling_auth(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:942: in _send_handling_auth
response = self._send_handling_redirects(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:979: in _send_handling_redirects
response = self._send_single_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1014: in _send_single_request
response = transport.handle_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:348: in handle_request
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:345: in handle_request
portal.call(self.app, scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:334: in call
return cast(T_Retval, self.start_task_soon(func, *args).result())
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:443: in result
return self.__get_result()
^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:395: in __get_result
raise self._exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:259: in _call_func
retval = await retval_or_awaitable
^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1135: in __call__
await super().__call__(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:107: in __call__
await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:186: in __call__
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:164: in __call__
await self.app(scope, receive, _send)
stampbot/main.py:205: in __call__
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:191: in __call__
with recv_stream, send_stream, collapse_excgroups():
^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/contextlib.py:162: in __exit__
self.gen.throw(value)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_utils.py:85: in collapse_excgroups
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:193: in __call__
response = await self.dispatch_func(request, call_next)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:120: in metrics_middleware
response = await call_next(request)
^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:168: in call_next
raise app_exc from app_exc.__cause__ or app_exc.__context__
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:144: in coro
await self.app(scope, receive_or_disconnect, send_no_error)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/exceptions.py:63: in __call__
await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/middleware/asyncexitstack.py:18: in __call__
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:716: in __call__
await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:736: in app
await route.handle(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:290: in handle
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:115: in app
await wrap_app_handling_exceptions(app, request)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:101: in app
response = await f(request)
^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:355: in app
raw_response = await run_endpoint_function(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:243: in run_endpoint_function
return await dependant.call(**values)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
@app.get("/")
async def root() -> Response:
"""Root endpoint - redirects to setup if not configured.
Returns:
Redirect to /setup if unconfigured, otherwise JSON status response.
"""
if not is_configured() and settings.setup_enabled:
return RedirectResponse(url="/setup", status_code=307)
return Response(
> content='{"app": "stampbot", "version": "' + APP_VERSION / '", "status": "running"}',
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
media_type="application/json",
)
E TypeError: unsupported operand type(s) for /: 'str' and 'str'
stampbot/main.py:222: TypeError
=========================== short test summary info ============================
FAILED tests/test_main.py::test_root_endpoint - TypeError: unsupported operand type(s) for /: 'str' and 'str'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 93 passed, 3 deselected in 1.17s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Add_FloorDiv, occurrence: 0
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -219,7 +219,7 @@
return RedirectResponse(url="/setup", status_code=307)
return Response(
- content='{"app": "stampbot", "version": "' + APP_VERSION + '", "status": "running"}',
+ content='{"app": "stampbot", "version": "' // APP_VERSION + '", "status": "running"}',
media_type="application/json",
)
........................................................................ [ 34%]
.....................F
=================================== FAILURES ===================================
______________________________ test_root_endpoint ______________________________
test_client = <starlette.testclient.TestClient object at 0x7f5218694190>
def test_root_endpoint(test_client: TestClient):
"""Test root endpoint returns basic info."""
> response = test_client.get("/")
^^^^^^^^^^^^^^^^^^^^
tests/test_main.py:44:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:473: in get
return super().get(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1053: in get
return self.request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:445: in request
return super().request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:825: in request
return self.send(request, auth=auth, follow_redirects=follow_redirects)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:914: in send
response = self._send_handling_auth(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:942: in _send_handling_auth
response = self._send_handling_redirects(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:979: in _send_handling_redirects
response = self._send_single_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1014: in _send_single_request
response = transport.handle_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:348: in handle_request
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:345: in handle_request
portal.call(self.app, scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:334: in call
return cast(T_Retval, self.start_task_soon(func, *args).result())
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:443: in result
return self.__get_result()
^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:395: in __get_result
raise self._exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:259: in _call_func
retval = await retval_or_awaitable
^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1135: in __call__
await super().__call__(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:107: in __call__
await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:186: in __call__
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:164: in __call__
await self.app(scope, receive, _send)
stampbot/main.py:205: in __call__
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:191: in __call__
with recv_stream, send_stream, collapse_excgroups():
^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/contextlib.py:162: in __exit__
self.gen.throw(value)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_utils.py:85: in collapse_excgroups
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:193: in __call__
response = await self.dispatch_func(request, call_next)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:120: in metrics_middleware
response = await call_next(request)
^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:168: in call_next
raise app_exc from app_exc.__cause__ or app_exc.__context__
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:144: in coro
await self.app(scope, receive_or_disconnect, send_no_error)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/exceptions.py:63: in __call__
await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/middleware/asyncexitstack.py:18: in __call__
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:716: in __call__
await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:736: in app
await route.handle(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:290: in handle
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:115: in app
await wrap_app_handling_exceptions(app, request)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:101: in app
response = await f(request)
^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:355: in app
raw_response = await run_endpoint_function(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:243: in run_endpoint_function
return await dependant.call(**values)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
@app.get("/")
async def root() -> Response:
"""Root endpoint - redirects to setup if not configured.
Returns:
Redirect to /setup if unconfigured, otherwise JSON status response.
"""
if not is_configured() and settings.setup_enabled:
return RedirectResponse(url="/setup", status_code=307)
return Response(
> content='{"app": "stampbot", "version": "' // APP_VERSION + '", "status": "running"}',
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
media_type="application/json",
)
E TypeError: unsupported operand type(s) for //: 'str' and 'str'
stampbot/main.py:222: TypeError
=========================== short test summary info ============================
FAILED tests/test_main.py::test_root_endpoint - TypeError: unsupported operand type(s) for //: 'str' and 'str'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 93 passed, 3 deselected in 1.15s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Add_FloorDiv, occurrence: 1
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -219,7 +219,7 @@
return RedirectResponse(url="/setup", status_code=307)
return Response(
- content='{"app": "stampbot", "version": "' + APP_VERSION + '", "status": "running"}',
+ content='{"app": "stampbot", "version": "' + APP_VERSION // '", "status": "running"}',
media_type="application/json",
)
........................................................................ [ 34%]
.....................F
=================================== FAILURES ===================================
______________________________ test_root_endpoint ______________________________
test_client = <starlette.testclient.TestClient object at 0x7ff73d89c190>
def test_root_endpoint(test_client: TestClient):
"""Test root endpoint returns basic info."""
> response = test_client.get("/")
^^^^^^^^^^^^^^^^^^^^
tests/test_main.py:44:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:473: in get
return super().get(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1053: in get
return self.request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:445: in request
return super().request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:825: in request
return self.send(request, auth=auth, follow_redirects=follow_redirects)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:914: in send
response = self._send_handling_auth(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:942: in _send_handling_auth
response = self._send_handling_redirects(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:979: in _send_handling_redirects
response = self._send_single_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1014: in _send_single_request
response = transport.handle_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:348: in handle_request
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:345: in handle_request
portal.call(self.app, scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:334: in call
return cast(T_Retval, self.start_task_soon(func, *args).result())
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:443: in result
return self.__get_result()
^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:395: in __get_result
raise self._exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:259: in _call_func
retval = await retval_or_awaitable
^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1135: in __call__
await super().__call__(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:107: in __call__
await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:186: in __call__
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:164: in __call__
await self.app(scope, receive, _send)
stampbot/main.py:205: in __call__
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:191: in __call__
with recv_stream, send_stream, collapse_excgroups():
^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/contextlib.py:162: in __exit__
self.gen.throw(value)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_utils.py:85: in collapse_excgroups
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:193: in __call__
response = await self.dispatch_func(request, call_next)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:120: in metrics_middleware
response = await call_next(request)
^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:168: in call_next
raise app_exc from app_exc.__cause__ or app_exc.__context__
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:144: in coro
await self.app(scope, receive_or_disconnect, send_no_error)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/exceptions.py:63: in __call__
await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/middleware/asyncexitstack.py:18: in __call__
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:716: in __call__
await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:736: in app
await route.handle(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:290: in handle
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:115: in app
await wrap_app_handling_exceptions(app, request)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:101: in app
response = await f(request)
^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:355: in app
raw_response = await run_endpoint_function(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:243: in run_endpoint_function
return await dependant.call(**values)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
@app.get("/")
async def root() -> Response:
"""Root endpoint - redirects to setup if not configured.
Returns:
Redirect to /setup if unconfigured, otherwise JSON status response.
"""
if not is_configured() and settings.setup_enabled:
return RedirectResponse(url="/setup", status_code=307)
return Response(
> content='{"app": "stampbot", "version": "' + APP_VERSION // '", "status": "running"}',
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
media_type="application/json",
)
E TypeError: unsupported operand type(s) for //: 'str' and 'str'
stampbot/main.py:222: TypeError
=========================== short test summary info ============================
FAILED tests/test_main.py::test_root_endpoint - TypeError: unsupported operand type(s) for //: 'str' and 'str'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 93 passed, 3 deselected in 1.19s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Add_Mod, occurrence: 0
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -219,7 +219,7 @@
return RedirectResponse(url="/setup", status_code=307)
return Response(
- content='{"app": "stampbot", "version": "' + APP_VERSION + '", "status": "running"}',
+ content='{"app": "stampbot", "version": "' % APP_VERSION + '", "status": "running"}',
media_type="application/json",
)
........................................................................ [ 34%]
.....................F
=================================== FAILURES ===================================
______________________________ test_root_endpoint ______________________________
test_client = <starlette.testclient.TestClient object at 0x7fa3fb798190>
def test_root_endpoint(test_client: TestClient):
"""Test root endpoint returns basic info."""
> response = test_client.get("/")
^^^^^^^^^^^^^^^^^^^^
tests/test_main.py:44:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:473: in get
return super().get(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1053: in get
return self.request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:445: in request
return super().request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:825: in request
return self.send(request, auth=auth, follow_redirects=follow_redirects)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:914: in send
response = self._send_handling_auth(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:942: in _send_handling_auth
response = self._send_handling_redirects(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:979: in _send_handling_redirects
response = self._send_single_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1014: in _send_single_request
response = transport.handle_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:348: in handle_request
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:345: in handle_request
portal.call(self.app, scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:334: in call
return cast(T_Retval, self.start_task_soon(func, *args).result())
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:443: in result
return self.__get_result()
^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:395: in __get_result
raise self._exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:259: in _call_func
retval = await retval_or_awaitable
^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1135: in __call__
await super().__call__(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:107: in __call__
await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:186: in __call__
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:164: in __call__
await self.app(scope, receive, _send)
stampbot/main.py:205: in __call__
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:191: in __call__
with recv_stream, send_stream, collapse_excgroups():
^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/contextlib.py:162: in __exit__
self.gen.throw(value)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_utils.py:85: in collapse_excgroups
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:193: in __call__
response = await self.dispatch_func(request, call_next)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:120: in metrics_middleware
response = await call_next(request)
^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:168: in call_next
raise app_exc from app_exc.__cause__ or app_exc.__context__
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:144: in coro
await self.app(scope, receive_or_disconnect, send_no_error)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/exceptions.py:63: in __call__
await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/middleware/asyncexitstack.py:18: in __call__
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:716: in __call__
await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:736: in app
await route.handle(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:290: in handle
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:115: in app
await wrap_app_handling_exceptions(app, request)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:101: in app
response = await f(request)
^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:355: in app
raw_response = await run_endpoint_function(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:243: in run_endpoint_function
return await dependant.call(**values)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
@app.get("/")
async def root() -> Response:
"""Root endpoint - redirects to setup if not configured.
Returns:
Redirect to /setup if unconfigured, otherwise JSON status response.
"""
if not is_configured() and settings.setup_enabled:
return RedirectResponse(url="/setup", status_code=307)
return Response(
> content='{"app": "stampbot", "version": "' % APP_VERSION + '", "status": "running"}',
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
media_type="application/json",
)
E TypeError: not all arguments converted during string formatting
stampbot/main.py:222: TypeError
=========================== short test summary info ============================
FAILED tests/test_main.py::test_root_endpoint - TypeError: not all arguments converted during string formatting
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 93 passed, 3 deselected in 1.18s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Add_Mod, occurrence: 1
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -219,7 +219,7 @@
return RedirectResponse(url="/setup", status_code=307)
return Response(
- content='{"app": "stampbot", "version": "' + APP_VERSION + '", "status": "running"}',
+ content='{"app": "stampbot", "version": "' + APP_VERSION % '", "status": "running"}',
media_type="application/json",
)
........................................................................ [ 34%]
.....................F
=================================== FAILURES ===================================
______________________________ test_root_endpoint ______________________________
test_client = <starlette.testclient.TestClient object at 0x7f2e1da8c190>
def test_root_endpoint(test_client: TestClient):
"""Test root endpoint returns basic info."""
> response = test_client.get("/")
^^^^^^^^^^^^^^^^^^^^
tests/test_main.py:44:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:473: in get
return super().get(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1053: in get
return self.request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:445: in request
return super().request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:825: in request
return self.send(request, auth=auth, follow_redirects=follow_redirects)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:914: in send
response = self._send_handling_auth(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:942: in _send_handling_auth
response = self._send_handling_redirects(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:979: in _send_handling_redirects
response = self._send_single_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1014: in _send_single_request
response = transport.handle_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:348: in handle_request
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:345: in handle_request
portal.call(self.app, scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:334: in call
return cast(T_Retval, self.start_task_soon(func, *args).result())
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:443: in result
return self.__get_result()
^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:395: in __get_result
raise self._exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:259: in _call_func
retval = await retval_or_awaitable
^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1135: in __call__
await super().__call__(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:107: in __call__
await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:186: in __call__
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:164: in __call__
await self.app(scope, receive, _send)
stampbot/main.py:205: in __call__
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:191: in __call__
with recv_stream, send_stream, collapse_excgroups():
^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/contextlib.py:162: in __exit__
self.gen.throw(value)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_utils.py:85: in collapse_excgroups
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:193: in __call__
response = await self.dispatch_func(request, call_next)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:120: in metrics_middleware
response = await call_next(request)
^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:168: in call_next
raise app_exc from app_exc.__cause__ or app_exc.__context__
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:144: in coro
await self.app(scope, receive_or_disconnect, send_no_error)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/exceptions.py:63: in __call__
await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/middleware/asyncexitstack.py:18: in __call__
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:716: in __call__
await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:736: in app
await route.handle(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:290: in handle
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:115: in app
await wrap_app_handling_exceptions(app, request)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:101: in app
response = await f(request)
^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:355: in app
raw_response = await run_endpoint_function(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:243: in run_endpoint_function
return await dependant.call(**values)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
@app.get("/")
async def root() -> Response:
"""Root endpoint - redirects to setup if not configured.
Returns:
Redirect to /setup if unconfigured, otherwise JSON status response.
"""
if not is_configured() and settings.setup_enabled:
return RedirectResponse(url="/setup", status_code=307)
return Response(
> content='{"app": "stampbot", "version": "' + APP_VERSION % '", "status": "running"}',
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
media_type="application/json",
)
E TypeError: not all arguments converted during string formatting
stampbot/main.py:222: TypeError
=========================== short test summary info ============================
FAILED tests/test_main.py::test_root_endpoint - TypeError: not all arguments converted during string formatting
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 93 passed, 3 deselected in 1.16s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Add_Pow, occurrence: 0
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -219,7 +219,7 @@
return RedirectResponse(url="/setup", status_code=307)
return Response(
- content='{"app": "stampbot", "version": "' + APP_VERSION + '", "status": "running"}',
+ content='{"app": "stampbot", "version": "' ** APP_VERSION + '", "status": "running"}',
media_type="application/json",
)
........................................................................ [ 34%]
.....................F
=================================== FAILURES ===================================
______________________________ test_root_endpoint ______________________________
test_client = <starlette.testclient.TestClient object at 0x7fdc8baa4190>
def test_root_endpoint(test_client: TestClient):
"""Test root endpoint returns basic info."""
> response = test_client.get("/")
^^^^^^^^^^^^^^^^^^^^
tests/test_main.py:44:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:473: in get
return super().get(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1053: in get
return self.request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:445: in request
return super().request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:825: in request
return self.send(request, auth=auth, follow_redirects=follow_redirects)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:914: in send
response = self._send_handling_auth(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:942: in _send_handling_auth
response = self._send_handling_redirects(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:979: in _send_handling_redirects
response = self._send_single_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1014: in _send_single_request
response = transport.handle_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:348: in handle_request
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:345: in handle_request
portal.call(self.app, scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:334: in call
return cast(T_Retval, self.start_task_soon(func, *args).result())
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:443: in result
return self.__get_result()
^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:395: in __get_result
raise self._exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:259: in _call_func
retval = await retval_or_awaitable
^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1135: in __call__
await super().__call__(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:107: in __call__
await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:186: in __call__
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:164: in __call__
await self.app(scope, receive, _send)
stampbot/main.py:205: in __call__
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:191: in __call__
with recv_stream, send_stream, collapse_excgroups():
^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/contextlib.py:162: in __exit__
self.gen.throw(value)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_utils.py:85: in collapse_excgroups
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:193: in __call__
response = await self.dispatch_func(request, call_next)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:120: in metrics_middleware
response = await call_next(request)
^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:168: in call_next
raise app_exc from app_exc.__cause__ or app_exc.__context__
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:144: in coro
await self.app(scope, receive_or_disconnect, send_no_error)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/exceptions.py:63: in __call__
await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/middleware/asyncexitstack.py:18: in __call__
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:716: in __call__
await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:736: in app
await route.handle(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:290: in handle
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:115: in app
await wrap_app_handling_exceptions(app, request)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:101: in app
response = await f(request)
^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:355: in app
raw_response = await run_endpoint_function(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:243: in run_endpoint_function
return await dependant.call(**values)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
@app.get("/")
async def root() -> Response:
"""Root endpoint - redirects to setup if not configured.
Returns:
Redirect to /setup if unconfigured, otherwise JSON status response.
"""
if not is_configured() and settings.setup_enabled:
return RedirectResponse(url="/setup", status_code=307)
return Response(
> content='{"app": "stampbot", "version": "' ** APP_VERSION + '", "status": "running"}',
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
media_type="application/json",
)
E TypeError: unsupported operand type(s) for ** or pow(): 'str' and 'str'
stampbot/main.py:222: TypeError
=========================== short test summary info ============================
FAILED tests/test_main.py::test_root_endpoint - TypeError: unsupported operand type(s) for ** or pow(): 'str' and 'str'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 93 passed, 3 deselected in 1.18s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Add_Pow, occurrence: 1
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -219,7 +219,7 @@
return RedirectResponse(url="/setup", status_code=307)
return Response(
- content='{"app": "stampbot", "version": "' + APP_VERSION + '", "status": "running"}',
+ content='{"app": "stampbot", "version": "' + APP_VERSION ** '", "status": "running"}',
media_type="application/json",
)
........................................................................ [ 34%]
.....................F
=================================== FAILURES ===================================
______________________________ test_root_endpoint ______________________________
test_client = <starlette.testclient.TestClient object at 0x7f3190d9c190>
def test_root_endpoint(test_client: TestClient):
"""Test root endpoint returns basic info."""
> response = test_client.get("/")
^^^^^^^^^^^^^^^^^^^^
tests/test_main.py:44:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:473: in get
return super().get(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1053: in get
return self.request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:445: in request
return super().request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:825: in request
return self.send(request, auth=auth, follow_redirects=follow_redirects)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:914: in send
response = self._send_handling_auth(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:942: in _send_handling_auth
response = self._send_handling_redirects(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:979: in _send_handling_redirects
response = self._send_single_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1014: in _send_single_request
response = transport.handle_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:348: in handle_request
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:345: in handle_request
portal.call(self.app, scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:334: in call
return cast(T_Retval, self.start_task_soon(func, *args).result())
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:443: in result
return self.__get_result()
^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:395: in __get_result
raise self._exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:259: in _call_func
retval = await retval_or_awaitable
^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1135: in __call__
await super().__call__(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:107: in __call__
await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:186: in __call__
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:164: in __call__
await self.app(scope, receive, _send)
stampbot/main.py:205: in __call__
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:191: in __call__
with recv_stream, send_stream, collapse_excgroups():
^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/contextlib.py:162: in __exit__
self.gen.throw(value)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_utils.py:85: in collapse_excgroups
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:193: in __call__
response = await self.dispatch_func(request, call_next)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:120: in metrics_middleware
response = await call_next(request)
^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:168: in call_next
raise app_exc from app_exc.__cause__ or app_exc.__context__
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:144: in coro
await self.app(scope, receive_or_disconnect, send_no_error)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/exceptions.py:63: in __call__
await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/middleware/asyncexitstack.py:18: in __call__
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:716: in __call__
await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:736: in app
await route.handle(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:290: in handle
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:115: in app
await wrap_app_handling_exceptions(app, request)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:101: in app
response = await f(request)
^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:355: in app
raw_response = await run_endpoint_function(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:243: in run_endpoint_function
return await dependant.call(**values)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
@app.get("/")
async def root() -> Response:
"""Root endpoint - redirects to setup if not configured.
Returns:
Redirect to /setup if unconfigured, otherwise JSON status response.
"""
if not is_configured() and settings.setup_enabled:
return RedirectResponse(url="/setup", status_code=307)
return Response(
> content='{"app": "stampbot", "version": "' + APP_VERSION ** '", "status": "running"}',
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
media_type="application/json",
)
E TypeError: unsupported operand type(s) for ** or pow(): 'str' and 'str'
stampbot/main.py:222: TypeError
=========================== short test summary info ============================
FAILED tests/test_main.py::test_root_endpoint - TypeError: unsupported operand type(s) for ** or pow(): 'str' and 'str'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 93 passed, 3 deselected in 1.15s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Add_RShift, occurrence: 0
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -219,7 +219,7 @@
return RedirectResponse(url="/setup", status_code=307)
return Response(
- content='{"app": "stampbot", "version": "' + APP_VERSION + '", "status": "running"}',
+ content='{"app": "stampbot", "version": "' >> APP_VERSION + '", "status": "running"}',
media_type="application/json",
)
........................................................................ [ 34%]
.....................F
=================================== FAILURES ===================================
______________________________ test_root_endpoint ______________________________
test_client = <starlette.testclient.TestClient object at 0x7f50ae994190>
def test_root_endpoint(test_client: TestClient):
"""Test root endpoint returns basic info."""
> response = test_client.get("/")
^^^^^^^^^^^^^^^^^^^^
tests/test_main.py:44:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:473: in get
return super().get(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1053: in get
return self.request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:445: in request
return super().request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:825: in request
return self.send(request, auth=auth, follow_redirects=follow_redirects)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:914: in send
response = self._send_handling_auth(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:942: in _send_handling_auth
response = self._send_handling_redirects(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:979: in _send_handling_redirects
response = self._send_single_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1014: in _send_single_request
response = transport.handle_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:348: in handle_request
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:345: in handle_request
portal.call(self.app, scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:334: in call
return cast(T_Retval, self.start_task_soon(func, *args).result())
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:443: in result
return self.__get_result()
^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:395: in __get_result
raise self._exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:259: in _call_func
retval = await retval_or_awaitable
^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1135: in __call__
await super().__call__(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:107: in __call__
await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:186: in __call__
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:164: in __call__
await self.app(scope, receive, _send)
stampbot/main.py:205: in __call__
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:191: in __call__
with recv_stream, send_stream, collapse_excgroups():
^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/contextlib.py:162: in __exit__
self.gen.throw(value)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_utils.py:85: in collapse_excgroups
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:193: in __call__
response = await self.dispatch_func(request, call_next)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:120: in metrics_middleware
response = await call_next(request)
^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:168: in call_next
raise app_exc from app_exc.__cause__ or app_exc.__context__
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:144: in coro
await self.app(scope, receive_or_disconnect, send_no_error)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/exceptions.py:63: in __call__
await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/middleware/asyncexitstack.py:18: in __call__
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:716: in __call__
await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:736: in app
await route.handle(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:290: in handle
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:115: in app
await wrap_app_handling_exceptions(app, request)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:101: in app
response = await f(request)
^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:355: in app
raw_response = await run_endpoint_function(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:243: in run_endpoint_function
return await dependant.call(**values)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
@app.get("/")
async def root() -> Response:
"""Root endpoint - redirects to setup if not configured.
Returns:
Redirect to /setup if unconfigured, otherwise JSON status response.
"""
if not is_configured() and settings.setup_enabled:
return RedirectResponse(url="/setup", status_code=307)
return Response(
> content='{"app": "stampbot", "version": "' >> APP_VERSION + '", "status": "running"}',
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
media_type="application/json",
)
E TypeError: unsupported operand type(s) for >>: 'str' and 'str'
stampbot/main.py:222: TypeError
=========================== short test summary info ============================
FAILED tests/test_main.py::test_root_endpoint - TypeError: unsupported operand type(s) for >>: 'str' and 'str'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 93 passed, 3 deselected in 1.15s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Add_RShift, occurrence: 1
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -219,7 +219,7 @@
return RedirectResponse(url="/setup", status_code=307)
return Response(
- content='{"app": "stampbot", "version": "' + APP_VERSION + '", "status": "running"}',
+ content='{"app": "stampbot", "version": "' + APP_VERSION >> '", "status": "running"}',
media_type="application/json",
)
........................................................................ [ 34%]
.....................F
=================================== FAILURES ===================================
______________________________ test_root_endpoint ______________________________
test_client = <starlette.testclient.TestClient object at 0x7f7a7fc98190>
def test_root_endpoint(test_client: TestClient):
"""Test root endpoint returns basic info."""
> response = test_client.get("/")
^^^^^^^^^^^^^^^^^^^^
tests/test_main.py:44:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:473: in get
return super().get(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1053: in get
return self.request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:445: in request
return super().request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:825: in request
return self.send(request, auth=auth, follow_redirects=follow_redirects)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:914: in send
response = self._send_handling_auth(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:942: in _send_handling_auth
response = self._send_handling_redirects(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:979: in _send_handling_redirects
response = self._send_single_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1014: in _send_single_request
response = transport.handle_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:348: in handle_request
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:345: in handle_request
portal.call(self.app, scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:334: in call
return cast(T_Retval, self.start_task_soon(func, *args).result())
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:443: in result
return self.__get_result()
^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:395: in __get_result
raise self._exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:259: in _call_func
retval = await retval_or_awaitable
^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1135: in __call__
await super().__call__(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:107: in __call__
await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:186: in __call__
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:164: in __call__
await self.app(scope, receive, _send)
stampbot/main.py:205: in __call__
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:191: in __call__
with recv_stream, send_stream, collapse_excgroups():
^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/contextlib.py:162: in __exit__
self.gen.throw(value)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_utils.py:85: in collapse_excgroups
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:193: in __call__
response = await self.dispatch_func(request, call_next)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:120: in metrics_middleware
response = await call_next(request)
^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:168: in call_next
raise app_exc from app_exc.__cause__ or app_exc.__context__
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:144: in coro
await self.app(scope, receive_or_disconnect, send_no_error)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/exceptions.py:63: in __call__
await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/middleware/asyncexitstack.py:18: in __call__
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:716: in __call__
await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:736: in app
await route.handle(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:290: in handle
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:115: in app
await wrap_app_handling_exceptions(app, request)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:101: in app
response = await f(request)
^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:355: in app
raw_response = await run_endpoint_function(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:243: in run_endpoint_function
return await dependant.call(**values)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
@app.get("/")
async def root() -> Response:
"""Root endpoint - redirects to setup if not configured.
Returns:
Redirect to /setup if unconfigured, otherwise JSON status response.
"""
if not is_configured() and settings.setup_enabled:
return RedirectResponse(url="/setup", status_code=307)
return Response(
> content='{"app": "stampbot", "version": "' + APP_VERSION >> '", "status": "running"}',
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
media_type="application/json",
)
E TypeError: unsupported operand type(s) for >>: 'str' and 'str'
stampbot/main.py:222: TypeError
=========================== short test summary info ============================
FAILED tests/test_main.py::test_root_endpoint - TypeError: unsupported operand type(s) for >>: 'str' and 'str'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 93 passed, 3 deselected in 1.21s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Add_LShift, occurrence: 0
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -219,7 +219,7 @@
return RedirectResponse(url="/setup", status_code=307)
return Response(
- content='{"app": "stampbot", "version": "' + APP_VERSION + '", "status": "running"}',
+ content='{"app": "stampbot", "version": "' << APP_VERSION + '", "status": "running"}',
media_type="application/json",
)
........................................................................ [ 34%]
.....................F
=================================== FAILURES ===================================
______________________________ test_root_endpoint ______________________________
test_client = <starlette.testclient.TestClient object at 0x7f06f679c190>
def test_root_endpoint(test_client: TestClient):
"""Test root endpoint returns basic info."""
> response = test_client.get("/")
^^^^^^^^^^^^^^^^^^^^
tests/test_main.py:44:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:473: in get
return super().get(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1053: in get
return self.request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:445: in request
return super().request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:825: in request
return self.send(request, auth=auth, follow_redirects=follow_redirects)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:914: in send
response = self._send_handling_auth(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:942: in _send_handling_auth
response = self._send_handling_redirects(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:979: in _send_handling_redirects
response = self._send_single_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1014: in _send_single_request
response = transport.handle_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:348: in handle_request
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:345: in handle_request
portal.call(self.app, scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:334: in call
return cast(T_Retval, self.start_task_soon(func, *args).result())
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:443: in result
return self.__get_result()
^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:395: in __get_result
raise self._exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:259: in _call_func
retval = await retval_or_awaitable
^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1135: in __call__
await super().__call__(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:107: in __call__
await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:186: in __call__
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:164: in __call__
await self.app(scope, receive, _send)
stampbot/main.py:205: in __call__
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:191: in __call__
with recv_stream, send_stream, collapse_excgroups():
^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/contextlib.py:162: in __exit__
self.gen.throw(value)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_utils.py:85: in collapse_excgroups
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:193: in __call__
response = await self.dispatch_func(request, call_next)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:120: in metrics_middleware
response = await call_next(request)
^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:168: in call_next
raise app_exc from app_exc.__cause__ or app_exc.__context__
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:144: in coro
await self.app(scope, receive_or_disconnect, send_no_error)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/exceptions.py:63: in __call__
await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/middleware/asyncexitstack.py:18: in __call__
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:716: in __call__
await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:736: in app
await route.handle(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:290: in handle
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:115: in app
await wrap_app_handling_exceptions(app, request)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:101: in app
response = await f(request)
^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:355: in app
raw_response = await run_endpoint_function(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:243: in run_endpoint_function
return await dependant.call(**values)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
@app.get("/")
async def root() -> Response:
"""Root endpoint - redirects to setup if not configured.
Returns:
Redirect to /setup if unconfigured, otherwise JSON status response.
"""
if not is_configured() and settings.setup_enabled:
return RedirectResponse(url="/setup", status_code=307)
return Response(
> content='{"app": "stampbot", "version": "' << APP_VERSION + '", "status": "running"}',
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
media_type="application/json",
)
E TypeError: unsupported operand type(s) for <<: 'str' and 'str'
stampbot/main.py:222: TypeError
=========================== short test summary info ============================
FAILED tests/test_main.py::test_root_endpoint - TypeError: unsupported operand type(s) for <<: 'str' and 'str'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 93 passed, 3 deselected in 1.14s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Add_LShift, occurrence: 1
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -219,7 +219,7 @@
return RedirectResponse(url="/setup", status_code=307)
return Response(
- content='{"app": "stampbot", "version": "' + APP_VERSION + '", "status": "running"}',
+ content='{"app": "stampbot", "version": "' + APP_VERSION << '", "status": "running"}',
media_type="application/json",
)
........................................................................ [ 34%]
.....................F
=================================== FAILURES ===================================
______________________________ test_root_endpoint ______________________________
test_client = <starlette.testclient.TestClient object at 0x7fc8bb198190>
def test_root_endpoint(test_client: TestClient):
"""Test root endpoint returns basic info."""
> response = test_client.get("/")
^^^^^^^^^^^^^^^^^^^^
tests/test_main.py:44:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:473: in get
return super().get(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1053: in get
return self.request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:445: in request
return super().request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:825: in request
return self.send(request, auth=auth, follow_redirects=follow_redirects)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:914: in send
response = self._send_handling_auth(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:942: in _send_handling_auth
response = self._send_handling_redirects(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:979: in _send_handling_redirects
response = self._send_single_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1014: in _send_single_request
response = transport.handle_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:348: in handle_request
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:345: in handle_request
portal.call(self.app, scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:334: in call
return cast(T_Retval, self.start_task_soon(func, *args).result())
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:443: in result
return self.__get_result()
^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:395: in __get_result
raise self._exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:259: in _call_func
retval = await retval_or_awaitable
^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1135: in __call__
await super().__call__(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:107: in __call__
await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:186: in __call__
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:164: in __call__
await self.app(scope, receive, _send)
stampbot/main.py:205: in __call__
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:191: in __call__
with recv_stream, send_stream, collapse_excgroups():
^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/contextlib.py:162: in __exit__
self.gen.throw(value)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_utils.py:85: in collapse_excgroups
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:193: in __call__
response = await self.dispatch_func(request, call_next)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:120: in metrics_middleware
response = await call_next(request)
^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:168: in call_next
raise app_exc from app_exc.__cause__ or app_exc.__context__
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:144: in coro
await self.app(scope, receive_or_disconnect, send_no_error)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/exceptions.py:63: in __call__
await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/middleware/asyncexitstack.py:18: in __call__
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:716: in __call__
await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:736: in app
await route.handle(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:290: in handle
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:115: in app
await wrap_app_handling_exceptions(app, request)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:101: in app
response = await f(request)
^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:355: in app
raw_response = await run_endpoint_function(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:243: in run_endpoint_function
return await dependant.call(**values)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
@app.get("/")
async def root() -> Response:
"""Root endpoint - redirects to setup if not configured.
Returns:
Redirect to /setup if unconfigured, otherwise JSON status response.
"""
if not is_configured() and settings.setup_enabled:
return RedirectResponse(url="/setup", status_code=307)
return Response(
> content='{"app": "stampbot", "version": "' + APP_VERSION << '", "status": "running"}',
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
media_type="application/json",
)
E TypeError: unsupported operand type(s) for <<: 'str' and 'str'
stampbot/main.py:222: TypeError
=========================== short test summary info ============================
FAILED tests/test_main.py::test_root_endpoint - TypeError: unsupported operand type(s) for <<: 'str' and 'str'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 93 passed, 3 deselected in 1.15s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Add_BitOr, occurrence: 0
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -219,7 +219,7 @@
return RedirectResponse(url="/setup", status_code=307)
return Response(
- content='{"app": "stampbot", "version": "' + APP_VERSION + '", "status": "running"}',
+ content='{"app": "stampbot", "version": "' | APP_VERSION + '", "status": "running"}',
media_type="application/json",
)
........................................................................ [ 34%]
.....................F
=================================== FAILURES ===================================
______________________________ test_root_endpoint ______________________________
test_client = <starlette.testclient.TestClient object at 0x7f5192074190>
def test_root_endpoint(test_client: TestClient):
"""Test root endpoint returns basic info."""
> response = test_client.get("/")
^^^^^^^^^^^^^^^^^^^^
tests/test_main.py:44:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:473: in get
return super().get(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1053: in get
return self.request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:445: in request
return super().request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:825: in request
return self.send(request, auth=auth, follow_redirects=follow_redirects)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:914: in send
response = self._send_handling_auth(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:942: in _send_handling_auth
response = self._send_handling_redirects(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:979: in _send_handling_redirects
response = self._send_single_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1014: in _send_single_request
response = transport.handle_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:348: in handle_request
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:345: in handle_request
portal.call(self.app, scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:334: in call
return cast(T_Retval, self.start_task_soon(func, *args).result())
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:443: in result
return self.__get_result()
^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:395: in __get_result
raise self._exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:259: in _call_func
retval = await retval_or_awaitable
^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1135: in __call__
await super().__call__(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:107: in __call__
await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:186: in __call__
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:164: in __call__
await self.app(scope, receive, _send)
stampbot/main.py:205: in __call__
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:191: in __call__
with recv_stream, send_stream, collapse_excgroups():
^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/contextlib.py:162: in __exit__
self.gen.throw(value)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_utils.py:85: in collapse_excgroups
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:193: in __call__
response = await self.dispatch_func(request, call_next)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:120: in metrics_middleware
response = await call_next(request)
^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:168: in call_next
raise app_exc from app_exc.__cause__ or app_exc.__context__
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:144: in coro
await self.app(scope, receive_or_disconnect, send_no_error)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/exceptions.py:63: in __call__
await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/middleware/asyncexitstack.py:18: in __call__
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:716: in __call__
await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:736: in app
await route.handle(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:290: in handle
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:115: in app
await wrap_app_handling_exceptions(app, request)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:101: in app
response = await f(request)
^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:355: in app
raw_response = await run_endpoint_function(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:243: in run_endpoint_function
return await dependant.call(**values)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
@app.get("/")
async def root() -> Response:
"""Root endpoint - redirects to setup if not configured.
Returns:
Redirect to /setup if unconfigured, otherwise JSON status response.
"""
if not is_configured() and settings.setup_enabled:
return RedirectResponse(url="/setup", status_code=307)
return Response(
> content='{"app": "stampbot", "version": "' | APP_VERSION + '", "status": "running"}',
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
media_type="application/json",
)
E TypeError: unsupported operand type(s) for |: 'str' and 'str'
stampbot/main.py:222: TypeError
=========================== short test summary info ============================
FAILED tests/test_main.py::test_root_endpoint - TypeError: unsupported operand type(s) for |: 'str' and 'str'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 93 passed, 3 deselected in 1.15s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Add_BitOr, occurrence: 1
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -219,7 +219,7 @@
return RedirectResponse(url="/setup", status_code=307)
return Response(
- content='{"app": "stampbot", "version": "' + APP_VERSION + '", "status": "running"}',
+ content='{"app": "stampbot", "version": "' + APP_VERSION | '", "status": "running"}',
media_type="application/json",
)
........................................................................ [ 34%]
.....................F
=================================== FAILURES ===================================
______________________________ test_root_endpoint ______________________________
test_client = <starlette.testclient.TestClient object at 0x7f3b51984190>
def test_root_endpoint(test_client: TestClient):
"""Test root endpoint returns basic info."""
> response = test_client.get("/")
^^^^^^^^^^^^^^^^^^^^
tests/test_main.py:44:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:473: in get
return super().get(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1053: in get
return self.request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:445: in request
return super().request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:825: in request
return self.send(request, auth=auth, follow_redirects=follow_redirects)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:914: in send
response = self._send_handling_auth(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:942: in _send_handling_auth
response = self._send_handling_redirects(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:979: in _send_handling_redirects
response = self._send_single_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1014: in _send_single_request
response = transport.handle_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:348: in handle_request
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:345: in handle_request
portal.call(self.app, scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:334: in call
return cast(T_Retval, self.start_task_soon(func, *args).result())
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:443: in result
return self.__get_result()
^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:395: in __get_result
raise self._exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:259: in _call_func
retval = await retval_or_awaitable
^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1135: in __call__
await super().__call__(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:107: in __call__
await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:186: in __call__
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:164: in __call__
await self.app(scope, receive, _send)
stampbot/main.py:205: in __call__
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:191: in __call__
with recv_stream, send_stream, collapse_excgroups():
^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/contextlib.py:162: in __exit__
self.gen.throw(value)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_utils.py:85: in collapse_excgroups
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:193: in __call__
response = await self.dispatch_func(request, call_next)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:120: in metrics_middleware
response = await call_next(request)
^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:168: in call_next
raise app_exc from app_exc.__cause__ or app_exc.__context__
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:144: in coro
await self.app(scope, receive_or_disconnect, send_no_error)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/exceptions.py:63: in __call__
await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/middleware/asyncexitstack.py:18: in __call__
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:716: in __call__
await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:736: in app
await route.handle(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:290: in handle
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:115: in app
await wrap_app_handling_exceptions(app, request)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:101: in app
response = await f(request)
^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:355: in app
raw_response = await run_endpoint_function(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:243: in run_endpoint_function
return await dependant.call(**values)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
@app.get("/")
async def root() -> Response:
"""Root endpoint - redirects to setup if not configured.
Returns:
Redirect to /setup if unconfigured, otherwise JSON status response.
"""
if not is_configured() and settings.setup_enabled:
return RedirectResponse(url="/setup", status_code=307)
return Response(
> content='{"app": "stampbot", "version": "' + APP_VERSION | '", "status": "running"}',
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
media_type="application/json",
)
E TypeError: unsupported operand type(s) for |: 'str' and 'str'
stampbot/main.py:222: TypeError
=========================== short test summary info ============================
FAILED tests/test_main.py::test_root_endpoint - TypeError: unsupported operand type(s) for |: 'str' and 'str'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 93 passed, 3 deselected in 1.19s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Add_BitAnd, occurrence: 0
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -219,7 +219,7 @@
return RedirectResponse(url="/setup", status_code=307)
return Response(
- content='{"app": "stampbot", "version": "' + APP_VERSION + '", "status": "running"}',
+ content='{"app": "stampbot", "version": "' & APP_VERSION + '", "status": "running"}',
media_type="application/json",
)
........................................................................ [ 34%]
.....................F
=================================== FAILURES ===================================
______________________________ test_root_endpoint ______________________________
test_client = <starlette.testclient.TestClient object at 0x7fef26ea4190>
def test_root_endpoint(test_client: TestClient):
"""Test root endpoint returns basic info."""
> response = test_client.get("/")
^^^^^^^^^^^^^^^^^^^^
tests/test_main.py:44:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:473: in get
return super().get(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1053: in get
return self.request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:445: in request
return super().request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:825: in request
return self.send(request, auth=auth, follow_redirects=follow_redirects)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:914: in send
response = self._send_handling_auth(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:942: in _send_handling_auth
response = self._send_handling_redirects(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:979: in _send_handling_redirects
response = self._send_single_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1014: in _send_single_request
response = transport.handle_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:348: in handle_request
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:345: in handle_request
portal.call(self.app, scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:334: in call
return cast(T_Retval, self.start_task_soon(func, *args).result())
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:443: in result
return self.__get_result()
^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:395: in __get_result
raise self._exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:259: in _call_func
retval = await retval_or_awaitable
^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1135: in __call__
await super().__call__(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:107: in __call__
await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:186: in __call__
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:164: in __call__
await self.app(scope, receive, _send)
stampbot/main.py:205: in __call__
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:191: in __call__
with recv_stream, send_stream, collapse_excgroups():
^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/contextlib.py:162: in __exit__
self.gen.throw(value)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_utils.py:85: in collapse_excgroups
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:193: in __call__
response = await self.dispatch_func(request, call_next)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:120: in metrics_middleware
response = await call_next(request)
^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:168: in call_next
raise app_exc from app_exc.__cause__ or app_exc.__context__
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:144: in coro
await self.app(scope, receive_or_disconnect, send_no_error)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/exceptions.py:63: in __call__
await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/middleware/asyncexitstack.py:18: in __call__
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:716: in __call__
await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:736: in app
await route.handle(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:290: in handle
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:115: in app
await wrap_app_handling_exceptions(app, request)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:101: in app
response = await f(request)
^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:355: in app
raw_response = await run_endpoint_function(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:243: in run_endpoint_function
return await dependant.call(**values)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
@app.get("/")
async def root() -> Response:
"""Root endpoint - redirects to setup if not configured.
Returns:
Redirect to /setup if unconfigured, otherwise JSON status response.
"""
if not is_configured() and settings.setup_enabled:
return RedirectResponse(url="/setup", status_code=307)
return Response(
> content='{"app": "stampbot", "version": "' & APP_VERSION + '", "status": "running"}',
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
media_type="application/json",
)
E TypeError: unsupported operand type(s) for &: 'str' and 'str'
stampbot/main.py:222: TypeError
=========================== short test summary info ============================
FAILED tests/test_main.py::test_root_endpoint - TypeError: unsupported operand type(s) for &: 'str' and 'str'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 93 passed, 3 deselected in 1.14s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Add_BitAnd, occurrence: 1
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -219,7 +219,7 @@
return RedirectResponse(url="/setup", status_code=307)
return Response(
- content='{"app": "stampbot", "version": "' + APP_VERSION + '", "status": "running"}',
+ content='{"app": "stampbot", "version": "' + APP_VERSION & '", "status": "running"}',
media_type="application/json",
)
........................................................................ [ 34%]
.....................F
=================================== FAILURES ===================================
______________________________ test_root_endpoint ______________________________
test_client = <starlette.testclient.TestClient object at 0x7f960649c190>
def test_root_endpoint(test_client: TestClient):
"""Test root endpoint returns basic info."""
> response = test_client.get("/")
^^^^^^^^^^^^^^^^^^^^
tests/test_main.py:44:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:473: in get
return super().get(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1053: in get
return self.request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:445: in request
return super().request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:825: in request
return self.send(request, auth=auth, follow_redirects=follow_redirects)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:914: in send
response = self._send_handling_auth(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:942: in _send_handling_auth
response = self._send_handling_redirects(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:979: in _send_handling_redirects
response = self._send_single_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1014: in _send_single_request
response = transport.handle_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:348: in handle_request
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:345: in handle_request
portal.call(self.app, scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:334: in call
return cast(T_Retval, self.start_task_soon(func, *args).result())
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:443: in result
return self.__get_result()
^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:395: in __get_result
raise self._exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:259: in _call_func
retval = await retval_or_awaitable
^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1135: in __call__
await super().__call__(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:107: in __call__
await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:186: in __call__
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:164: in __call__
await self.app(scope, receive, _send)
stampbot/main.py:205: in __call__
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:191: in __call__
with recv_stream, send_stream, collapse_excgroups():
^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/contextlib.py:162: in __exit__
self.gen.throw(value)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_utils.py:85: in collapse_excgroups
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:193: in __call__
response = await self.dispatch_func(request, call_next)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:120: in metrics_middleware
response = await call_next(request)
^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:168: in call_next
raise app_exc from app_exc.__cause__ or app_exc.__context__
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:144: in coro
await self.app(scope, receive_or_disconnect, send_no_error)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/exceptions.py:63: in __call__
await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/middleware/asyncexitstack.py:18: in __call__
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:716: in __call__
await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:736: in app
await route.handle(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:290: in handle
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:115: in app
await wrap_app_handling_exceptions(app, request)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:101: in app
response = await f(request)
^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:355: in app
raw_response = await run_endpoint_function(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:243: in run_endpoint_function
return await dependant.call(**values)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
@app.get("/")
async def root() -> Response:
"""Root endpoint - redirects to setup if not configured.
Returns:
Redirect to /setup if unconfigured, otherwise JSON status response.
"""
if not is_configured() and settings.setup_enabled:
return RedirectResponse(url="/setup", status_code=307)
return Response(
> content='{"app": "stampbot", "version": "' + APP_VERSION & '", "status": "running"}',
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
media_type="application/json",
)
E TypeError: unsupported operand type(s) for &: 'str' and 'str'
stampbot/main.py:222: TypeError
=========================== short test summary info ============================
FAILED tests/test_main.py::test_root_endpoint - TypeError: unsupported operand type(s) for &: 'str' and 'str'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 93 passed, 3 deselected in 1.14s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Add_BitXor, occurrence: 0
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -219,7 +219,7 @@
return RedirectResponse(url="/setup", status_code=307)
return Response(
- content='{"app": "stampbot", "version": "' + APP_VERSION + '", "status": "running"}',
+ content='{"app": "stampbot", "version": "' ^ APP_VERSION + '", "status": "running"}',
media_type="application/json",
)
........................................................................ [ 34%]
.....................F
=================================== FAILURES ===================================
______________________________ test_root_endpoint ______________________________
test_client = <starlette.testclient.TestClient object at 0x7f0e5b774190>
def test_root_endpoint(test_client: TestClient):
"""Test root endpoint returns basic info."""
> response = test_client.get("/")
^^^^^^^^^^^^^^^^^^^^
tests/test_main.py:44:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:473: in get
return super().get(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1053: in get
return self.request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:445: in request
return super().request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:825: in request
return self.send(request, auth=auth, follow_redirects=follow_redirects)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:914: in send
response = self._send_handling_auth(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:942: in _send_handling_auth
response = self._send_handling_redirects(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:979: in _send_handling_redirects
response = self._send_single_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1014: in _send_single_request
response = transport.handle_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:348: in handle_request
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:345: in handle_request
portal.call(self.app, scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:334: in call
return cast(T_Retval, self.start_task_soon(func, *args).result())
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:443: in result
return self.__get_result()
^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:395: in __get_result
raise self._exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:259: in _call_func
retval = await retval_or_awaitable
^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1135: in __call__
await super().__call__(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:107: in __call__
await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:186: in __call__
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:164: in __call__
await self.app(scope, receive, _send)
stampbot/main.py:205: in __call__
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:191: in __call__
with recv_stream, send_stream, collapse_excgroups():
^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/contextlib.py:162: in __exit__
self.gen.throw(value)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_utils.py:85: in collapse_excgroups
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:193: in __call__
response = await self.dispatch_func(request, call_next)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:120: in metrics_middleware
response = await call_next(request)
^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:168: in call_next
raise app_exc from app_exc.__cause__ or app_exc.__context__
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:144: in coro
await self.app(scope, receive_or_disconnect, send_no_error)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/exceptions.py:63: in __call__
await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/middleware/asyncexitstack.py:18: in __call__
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:716: in __call__
await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:736: in app
await route.handle(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:290: in handle
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:115: in app
await wrap_app_handling_exceptions(app, request)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:101: in app
response = await f(request)
^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:355: in app
raw_response = await run_endpoint_function(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:243: in run_endpoint_function
return await dependant.call(**values)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
@app.get("/")
async def root() -> Response:
"""Root endpoint - redirects to setup if not configured.
Returns:
Redirect to /setup if unconfigured, otherwise JSON status response.
"""
if not is_configured() and settings.setup_enabled:
return RedirectResponse(url="/setup", status_code=307)
return Response(
> content='{"app": "stampbot", "version": "' ^ APP_VERSION + '", "status": "running"}',
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
media_type="application/json",
)
E TypeError: unsupported operand type(s) for ^: 'str' and 'str'
stampbot/main.py:222: TypeError
=========================== short test summary info ============================
FAILED tests/test_main.py::test_root_endpoint - TypeError: unsupported operand type(s) for ^: 'str' and 'str'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 93 passed, 3 deselected in 1.16s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Add_BitXor, occurrence: 1
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -219,7 +219,7 @@
return RedirectResponse(url="/setup", status_code=307)
return Response(
- content='{"app": "stampbot", "version": "' + APP_VERSION + '", "status": "running"}',
+ content='{"app": "stampbot", "version": "' + APP_VERSION ^ '", "status": "running"}',
media_type="application/json",
)
........................................................................ [ 34%]
.....................F
=================================== FAILURES ===================================
______________________________ test_root_endpoint ______________________________
test_client = <starlette.testclient.TestClient object at 0x7f6f9d49c190>
def test_root_endpoint(test_client: TestClient):
"""Test root endpoint returns basic info."""
> response = test_client.get("/")
^^^^^^^^^^^^^^^^^^^^
tests/test_main.py:44:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:473: in get
return super().get(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1053: in get
return self.request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:445: in request
return super().request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:825: in request
return self.send(request, auth=auth, follow_redirects=follow_redirects)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:914: in send
response = self._send_handling_auth(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:942: in _send_handling_auth
response = self._send_handling_redirects(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:979: in _send_handling_redirects
response = self._send_single_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1014: in _send_single_request
response = transport.handle_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:348: in handle_request
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:345: in handle_request
portal.call(self.app, scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:334: in call
return cast(T_Retval, self.start_task_soon(func, *args).result())
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:443: in result
return self.__get_result()
^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:395: in __get_result
raise self._exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:259: in _call_func
retval = await retval_or_awaitable
^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1135: in __call__
await super().__call__(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:107: in __call__
await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:186: in __call__
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:164: in __call__
await self.app(scope, receive, _send)
stampbot/main.py:205: in __call__
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:191: in __call__
with recv_stream, send_stream, collapse_excgroups():
^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/contextlib.py:162: in __exit__
self.gen.throw(value)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_utils.py:85: in collapse_excgroups
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:193: in __call__
response = await self.dispatch_func(request, call_next)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:120: in metrics_middleware
response = await call_next(request)
^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:168: in call_next
raise app_exc from app_exc.__cause__ or app_exc.__context__
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:144: in coro
await self.app(scope, receive_or_disconnect, send_no_error)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/exceptions.py:63: in __call__
await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/middleware/asyncexitstack.py:18: in __call__
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:716: in __call__
await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:736: in app
await route.handle(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:290: in handle
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:115: in app
await wrap_app_handling_exceptions(app, request)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:101: in app
response = await f(request)
^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:355: in app
raw_response = await run_endpoint_function(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:243: in run_endpoint_function
return await dependant.call(**values)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
@app.get("/")
async def root() -> Response:
"""Root endpoint - redirects to setup if not configured.
Returns:
Redirect to /setup if unconfigured, otherwise JSON status response.
"""
if not is_configured() and settings.setup_enabled:
return RedirectResponse(url="/setup", status_code=307)
return Response(
> content='{"app": "stampbot", "version": "' + APP_VERSION ^ '", "status": "running"}',
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
media_type="application/json",
)
E TypeError: unsupported operand type(s) for ^: 'str' and 'str'
stampbot/main.py:222: TypeError
=========================== short test summary info ============================
FAILED tests/test_main.py::test_root_endpoint - TypeError: unsupported operand type(s) for ^: 'str' and 'str'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 93 passed, 3 deselected in 1.14s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_Add, occurrence: 0
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -119,7 +119,7 @@
try:
response = await call_next(request)
- duration = time.time() - start_time
+ duration = time.time() + start_time
# Track request metrics
http_requests_total.labels(........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.60s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_Add, occurrence: 1
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -313,7 +313,7 @@
try:
start_time = time.time()
result = await webhook_handler.handle_event(x_github_event, payload)
- duration = time.time() - start_time
+ duration = time.time() + start_time
webhook_processing_duration_seconds.labels(event_type=x_github_event or "unknown").observe(
duration........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_Mul, occurrence: 0
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -119,7 +119,7 @@
try:
response = await call_next(request)
- duration = time.time() - start_time
+ duration = time.time() * start_time
# Track request metrics
http_requests_total.labels(........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.62s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_Mul, occurrence: 1
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -313,7 +313,7 @@
try:
start_time = time.time()
result = await webhook_handler.handle_event(x_github_event, payload)
- duration = time.time() - start_time
+ duration = time.time() * start_time
webhook_processing_duration_seconds.labels(event_type=x_github_event or "unknown").observe(
duration........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.62s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_Div, occurrence: 0
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -119,7 +119,7 @@
try:
response = await call_next(request)
- duration = time.time() - start_time
+ duration = time.time() / start_time
# Track request metrics
http_requests_total.labels(........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.62s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_Div, occurrence: 1
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -313,7 +313,7 @@
try:
start_time = time.time()
result = await webhook_handler.handle_event(x_github_event, payload)
- duration = time.time() - start_time
+ duration = time.time() / start_time
webhook_processing_duration_seconds.labels(event_type=x_github_event or "unknown").observe(
duration........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_FloorDiv, occurrence: 0
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -119,7 +119,7 @@
try:
response = await call_next(request)
- duration = time.time() - start_time
+ duration = time.time() // start_time
# Track request metrics
http_requests_total.labels(........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.62s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_FloorDiv, occurrence: 1
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -313,7 +313,7 @@
try:
start_time = time.time()
result = await webhook_handler.handle_event(x_github_event, payload)
- duration = time.time() - start_time
+ duration = time.time() // start_time
webhook_processing_duration_seconds.labels(event_type=x_github_event or "unknown").observe(
duration........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.60s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_Mod, occurrence: 0
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -119,7 +119,7 @@
try:
response = await call_next(request)
- duration = time.time() - start_time
+ duration = time.time() % start_time
# Track request metrics
http_requests_total.labels(........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.67s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_Mod, occurrence: 1
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -313,7 +313,7 @@
try:
start_time = time.time()
result = await webhook_handler.handle_event(x_github_event, payload)
- duration = time.time() - start_time
+ duration = time.time() % start_time
webhook_processing_duration_seconds.labels(event_type=x_github_event or "unknown").observe(
duration........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_Pow, occurrence: 0
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -119,7 +119,7 @@
try:
response = await call_next(request)
- duration = time.time() - start_time
+ duration = time.time() ** start_time
# Track request metrics
http_requests_total.labels(........................................................................ [ 34%]
...................F
=================================== FAILURES ===================================
__________________ test_lifespan_startup_shutdown_configured ___________________
+ Exception Group Traceback (most recent call last):
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_utils.py", line 79, in collapse_excgroups
| yield
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py", line 192, in __call__
| async with anyio.create_task_group() as task_group:
| ~~~~~~~~~~~~~~~~~~~~~~~^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/_backends/_asyncio.py", line 783, in __aexit__
| raise BaseExceptionGroup(
| "unhandled errors in a TaskGroup", self._exceptions
| ) from None
| ExceptionGroup: unhandled errors in a TaskGroup (1 sub-exception)
+-+---------------- 1 ----------------
| Traceback (most recent call last):
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/runner.py", line 353, in from_call
| result: TResult | None = func()
| ~~~~^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/runner.py", line 245, in <lambda>
| lambda: runtest_hook(item=item, **kwds),
| ~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_hooks.py", line 512, in __call__
| return self._hookexec(self.name, self._hookimpls.copy(), kwargs, firstresult)
| ~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_manager.py", line 120, in _hookexec
| return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
| ~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_callers.py", line 167, in _multicall
| raise exception
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_callers.py", line 139, in _multicall
| teardown.throw(exception)
| ~~~~~~~~~~~~~~^^^^^^^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/logging.py", line 850, in pytest_runtest_call
| yield
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_callers.py", line 139, in _multicall
| teardown.throw(exception)
| ~~~~~~~~~~~~~~^^^^^^^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_callers.py", line 53, in run_old_style_hookwrapper
| return result.get_result()
| ~~~~~~~~~~~~~~~~~^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_result.py", line 103, in get_result
| raise exc.with_traceback(tb)
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_callers.py", line 38, in run_old_style_hookwrapper
| res = yield
| ^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_callers.py", line 139, in _multicall
| teardown.throw(exception)
| ~~~~~~~~~~~~~~^^^^^^^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/capture.py", line 900, in pytest_runtest_call
| return (yield)
| ^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_callers.py", line 139, in _multicall
| teardown.throw(exception)
| ~~~~~~~~~~~~~~^^^^^^^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/skipping.py", line 268, in pytest_runtest_call
| return (yield)
| ^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_callers.py", line 121, in _multicall
| res = hook_impl.function(*args)
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/runner.py", line 179, in pytest_runtest_call
| item.runtest()
| ~~~~~~~~~~~~^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/python.py", line 1720, in runtest
| self.ihook.pytest_pyfunc_call(pyfuncitem=self)
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_hooks.py", line 512, in __call__
| return self._hookexec(self.name, self._hookimpls.copy(), kwargs, firstresult)
| ~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_manager.py", line 120, in _hookexec
| return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
| ~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_callers.py", line 167, in _multicall
| raise exception
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_callers.py", line 139, in _multicall
| teardown.throw(exception)
| ~~~~~~~~~~~~~~^^^^^^^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_callers.py", line 53, in run_old_style_hookwrapper
| return result.get_result()
| ~~~~~~~~~~~~~~~~~^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_result.py", line 103, in get_result
| raise exc.with_traceback(tb)
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_callers.py", line 38, in run_old_style_hookwrapper
| res = yield
| ^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_callers.py", line 121, in _multicall
| res = hook_impl.function(*args)
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/python.py", line 166, in pytest_pyfunc_call
| result = testfunction(**testargs)
| File "/home/runner/work/stampbot/stampbot/tests/test_main.py", line 20, in test_lifespan_startup_shutdown_configured
| response = client.get("/health")
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py", line 473, in get
| return super().get(
| ~~~~~~~~~~~^
| url,
| ^^^^
| ...<6 lines>...
| extensions=extensions,
| ^^^^^^^^^^^^^^^^^^^^^^
| )
| ^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py", line 1053, in get
| return self.request(
| ~~~~~~~~~~~~^
| "GET",
| ^^^^^^
| ...<7 lines>...
| extensions=extensions,
| ^^^^^^^^^^^^^^^^^^^^^^
| )
| ^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py", line 445, in request
| return super().request(
| ~~~~~~~~~~~~~~~^
| method,
| ^^^^^^^
| ...<11 lines>...
| extensions=extensions,
| ^^^^^^^^^^^^^^^^^^^^^^
| )
| ^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py", line 825, in request
| return self.send(request, auth=auth, follow_redirects=follow_redirects)
| ~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py", line 914, in send
| response = self._send_handling_auth(
| request,
| ...<2 lines>...
| history=[],
| )
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py", line 942, in _send_handling_auth
| response = self._send_handling_redirects(
| request,
| follow_redirects=follow_redirects,
| history=history,
| )
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py", line 979, in _send_handling_redirects
| response = self._send_single_request(request)
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py", line 1014, in _send_single_request
| response = transport.handle_request(request)
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py", line 348, in handle_request
| raise exc
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py", line 345, in handle_request
| portal.call(self.app, scope, receive, send)
| ~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py", line 334, in call
| return cast(T_Retval, self.start_task_soon(func, *args).result())
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^
| File "/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py", line 450, in result
| return self.__get_result()
| ~~~~~~~~~~~~~~~~~^^
| File "/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py", line 395, in __get_result
| raise self._exception
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py", line 259, in _call_func
| retval = await retval_or_awaitable
| ^^^^^^^^^^^^^^^^^^^^^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py", line 1135, in __call__
| await super().__call__(scope, receive, send)
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py", line 107, in __call__
| await self.middleware_stack(scope, receive, send)
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py", line 186, in __call__
| raise exc
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py", line 164, in __call__
| await self.app(scope, receive, _send)
| File "/home/runner/work/stampbot/stampbot/stampbot/main.py", line 205, in __call__
| await self.app(scope, receive, send)
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py", line 191, in __call__
| with recv_stream, send_stream, collapse_excgroups():
| ~~~~~~~~~~~~~~~~~~^^
| File "/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/contextlib.py", line 162, in __exit__
| self.gen.throw(value)
| ~~~~~~~~~~~~~~^^^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_utils.py", line 85, in collapse_excgroups
| raise exc
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py", line 193, in __call__
| response = await self.dispatch_func(request, call_next)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| File "/home/runner/work/stampbot/stampbot/stampbot/main.py", line 122, in metrics_middleware
| duration = time.time() ** start_time
| ~~~~~~~~~~~~^^~~~~~~~~~~~
| OverflowError: (34, 'Numerical result out of range')
+------------------------------------
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/home/runner/work/stampbot/stampbot/tests/test_main.py", line 20, in test_lifespan_startup_shutdown_configured
response = client.get("/health")
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py", line 473, in get
return super().get(
~~~~~~~~~~~^
url,
^^^^
...<6 lines>...
extensions=extensions,
^^^^^^^^^^^^^^^^^^^^^^
)
^
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py", line 1053, in get
return self.request(
~~~~~~~~~~~~^
"GET",
^^^^^^
...<7 lines>...
extensions=extensions,
^^^^^^^^^^^^^^^^^^^^^^
)
^
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py", line 445, in request
return super().request(
~~~~~~~~~~~~~~~^
method,
^^^^^^^
...<11 lines>...
extensions=extensions,
^^^^^^^^^^^^^^^^^^^^^^
)
^
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py", line 825, in request
return self.send(request, auth=auth, follow_redirects=follow_redirects)
~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py", line 914, in send
response = self._send_handling_auth(
request,
...<2 lines>...
history=[],
)
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py", line 942, in _send_handling_auth
response = self._send_handling_redirects(
request,
follow_redirects=follow_redirects,
history=history,
)
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py", line 979, in _send_handling_redirects
response = self._send_single_request(request)
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py", line 1014, in _send_single_request
response = transport.handle_request(request)
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py", line 348, in handle_request
raise exc
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py", line 345, in handle_request
portal.call(self.app, scope, receive, send)
~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py", line 334, in call
return cast(T_Retval, self.start_task_soon(func, *args).result())
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^
File "/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py", line 450, in result
return self.__get_result()
~~~~~~~~~~~~~~~~~^^
File "/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py", line 395, in __get_result
raise self._exception
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py", line 259, in _call_func
retval = await retval_or_awaitable
^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py", line 1135, in __call__
await super().__call__(scope, receive, send)
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py", line 107, in __call__
await self.middleware_stack(scope, receive, send)
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py", line 186, in __call__
raise exc
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py", line 164, in __call__
await self.app(scope, receive, _send)
File "/home/runner/work/stampbot/stampbot/stampbot/main.py", line 205, in __call__
await self.app(scope, receive, send)
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py", line 191, in __call__
with recv_stream, send_stream, collapse_excgroups():
~~~~~~~~~~~~~~~~~~^^
File "/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/contextlib.py", line 162, in __exit__
self.gen.throw(value)
~~~~~~~~~~~~~~^^^^^^^
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_utils.py", line 85, in collapse_excgroups
raise exc
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py", line 193, in __call__
response = await self.dispatch_func(request, call_next)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/runner/work/stampbot/stampbot/stampbot/main.py", line 122, in metrics_middleware
duration = time.time() ** start_time
~~~~~~~~~~~~^^~~~~~~~~~~~
OverflowError: (34, 'Numerical result out of range')
During handling of the above exception, another exception occurred:
def test_lifespan_startup_shutdown_configured():
"""Test app lifespan startup and shutdown when configured."""
from stampbot.main import app
# Use TestClient as context manager to trigger lifespan events
with TestClient(app) as client:
# App should be running
> response = client.get("/health")
^^^^^^^^^^^^^^^^^^^^^
tests/test_main.py:20:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:473: in get
return super().get(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1053: in get
return self.request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:445: in request
return super().request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:825: in request
return self.send(request, auth=auth, follow_redirects=follow_redirects)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:914: in send
response = self._send_handling_auth(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:942: in _send_handling_auth
response = self._send_handling_redirects(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:979: in _send_handling_redirects
response = self._send_single_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1014: in _send_single_request
response = transport.handle_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:348: in handle_request
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:345: in handle_request
portal.call(self.app, scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:334: in call
return cast(T_Retval, self.start_task_soon(func, *args).result())
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:450: in result
return self.__get_result()
^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:395: in __get_result
raise self._exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:259: in _call_func
retval = await retval_or_awaitable
^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1135: in __call__
await super().__call__(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:107: in __call__
await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:186: in __call__
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:164: in __call__
await self.app(scope, receive, _send)
stampbot/main.py:205: in __call__
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:191: in __call__
with recv_stream, send_stream, collapse_excgroups():
^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/contextlib.py:162: in __exit__
self.gen.throw(value)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_utils.py:85: in collapse_excgroups
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:193: in __call__
response = await self.dispatch_func(request, call_next)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
request = <starlette.middleware.base._CachedRequest object at 0x7fbbe8d830e0>
call_next = <function BaseHTTPMiddleware.__call__.<locals>.call_next at 0x7fbbe8d86820>
@app.middleware("http")
async def metrics_middleware(request: Request, call_next: Any) -> Response:
"""Middleware to track HTTP metrics.
Args:
request: Incoming HTTP request.
call_next: Next middleware or route handler.
Returns:
HTTP response from downstream handler.
"""
method = request.method
endpoint = request.url.path
# Track in-progress requests
http_requests_in_progress.labels(method=method, endpoint=endpoint).inc()
start_time = time.time()
try:
response = await call_next(request)
> duration = time.time() ** start_time
^^^^^^^^^^^^^^^^^^^^^^^^^
E OverflowError: (34, 'Numerical result out of range')
stampbot/main.py:122: OverflowError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T20:05:03.298968Z[0m [[32m[1minfo [0m] [1mOpenTelemetry disabled [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.telemetry (INFO)>[0m [36m_name[0m=[35minfo[0m
[2m2026-05-16T20:05:03.319457Z[0m [[32m[1minfo [0m] [1mStarting stampbot on 0.0.0.0:8000[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.main (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'host': '0.0.0.0', 'port': 8000, 'log_level': 'INFO'}[0m
[2m2026-05-16T20:05:03.319785Z[0m [[32m[1minfo [0m] [1mGitHub App credentials configured successfully[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.main (INFO)>[0m [36m_name[0m=[35minfo[0m
[2m2026-05-16T20:05:03.322081Z[0m [[32m[1minfo [0m] [1mShutting down stampbot [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.main (INFO)>[0m [36m_name[0m=[35minfo[0m
=========================== short test summary info ============================
FAILED tests/test_main.py::test_lifespan_startup_shutdown_configured - OverflowError: (34, 'Numerical result out of range')
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 91 passed, 3 deselected in 1.10s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_Pow, occurrence: 1
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -313,7 +313,7 @@
try:
start_time = time.time()
result = await webhook_handler.handle_event(x_github_event, payload)
- duration = time.time() - start_time
+ duration = time.time() ** start_time
webhook_processing_duration_seconds.labels(event_type=x_github_event or "unknown").observe(
duration........................................................................ [ 34%]
.............................F
=================================== FAILURES ===================================
___________________________ test_webhook_valid_ping ____________________________
test_client = <starlette.testclient.TestClient object at 0x7fd34b5be9c0>
def test_webhook_valid_ping(test_client: TestClient):
"""Test webhook accepts valid ping event."""
import hashlib
import hmac
from stampbot.webhook_handler import webhook_handler
body = json.dumps({"zen": "Design for failure."}).encode()
signature = (
"sha256="
+ hmac.new(
webhook_handler.webhook_secret,
body,
hashlib.sha256,
).hexdigest()
)
response = test_client.post(
"/webhook",
content=body,
headers={
"Content-Type": "application/json",
"X-GitHub-Event": "ping",
"X-Hub-Signature-256": signature,
},
)
> assert response.status_code == 200
E assert 500 == 200
E + where 500 = <Response [500 Internal Server Error]>.status_code
tests/test_main.py:175: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:46:48.859490Z[0m [[32m[1minfo [0m] [1mReceived ping event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mclient_ip[0m=[35mtestclient[0m [36mextra[0m=[35m{'event_type': 'ping', 'action': ''}[0m
[2m2026-05-16T19:46:48.859710Z[0m [[31m[1merror [0m] [1mError handling webhook event: (34, 'Numerical result out of range')[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.main (INFO)>[0m [36m_name[0m=[35merror[0m [36mclient_ip[0m=[35mtestclient[0m [36mextra[0m=[35m{'error': "(34, 'Numerical result out of range')"}[0m
[2m2026-05-16T19:46:48.860502Z[0m [[32m[1minfo [0m] [1mHTTP Request: POST http://testserver/webhook "HTTP/1.1 500 Internal Server Error"[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'ping', 'action': ''}, 'event': 'Received ping event', 'client_ip': 'testclient', 'level': 'info', 'timestamp': '2026-05-16T19:46:48.859490Z'}
ERROR stampbot.main:main.py:326 {'extra': {'error': "(34, 'Numerical result out of range')"}, 'event': "Error handling webhook event: (34, 'Numerical result out of range')", 'client_ip': 'testclient', 'level': 'error', 'timestamp': '2026-05-16T19:46:48.859710Z'}
INFO httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 500 Internal Server Error"
=========================== short test summary info ============================
FAILED tests/test_main.py::test_webhook_valid_ping - assert 500 == 200
+ where 500 = <Response [500 Internal Server Error]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 101 passed, 3 deselected in 0.86s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_RShift, occurrence: 0
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -119,7 +119,7 @@
try:
response = await call_next(request)
- duration = time.time() - start_time
+ duration = time.time() >> start_time
# Track request metrics
http_requests_total.labels(........................................................................ [ 34%]
...................F
=================================== FAILURES ===================================
__________________ test_lifespan_startup_shutdown_configured ___________________
+ Exception Group Traceback (most recent call last):
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_utils.py", line 79, in collapse_excgroups
| yield
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py", line 192, in __call__
| async with anyio.create_task_group() as task_group:
| ~~~~~~~~~~~~~~~~~~~~~~~^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/_backends/_asyncio.py", line 783, in __aexit__
| raise BaseExceptionGroup(
| "unhandled errors in a TaskGroup", self._exceptions
| ) from None
| ExceptionGroup: unhandled errors in a TaskGroup (1 sub-exception)
+-+---------------- 1 ----------------
| Traceback (most recent call last):
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/runner.py", line 353, in from_call
| result: TResult | None = func()
| ~~~~^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/runner.py", line 245, in <lambda>
| lambda: runtest_hook(item=item, **kwds),
| ~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_hooks.py", line 512, in __call__
| return self._hookexec(self.name, self._hookimpls.copy(), kwargs, firstresult)
| ~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_manager.py", line 120, in _hookexec
| return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
| ~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_callers.py", line 167, in _multicall
| raise exception
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_callers.py", line 139, in _multicall
| teardown.throw(exception)
| ~~~~~~~~~~~~~~^^^^^^^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/logging.py", line 850, in pytest_runtest_call
| yield
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_callers.py", line 139, in _multicall
| teardown.throw(exception)
| ~~~~~~~~~~~~~~^^^^^^^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_callers.py", line 53, in run_old_style_hookwrapper
| return result.get_result()
| ~~~~~~~~~~~~~~~~~^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_result.py", line 103, in get_result
| raise exc.with_traceback(tb)
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_callers.py", line 38, in run_old_style_hookwrapper
| res = yield
| ^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_callers.py", line 139, in _multicall
| teardown.throw(exception)
| ~~~~~~~~~~~~~~^^^^^^^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/capture.py", line 900, in pytest_runtest_call
| return (yield)
| ^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_callers.py", line 139, in _multicall
| teardown.throw(exception)
| ~~~~~~~~~~~~~~^^^^^^^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/skipping.py", line 268, in pytest_runtest_call
| return (yield)
| ^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_callers.py", line 121, in _multicall
| res = hook_impl.function(*args)
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/runner.py", line 179, in pytest_runtest_call
| item.runtest()
| ~~~~~~~~~~~~^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/python.py", line 1720, in runtest
| self.ihook.pytest_pyfunc_call(pyfuncitem=self)
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_hooks.py", line 512, in __call__
| return self._hookexec(self.name, self._hookimpls.copy(), kwargs, firstresult)
| ~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_manager.py", line 120, in _hookexec
| return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
| ~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_callers.py", line 167, in _multicall
| raise exception
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_callers.py", line 139, in _multicall
| teardown.throw(exception)
| ~~~~~~~~~~~~~~^^^^^^^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_callers.py", line 53, in run_old_style_hookwrapper
| return result.get_result()
| ~~~~~~~~~~~~~~~~~^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_result.py", line 103, in get_result
| raise exc.with_traceback(tb)
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_callers.py", line 38, in run_old_style_hookwrapper
| res = yield
| ^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_callers.py", line 121, in _multicall
| res = hook_impl.function(*args)
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/python.py", line 166, in pytest_pyfunc_call
| result = testfunction(**testargs)
| File "/home/runner/work/stampbot/stampbot/tests/test_main.py", line 20, in test_lifespan_startup_shutdown_configured
| response = client.get("/health")
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py", line 473, in get
| return super().get(
| ~~~~~~~~~~~^
| url,
| ^^^^
| ...<6 lines>...
| extensions=extensions,
| ^^^^^^^^^^^^^^^^^^^^^^
| )
| ^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py", line 1053, in get
| return self.request(
| ~~~~~~~~~~~~^
| "GET",
| ^^^^^^
| ...<7 lines>...
| extensions=extensions,
| ^^^^^^^^^^^^^^^^^^^^^^
| )
| ^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py", line 445, in request
| return super().request(
| ~~~~~~~~~~~~~~~^
| method,
| ^^^^^^^
| ...<11 lines>...
| extensions=extensions,
| ^^^^^^^^^^^^^^^^^^^^^^
| )
| ^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py", line 825, in request
| return self.send(request, auth=auth, follow_redirects=follow_redirects)
| ~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py", line 914, in send
| response = self._send_handling_auth(
| request,
| ...<2 lines>...
| history=[],
| )
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py", line 942, in _send_handling_auth
| response = self._send_handling_redirects(
| request,
| follow_redirects=follow_redirects,
| history=history,
| )
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py", line 979, in _send_handling_redirects
| response = self._send_single_request(request)
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py", line 1014, in _send_single_request
| response = transport.handle_request(request)
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py", line 348, in handle_request
| raise exc
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py", line 345, in handle_request
| portal.call(self.app, scope, receive, send)
| ~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py", line 334, in call
| return cast(T_Retval, self.start_task_soon(func, *args).result())
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^
| File "/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py", line 450, in result
| return self.__get_result()
| ~~~~~~~~~~~~~~~~~^^
| File "/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py", line 395, in __get_result
| raise self._exception
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py", line 259, in _call_func
| retval = await retval_or_awaitable
| ^^^^^^^^^^^^^^^^^^^^^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py", line 1135, in __call__
| await super().__call__(scope, receive, send)
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py", line 107, in __call__
| await self.middleware_stack(scope, receive, send)
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py", line 186, in __call__
| raise exc
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py", line 164, in __call__
| await self.app(scope, receive, _send)
| File "/home/runner/work/stampbot/stampbot/stampbot/main.py", line 205, in __call__
| await self.app(scope, receive, send)
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py", line 191, in __call__
| with recv_stream, send_stream, collapse_excgroups():
| ~~~~~~~~~~~~~~~~~~^^
| File "/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/contextlib.py", line 162, in __exit__
| self.gen.throw(value)
| ~~~~~~~~~~~~~~^^^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_utils.py", line 85, in collapse_excgroups
| raise exc
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py", line 193, in __call__
| response = await self.dispatch_func(request, call_next)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| File "/home/runner/work/stampbot/stampbot/stampbot/main.py", line 122, in metrics_middleware
| duration = time.time() >> start_time
| ~~~~~~~~~~~~^^~~~~~~~~~~~
| TypeError: unsupported operand type(s) for >>: 'float' and 'float'
+------------------------------------
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/home/runner/work/stampbot/stampbot/tests/test_main.py", line 20, in test_lifespan_startup_shutdown_configured
response = client.get("/health")
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py", line 473, in get
return super().get(
~~~~~~~~~~~^
url,
^^^^
...<6 lines>...
extensions=extensions,
^^^^^^^^^^^^^^^^^^^^^^
)
^
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py", line 1053, in get
return self.request(
~~~~~~~~~~~~^
"GET",
^^^^^^
...<7 lines>...
extensions=extensions,
^^^^^^^^^^^^^^^^^^^^^^
)
^
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py", line 445, in request
return super().request(
~~~~~~~~~~~~~~~^
method,
^^^^^^^
...<11 lines>...
extensions=extensions,
^^^^^^^^^^^^^^^^^^^^^^
)
^
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py", line 825, in request
return self.send(request, auth=auth, follow_redirects=follow_redirects)
~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py", line 914, in send
response = self._send_handling_auth(
request,
...<2 lines>...
history=[],
)
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py", line 942, in _send_handling_auth
response = self._send_handling_redirects(
request,
follow_redirects=follow_redirects,
history=history,
)
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py", line 979, in _send_handling_redirects
response = self._send_single_request(request)
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py", line 1014, in _send_single_request
response = transport.handle_request(request)
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py", line 348, in handle_request
raise exc
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py", line 345, in handle_request
portal.call(self.app, scope, receive, send)
~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py", line 334, in call
return cast(T_Retval, self.start_task_soon(func, *args).result())
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^
File "/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py", line 450, in result
return self.__get_result()
~~~~~~~~~~~~~~~~~^^
File "/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py", line 395, in __get_result
raise self._exception
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py", line 259, in _call_func
retval = await retval_or_awaitable
^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py", line 1135, in __call__
await super().__call__(scope, receive, send)
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py", line 107, in __call__
await self.middleware_stack(scope, receive, send)
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py", line 186, in __call__
raise exc
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py", line 164, in __call__
await self.app(scope, receive, _send)
File "/home/runner/work/stampbot/stampbot/stampbot/main.py", line 205, in __call__
await self.app(scope, receive, send)
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py", line 191, in __call__
with recv_stream, send_stream, collapse_excgroups():
~~~~~~~~~~~~~~~~~~^^
File "/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/contextlib.py", line 162, in __exit__
self.gen.throw(value)
~~~~~~~~~~~~~~^^^^^^^
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_utils.py", line 85, in collapse_excgroups
raise exc
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py", line 193, in __call__
response = await self.dispatch_func(request, call_next)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/runner/work/stampbot/stampbot/stampbot/main.py", line 122, in metrics_middleware
duration = time.time() >> start_time
~~~~~~~~~~~~^^~~~~~~~~~~~
TypeError: unsupported operand type(s) for >>: 'float' and 'float'
During handling of the above exception, another exception occurred:
def test_lifespan_startup_shutdown_configured():
"""Test app lifespan startup and shutdown when configured."""
from stampbot.main import app
# Use TestClient as context manager to trigger lifespan events
with TestClient(app) as client:
# App should be running
> response = client.get("/health")
^^^^^^^^^^^^^^^^^^^^^
tests/test_main.py:20:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:473: in get
return super().get(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1053: in get
return self.request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:445: in request
return super().request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:825: in request
return self.send(request, auth=auth, follow_redirects=follow_redirects)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:914: in send
response = self._send_handling_auth(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:942: in _send_handling_auth
response = self._send_handling_redirects(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:979: in _send_handling_redirects
response = self._send_single_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1014: in _send_single_request
response = transport.handle_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:348: in handle_request
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:345: in handle_request
portal.call(self.app, scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:334: in call
return cast(T_Retval, self.start_task_soon(func, *args).result())
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:450: in result
return self.__get_result()
^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:395: in __get_result
raise self._exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:259: in _call_func
retval = await retval_or_awaitable
^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1135: in __call__
await super().__call__(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:107: in __call__
await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:186: in __call__
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:164: in __call__
await self.app(scope, receive, _send)
stampbot/main.py:205: in __call__
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:191: in __call__
with recv_stream, send_stream, collapse_excgroups():
^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/contextlib.py:162: in __exit__
self.gen.throw(value)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_utils.py:85: in collapse_excgroups
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:193: in __call__
response = await self.dispatch_func(request, call_next)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
request = <starlette.middleware.base._CachedRequest object at 0x7f022ac7f0e0>
call_next = <function BaseHTTPMiddleware.__call__.<locals>.call_next at 0x7f022ac82820>
@app.middleware("http")
async def metrics_middleware(request: Request, call_next: Any) -> Response:
"""Middleware to track HTTP metrics.
Args:
request: Incoming HTTP request.
call_next: Next middleware or route handler.
Returns:
HTTP response from downstream handler.
"""
method = request.method
endpoint = request.url.path
# Track in-progress requests
http_requests_in_progress.labels(method=method, endpoint=endpoint).inc()
start_time = time.time()
try:
response = await call_next(request)
> duration = time.time() >> start_time
^^^^^^^^^^^^^^^^^^^^^^^^^
E TypeError: unsupported operand type(s) for >>: 'float' and 'float'
stampbot/main.py:122: TypeError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:31:41.616338Z[0m [[32m[1minfo [0m] [1mOpenTelemetry disabled [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.telemetry (INFO)>[0m [36m_name[0m=[35minfo[0m
[2m2026-05-16T19:31:41.635051Z[0m [[32m[1minfo [0m] [1mStarting stampbot on 0.0.0.0:8000[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.main (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'host': '0.0.0.0', 'port': 8000, 'log_level': 'INFO'}[0m
[2m2026-05-16T19:31:41.635366Z[0m [[32m[1minfo [0m] [1mGitHub App credentials configured successfully[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.main (INFO)>[0m [36m_name[0m=[35minfo[0m
[2m2026-05-16T19:31:41.637794Z[0m [[32m[1minfo [0m] [1mShutting down stampbot [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.main (INFO)>[0m [36m_name[0m=[35minfo[0m
=========================== short test summary info ============================
FAILED tests/test_main.py::test_lifespan_startup_shutdown_configured - TypeError: unsupported operand type(s) for >>: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 91 passed, 3 deselected in 1.05s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_RShift, occurrence: 1
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -313,7 +313,7 @@
try:
start_time = time.time()
result = await webhook_handler.handle_event(x_github_event, payload)
- duration = time.time() - start_time
+ duration = time.time() >> start_time
webhook_processing_duration_seconds.labels(event_type=x_github_event or "unknown").observe(
duration........................................................................ [ 34%]
.............................F
=================================== FAILURES ===================================
___________________________ test_webhook_valid_ping ____________________________
test_client = <starlette.testclient.TestClient object at 0x7f474e0aa9c0>
def test_webhook_valid_ping(test_client: TestClient):
"""Test webhook accepts valid ping event."""
import hashlib
import hmac
from stampbot.webhook_handler import webhook_handler
body = json.dumps({"zen": "Design for failure."}).encode()
signature = (
"sha256="
+ hmac.new(
webhook_handler.webhook_secret,
body,
hashlib.sha256,
).hexdigest()
)
response = test_client.post(
"/webhook",
content=body,
headers={
"Content-Type": "application/json",
"X-GitHub-Event": "ping",
"X-Hub-Signature-256": signature,
},
)
> assert response.status_code == 200
E assert 500 == 200
E + where 500 = <Response [500 Internal Server Error]>.status_code
tests/test_main.py:175: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:25:04.081728Z[0m [[32m[1minfo [0m] [1mReceived ping event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mclient_ip[0m=[35mtestclient[0m [36mextra[0m=[35m{'event_type': 'ping', 'action': ''}[0m
[2m2026-05-16T19:25:04.081925Z[0m [[31m[1merror [0m] [1mError handling webhook event: unsupported operand type(s) for >>: 'float' and 'float'[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.main (INFO)>[0m [36m_name[0m=[35merror[0m [36mclient_ip[0m=[35mtestclient[0m [36mextra[0m=[35m{'error': "unsupported operand type(s) for >>: 'float' and 'float'"}[0m
[2m2026-05-16T19:25:04.082705Z[0m [[32m[1minfo [0m] [1mHTTP Request: POST http://testserver/webhook "HTTP/1.1 500 Internal Server Error"[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'ping', 'action': ''}, 'event': 'Received ping event', 'client_ip': 'testclient', 'level': 'info', 'timestamp': '2026-05-16T19:25:04.081728Z'}
ERROR stampbot.main:main.py:326 {'extra': {'error': "unsupported operand type(s) for >>: 'float' and 'float'"}, 'event': "Error handling webhook event: unsupported operand type(s) for >>: 'float' and 'float'", 'client_ip': 'testclient', 'level': 'error', 'timestamp': '2026-05-16T19:25:04.081925Z'}
INFO httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 500 Internal Server Error"
=========================== short test summary info ============================
FAILED tests/test_main.py::test_webhook_valid_ping - assert 500 == 200
+ where 500 = <Response [500 Internal Server Error]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 101 passed, 3 deselected in 0.85s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_LShift, occurrence: 0
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -119,7 +119,7 @@
try:
response = await call_next(request)
- duration = time.time() - start_time
+ duration = time.time() << start_time
# Track request metrics
http_requests_total.labels(........................................................................ [ 34%]
...................F
=================================== FAILURES ===================================
__________________ test_lifespan_startup_shutdown_configured ___________________
+ Exception Group Traceback (most recent call last):
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_utils.py", line 79, in collapse_excgroups
| yield
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py", line 192, in __call__
| async with anyio.create_task_group() as task_group:
| ~~~~~~~~~~~~~~~~~~~~~~~^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/_backends/_asyncio.py", line 783, in __aexit__
| raise BaseExceptionGroup(
| "unhandled errors in a TaskGroup", self._exceptions
| ) from None
| ExceptionGroup: unhandled errors in a TaskGroup (1 sub-exception)
+-+---------------- 1 ----------------
| Traceback (most recent call last):
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/runner.py", line 353, in from_call
| result: TResult | None = func()
| ~~~~^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/runner.py", line 245, in <lambda>
| lambda: runtest_hook(item=item, **kwds),
| ~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_hooks.py", line 512, in __call__
| return self._hookexec(self.name, self._hookimpls.copy(), kwargs, firstresult)
| ~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_manager.py", line 120, in _hookexec
| return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
| ~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_callers.py", line 167, in _multicall
| raise exception
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_callers.py", line 139, in _multicall
| teardown.throw(exception)
| ~~~~~~~~~~~~~~^^^^^^^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/logging.py", line 850, in pytest_runtest_call
| yield
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_callers.py", line 139, in _multicall
| teardown.throw(exception)
| ~~~~~~~~~~~~~~^^^^^^^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_callers.py", line 53, in run_old_style_hookwrapper
| return result.get_result()
| ~~~~~~~~~~~~~~~~~^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_result.py", line 103, in get_result
| raise exc.with_traceback(tb)
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_callers.py", line 38, in run_old_style_hookwrapper
| res = yield
| ^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_callers.py", line 139, in _multicall
| teardown.throw(exception)
| ~~~~~~~~~~~~~~^^^^^^^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/capture.py", line 900, in pytest_runtest_call
| return (yield)
| ^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_callers.py", line 139, in _multicall
| teardown.throw(exception)
| ~~~~~~~~~~~~~~^^^^^^^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/skipping.py", line 268, in pytest_runtest_call
| return (yield)
| ^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_callers.py", line 121, in _multicall
| res = hook_impl.function(*args)
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/runner.py", line 179, in pytest_runtest_call
| item.runtest()
| ~~~~~~~~~~~~^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/python.py", line 1720, in runtest
| self.ihook.pytest_pyfunc_call(pyfuncitem=self)
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_hooks.py", line 512, in __call__
| return self._hookexec(self.name, self._hookimpls.copy(), kwargs, firstresult)
| ~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_manager.py", line 120, in _hookexec
| return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
| ~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_callers.py", line 167, in _multicall
| raise exception
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_callers.py", line 139, in _multicall
| teardown.throw(exception)
| ~~~~~~~~~~~~~~^^^^^^^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_callers.py", line 53, in run_old_style_hookwrapper
| return result.get_result()
| ~~~~~~~~~~~~~~~~~^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_result.py", line 103, in get_result
| raise exc.with_traceback(tb)
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_callers.py", line 38, in run_old_style_hookwrapper
| res = yield
| ^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_callers.py", line 121, in _multicall
| res = hook_impl.function(*args)
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/python.py", line 166, in pytest_pyfunc_call
| result = testfunction(**testargs)
| File "/home/runner/work/stampbot/stampbot/tests/test_main.py", line 20, in test_lifespan_startup_shutdown_configured
| response = client.get("/health")
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py", line 473, in get
| return super().get(
| ~~~~~~~~~~~^
| url,
| ^^^^
| ...<6 lines>...
| extensions=extensions,
| ^^^^^^^^^^^^^^^^^^^^^^
| )
| ^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py", line 1053, in get
| return self.request(
| ~~~~~~~~~~~~^
| "GET",
| ^^^^^^
| ...<7 lines>...
| extensions=extensions,
| ^^^^^^^^^^^^^^^^^^^^^^
| )
| ^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py", line 445, in request
| return super().request(
| ~~~~~~~~~~~~~~~^
| method,
| ^^^^^^^
| ...<11 lines>...
| extensions=extensions,
| ^^^^^^^^^^^^^^^^^^^^^^
| )
| ^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py", line 825, in request
| return self.send(request, auth=auth, follow_redirects=follow_redirects)
| ~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py", line 914, in send
| response = self._send_handling_auth(
| request,
| ...<2 lines>...
| history=[],
| )
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py", line 942, in _send_handling_auth
| response = self._send_handling_redirects(
| request,
| follow_redirects=follow_redirects,
| history=history,
| )
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py", line 979, in _send_handling_redirects
| response = self._send_single_request(request)
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py", line 1014, in _send_single_request
| response = transport.handle_request(request)
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py", line 348, in handle_request
| raise exc
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py", line 345, in handle_request
| portal.call(self.app, scope, receive, send)
| ~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py", line 334, in call
| return cast(T_Retval, self.start_task_soon(func, *args).result())
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^
| File "/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py", line 450, in result
| return self.__get_result()
| ~~~~~~~~~~~~~~~~~^^
| File "/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py", line 395, in __get_result
| raise self._exception
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py", line 259, in _call_func
| retval = await retval_or_awaitable
| ^^^^^^^^^^^^^^^^^^^^^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py", line 1135, in __call__
| await super().__call__(scope, receive, send)
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py", line 107, in __call__
| await self.middleware_stack(scope, receive, send)
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py", line 186, in __call__
| raise exc
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py", line 164, in __call__
| await self.app(scope, receive, _send)
| File "/home/runner/work/stampbot/stampbot/stampbot/main.py", line 205, in __call__
| await self.app(scope, receive, send)
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py", line 191, in __call__
| with recv_stream, send_stream, collapse_excgroups():
| ~~~~~~~~~~~~~~~~~~^^
| File "/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/contextlib.py", line 162, in __exit__
| self.gen.throw(value)
| ~~~~~~~~~~~~~~^^^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_utils.py", line 85, in collapse_excgroups
| raise exc
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py", line 193, in __call__
| response = await self.dispatch_func(request, call_next)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| File "/home/runner/work/stampbot/stampbot/stampbot/main.py", line 122, in metrics_middleware
| duration = time.time() << start_time
| ~~~~~~~~~~~~^^~~~~~~~~~~~
| TypeError: unsupported operand type(s) for <<: 'float' and 'float'
+------------------------------------
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/home/runner/work/stampbot/stampbot/tests/test_main.py", line 20, in test_lifespan_startup_shutdown_configured
response = client.get("/health")
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py", line 473, in get
return super().get(
~~~~~~~~~~~^
url,
^^^^
...<6 lines>...
extensions=extensions,
^^^^^^^^^^^^^^^^^^^^^^
)
^
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py", line 1053, in get
return self.request(
~~~~~~~~~~~~^
"GET",
^^^^^^
...<7 lines>...
extensions=extensions,
^^^^^^^^^^^^^^^^^^^^^^
)
^
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py", line 445, in request
return super().request(
~~~~~~~~~~~~~~~^
method,
^^^^^^^
...<11 lines>...
extensions=extensions,
^^^^^^^^^^^^^^^^^^^^^^
)
^
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py", line 825, in request
return self.send(request, auth=auth, follow_redirects=follow_redirects)
~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py", line 914, in send
response = self._send_handling_auth(
request,
...<2 lines>...
history=[],
)
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py", line 942, in _send_handling_auth
response = self._send_handling_redirects(
request,
follow_redirects=follow_redirects,
history=history,
)
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py", line 979, in _send_handling_redirects
response = self._send_single_request(request)
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py", line 1014, in _send_single_request
response = transport.handle_request(request)
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py", line 348, in handle_request
raise exc
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py", line 345, in handle_request
portal.call(self.app, scope, receive, send)
~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py", line 334, in call
return cast(T_Retval, self.start_task_soon(func, *args).result())
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^
File "/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py", line 450, in result
return self.__get_result()
~~~~~~~~~~~~~~~~~^^
File "/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py", line 395, in __get_result
raise self._exception
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py", line 259, in _call_func
retval = await retval_or_awaitable
^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py", line 1135, in __call__
await super().__call__(scope, receive, send)
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py", line 107, in __call__
await self.middleware_stack(scope, receive, send)
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py", line 186, in __call__
raise exc
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py", line 164, in __call__
await self.app(scope, receive, _send)
File "/home/runner/work/stampbot/stampbot/stampbot/main.py", line 205, in __call__
await self.app(scope, receive, send)
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py", line 191, in __call__
with recv_stream, send_stream, collapse_excgroups():
~~~~~~~~~~~~~~~~~~^^
File "/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/contextlib.py", line 162, in __exit__
self.gen.throw(value)
~~~~~~~~~~~~~~^^^^^^^
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_utils.py", line 85, in collapse_excgroups
raise exc
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py", line 193, in __call__
response = await self.dispatch_func(request, call_next)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/runner/work/stampbot/stampbot/stampbot/main.py", line 122, in metrics_middleware
duration = time.time() << start_time
~~~~~~~~~~~~^^~~~~~~~~~~~
TypeError: unsupported operand type(s) for <<: 'float' and 'float'
During handling of the above exception, another exception occurred:
def test_lifespan_startup_shutdown_configured():
"""Test app lifespan startup and shutdown when configured."""
from stampbot.main import app
# Use TestClient as context manager to trigger lifespan events
with TestClient(app) as client:
# App should be running
> response = client.get("/health")
^^^^^^^^^^^^^^^^^^^^^
tests/test_main.py:20:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:473: in get
return super().get(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1053: in get
return self.request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:445: in request
return super().request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:825: in request
return self.send(request, auth=auth, follow_redirects=follow_redirects)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:914: in send
response = self._send_handling_auth(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:942: in _send_handling_auth
response = self._send_handling_redirects(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:979: in _send_handling_redirects
response = self._send_single_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1014: in _send_single_request
response = transport.handle_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:348: in handle_request
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:345: in handle_request
portal.call(self.app, scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:334: in call
return cast(T_Retval, self.start_task_soon(func, *args).result())
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:450: in result
return self.__get_result()
^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:395: in __get_result
raise self._exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:259: in _call_func
retval = await retval_or_awaitable
^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1135: in __call__
await super().__call__(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:107: in __call__
await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:186: in __call__
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:164: in __call__
await self.app(scope, receive, _send)
stampbot/main.py:205: in __call__
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:191: in __call__
with recv_stream, send_stream, collapse_excgroups():
^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/contextlib.py:162: in __exit__
self.gen.throw(value)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_utils.py:85: in collapse_excgroups
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:193: in __call__
response = await self.dispatch_func(request, call_next)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
request = <starlette.middleware.base._CachedRequest object at 0x7f1f1a57b0e0>
call_next = <function BaseHTTPMiddleware.__call__.<locals>.call_next at 0x7f1f1a57e6c0>
@app.middleware("http")
async def metrics_middleware(request: Request, call_next: Any) -> Response:
"""Middleware to track HTTP metrics.
Args:
request: Incoming HTTP request.
call_next: Next middleware or route handler.
Returns:
HTTP response from downstream handler.
"""
method = request.method
endpoint = request.url.path
# Track in-progress requests
http_requests_in_progress.labels(method=method, endpoint=endpoint).inc()
start_time = time.time()
try:
response = await call_next(request)
> duration = time.time() << start_time
^^^^^^^^^^^^^^^^^^^^^^^^^
E TypeError: unsupported operand type(s) for <<: 'float' and 'float'
stampbot/main.py:122: TypeError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:31:01.090374Z[0m [[32m[1minfo [0m] [1mOpenTelemetry disabled [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.telemetry (INFO)>[0m [36m_name[0m=[35minfo[0m
[2m2026-05-16T19:31:01.109042Z[0m [[32m[1minfo [0m] [1mStarting stampbot on 0.0.0.0:8000[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.main (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'host': '0.0.0.0', 'port': 8000, 'log_level': 'INFO'}[0m
[2m2026-05-16T19:31:01.109379Z[0m [[32m[1minfo [0m] [1mGitHub App credentials configured successfully[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.main (INFO)>[0m [36m_name[0m=[35minfo[0m
[2m2026-05-16T19:31:01.111475Z[0m [[32m[1minfo [0m] [1mShutting down stampbot [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.main (INFO)>[0m [36m_name[0m=[35minfo[0m
=========================== short test summary info ============================
FAILED tests/test_main.py::test_lifespan_startup_shutdown_configured - TypeError: unsupported operand type(s) for <<: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 91 passed, 3 deselected in 1.04s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_LShift, occurrence: 1
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -313,7 +313,7 @@
try:
start_time = time.time()
result = await webhook_handler.handle_event(x_github_event, payload)
- duration = time.time() - start_time
+ duration = time.time() << start_time
webhook_processing_duration_seconds.labels(event_type=x_github_event or "unknown").observe(
duration........................................................................ [ 34%]
.............................F
=================================== FAILURES ===================================
___________________________ test_webhook_valid_ping ____________________________
test_client = <starlette.testclient.TestClient object at 0x7f8366fba9c0>
def test_webhook_valid_ping(test_client: TestClient):
"""Test webhook accepts valid ping event."""
import hashlib
import hmac
from stampbot.webhook_handler import webhook_handler
body = json.dumps({"zen": "Design for failure."}).encode()
signature = (
"sha256="
+ hmac.new(
webhook_handler.webhook_secret,
body,
hashlib.sha256,
).hexdigest()
)
response = test_client.post(
"/webhook",
content=body,
headers={
"Content-Type": "application/json",
"X-GitHub-Event": "ping",
"X-Hub-Signature-256": signature,
},
)
> assert response.status_code == 200
E assert 500 == 200
E + where 500 = <Response [500 Internal Server Error]>.status_code
tests/test_main.py:175: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:37:43.572104Z[0m [[32m[1minfo [0m] [1mReceived ping event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mclient_ip[0m=[35mtestclient[0m [36mextra[0m=[35m{'event_type': 'ping', 'action': ''}[0m
[2m2026-05-16T19:37:43.572359Z[0m [[31m[1merror [0m] [1mError handling webhook event: unsupported operand type(s) for <<: 'float' and 'float'[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.main (INFO)>[0m [36m_name[0m=[35merror[0m [36mclient_ip[0m=[35mtestclient[0m [36mextra[0m=[35m{'error': "unsupported operand type(s) for <<: 'float' and 'float'"}[0m
[2m2026-05-16T19:37:43.573120Z[0m [[32m[1minfo [0m] [1mHTTP Request: POST http://testserver/webhook "HTTP/1.1 500 Internal Server Error"[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'ping', 'action': ''}, 'event': 'Received ping event', 'client_ip': 'testclient', 'level': 'info', 'timestamp': '2026-05-16T19:37:43.572104Z'}
ERROR stampbot.main:main.py:326 {'extra': {'error': "unsupported operand type(s) for <<: 'float' and 'float'"}, 'event': "Error handling webhook event: unsupported operand type(s) for <<: 'float' and 'float'", 'client_ip': 'testclient', 'level': 'error', 'timestamp': '2026-05-16T19:37:43.572359Z'}
INFO httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 500 Internal Server Error"
=========================== short test summary info ============================
FAILED tests/test_main.py::test_webhook_valid_ping - assert 500 == 200
+ where 500 = <Response [500 Internal Server Error]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 101 passed, 3 deselected in 0.85s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_BitOr, occurrence: 0
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -119,7 +119,7 @@
try:
response = await call_next(request)
- duration = time.time() - start_time
+ duration = time.time() | start_time
# Track request metrics
http_requests_total.labels(........................................................................ [ 34%]
...................F
=================================== FAILURES ===================================
__________________ test_lifespan_startup_shutdown_configured ___________________
+ Exception Group Traceback (most recent call last):
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_utils.py", line 79, in collapse_excgroups
| yield
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py", line 192, in __call__
| async with anyio.create_task_group() as task_group:
| ~~~~~~~~~~~~~~~~~~~~~~~^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/_backends/_asyncio.py", line 783, in __aexit__
| raise BaseExceptionGroup(
| "unhandled errors in a TaskGroup", self._exceptions
| ) from None
| ExceptionGroup: unhandled errors in a TaskGroup (1 sub-exception)
+-+---------------- 1 ----------------
| Traceback (most recent call last):
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/runner.py", line 353, in from_call
| result: TResult | None = func()
| ~~~~^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/runner.py", line 245, in <lambda>
| lambda: runtest_hook(item=item, **kwds),
| ~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_hooks.py", line 512, in __call__
| return self._hookexec(self.name, self._hookimpls.copy(), kwargs, firstresult)
| ~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_manager.py", line 120, in _hookexec
| return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
| ~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_callers.py", line 167, in _multicall
| raise exception
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_callers.py", line 139, in _multicall
| teardown.throw(exception)
| ~~~~~~~~~~~~~~^^^^^^^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/logging.py", line 850, in pytest_runtest_call
| yield
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_callers.py", line 139, in _multicall
| teardown.throw(exception)
| ~~~~~~~~~~~~~~^^^^^^^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_callers.py", line 53, in run_old_style_hookwrapper
| return result.get_result()
| ~~~~~~~~~~~~~~~~~^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_result.py", line 103, in get_result
| raise exc.with_traceback(tb)
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_callers.py", line 38, in run_old_style_hookwrapper
| res = yield
| ^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_callers.py", line 139, in _multicall
| teardown.throw(exception)
| ~~~~~~~~~~~~~~^^^^^^^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/capture.py", line 900, in pytest_runtest_call
| return (yield)
| ^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_callers.py", line 139, in _multicall
| teardown.throw(exception)
| ~~~~~~~~~~~~~~^^^^^^^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/skipping.py", line 268, in pytest_runtest_call
| return (yield)
| ^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_callers.py", line 121, in _multicall
| res = hook_impl.function(*args)
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/runner.py", line 179, in pytest_runtest_call
| item.runtest()
| ~~~~~~~~~~~~^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/python.py", line 1720, in runtest
| self.ihook.pytest_pyfunc_call(pyfuncitem=self)
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_hooks.py", line 512, in __call__
| return self._hookexec(self.name, self._hookimpls.copy(), kwargs, firstresult)
| ~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_manager.py", line 120, in _hookexec
| return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
| ~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_callers.py", line 167, in _multicall
| raise exception
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_callers.py", line 139, in _multicall
| teardown.throw(exception)
| ~~~~~~~~~~~~~~^^^^^^^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_callers.py", line 53, in run_old_style_hookwrapper
| return result.get_result()
| ~~~~~~~~~~~~~~~~~^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_result.py", line 103, in get_result
| raise exc.with_traceback(tb)
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_callers.py", line 38, in run_old_style_hookwrapper
| res = yield
| ^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_callers.py", line 121, in _multicall
| res = hook_impl.function(*args)
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/python.py", line 166, in pytest_pyfunc_call
| result = testfunction(**testargs)
| File "/home/runner/work/stampbot/stampbot/tests/test_main.py", line 20, in test_lifespan_startup_shutdown_configured
| response = client.get("/health")
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py", line 473, in get
| return super().get(
| ~~~~~~~~~~~^
| url,
| ^^^^
| ...<6 lines>...
| extensions=extensions,
| ^^^^^^^^^^^^^^^^^^^^^^
| )
| ^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py", line 1053, in get
| return self.request(
| ~~~~~~~~~~~~^
| "GET",
| ^^^^^^
| ...<7 lines>...
| extensions=extensions,
| ^^^^^^^^^^^^^^^^^^^^^^
| )
| ^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py", line 445, in request
| return super().request(
| ~~~~~~~~~~~~~~~^
| method,
| ^^^^^^^
| ...<11 lines>...
| extensions=extensions,
| ^^^^^^^^^^^^^^^^^^^^^^
| )
| ^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py", line 825, in request
| return self.send(request, auth=auth, follow_redirects=follow_redirects)
| ~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py", line 914, in send
| response = self._send_handling_auth(
| request,
| ...<2 lines>...
| history=[],
| )
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py", line 942, in _send_handling_auth
| response = self._send_handling_redirects(
| request,
| follow_redirects=follow_redirects,
| history=history,
| )
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py", line 979, in _send_handling_redirects
| response = self._send_single_request(request)
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py", line 1014, in _send_single_request
| response = transport.handle_request(request)
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py", line 348, in handle_request
| raise exc
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py", line 345, in handle_request
| portal.call(self.app, scope, receive, send)
| ~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py", line 334, in call
| return cast(T_Retval, self.start_task_soon(func, *args).result())
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^
| File "/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py", line 450, in result
| return self.__get_result()
| ~~~~~~~~~~~~~~~~~^^
| File "/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py", line 395, in __get_result
| raise self._exception
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py", line 259, in _call_func
| retval = await retval_or_awaitable
| ^^^^^^^^^^^^^^^^^^^^^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py", line 1135, in __call__
| await super().__call__(scope, receive, send)
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py", line 107, in __call__
| await self.middleware_stack(scope, receive, send)
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py", line 186, in __call__
| raise exc
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py", line 164, in __call__
| await self.app(scope, receive, _send)
| File "/home/runner/work/stampbot/stampbot/stampbot/main.py", line 205, in __call__
| await self.app(scope, receive, send)
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py", line 191, in __call__
| with recv_stream, send_stream, collapse_excgroups():
| ~~~~~~~~~~~~~~~~~~^^
| File "/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/contextlib.py", line 162, in __exit__
| self.gen.throw(value)
| ~~~~~~~~~~~~~~^^^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_utils.py", line 85, in collapse_excgroups
| raise exc
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py", line 193, in __call__
| response = await self.dispatch_func(request, call_next)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| File "/home/runner/work/stampbot/stampbot/stampbot/main.py", line 122, in metrics_middleware
| duration = time.time() | start_time
| ~~~~~~~~~~~~^~~~~~~~~~~~
| TypeError: unsupported operand type(s) for |: 'float' and 'float'
+------------------------------------
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/home/runner/work/stampbot/stampbot/tests/test_main.py", line 20, in test_lifespan_startup_shutdown_configured
response = client.get("/health")
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py", line 473, in get
return super().get(
~~~~~~~~~~~^
url,
^^^^
...<6 lines>...
extensions=extensions,
^^^^^^^^^^^^^^^^^^^^^^
)
^
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py", line 1053, in get
return self.request(
~~~~~~~~~~~~^
"GET",
^^^^^^
...<7 lines>...
extensions=extensions,
^^^^^^^^^^^^^^^^^^^^^^
)
^
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py", line 445, in request
return super().request(
~~~~~~~~~~~~~~~^
method,
^^^^^^^
...<11 lines>...
extensions=extensions,
^^^^^^^^^^^^^^^^^^^^^^
)
^
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py", line 825, in request
return self.send(request, auth=auth, follow_redirects=follow_redirects)
~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py", line 914, in send
response = self._send_handling_auth(
request,
...<2 lines>...
history=[],
)
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py", line 942, in _send_handling_auth
response = self._send_handling_redirects(
request,
follow_redirects=follow_redirects,
history=history,
)
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py", line 979, in _send_handling_redirects
response = self._send_single_request(request)
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py", line 1014, in _send_single_request
response = transport.handle_request(request)
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py", line 348, in handle_request
raise exc
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py", line 345, in handle_request
portal.call(self.app, scope, receive, send)
~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py", line 334, in call
return cast(T_Retval, self.start_task_soon(func, *args).result())
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^
File "/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py", line 450, in result
return self.__get_result()
~~~~~~~~~~~~~~~~~^^
File "/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py", line 395, in __get_result
raise self._exception
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py", line 259, in _call_func
retval = await retval_or_awaitable
^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py", line 1135, in __call__
await super().__call__(scope, receive, send)
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py", line 107, in __call__
await self.middleware_stack(scope, receive, send)
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py", line 186, in __call__
raise exc
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py", line 164, in __call__
await self.app(scope, receive, _send)
File "/home/runner/work/stampbot/stampbot/stampbot/main.py", line 205, in __call__
await self.app(scope, receive, send)
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py", line 191, in __call__
with recv_stream, send_stream, collapse_excgroups():
~~~~~~~~~~~~~~~~~~^^
File "/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/contextlib.py", line 162, in __exit__
self.gen.throw(value)
~~~~~~~~~~~~~~^^^^^^^
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_utils.py", line 85, in collapse_excgroups
raise exc
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py", line 193, in __call__
response = await self.dispatch_func(request, call_next)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/runner/work/stampbot/stampbot/stampbot/main.py", line 122, in metrics_middleware
duration = time.time() | start_time
~~~~~~~~~~~~^~~~~~~~~~~~
TypeError: unsupported operand type(s) for |: 'float' and 'float'
During handling of the above exception, another exception occurred:
def test_lifespan_startup_shutdown_configured():
"""Test app lifespan startup and shutdown when configured."""
from stampbot.main import app
# Use TestClient as context manager to trigger lifespan events
with TestClient(app) as client:
# App should be running
> response = client.get("/health")
^^^^^^^^^^^^^^^^^^^^^
tests/test_main.py:20:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:473: in get
return super().get(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1053: in get
return self.request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:445: in request
return super().request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:825: in request
return self.send(request, auth=auth, follow_redirects=follow_redirects)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:914: in send
response = self._send_handling_auth(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:942: in _send_handling_auth
response = self._send_handling_redirects(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:979: in _send_handling_redirects
response = self._send_single_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1014: in _send_single_request
response = transport.handle_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:348: in handle_request
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:345: in handle_request
portal.call(self.app, scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:334: in call
return cast(T_Retval, self.start_task_soon(func, *args).result())
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:450: in result
return self.__get_result()
^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:395: in __get_result
raise self._exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:259: in _call_func
retval = await retval_or_awaitable
^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1135: in __call__
await super().__call__(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:107: in __call__
await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:186: in __call__
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:164: in __call__
await self.app(scope, receive, _send)
stampbot/main.py:205: in __call__
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:191: in __call__
with recv_stream, send_stream, collapse_excgroups():
^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/contextlib.py:162: in __exit__
self.gen.throw(value)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_utils.py:85: in collapse_excgroups
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:193: in __call__
response = await self.dispatch_func(request, call_next)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
request = <starlette.middleware.base._CachedRequest object at 0x7f690ee9b0e0>
call_next = <function BaseHTTPMiddleware.__call__.<locals>.call_next at 0x7f690ee9e820>
@app.middleware("http")
async def metrics_middleware(request: Request, call_next: Any) -> Response:
"""Middleware to track HTTP metrics.
Args:
request: Incoming HTTP request.
call_next: Next middleware or route handler.
Returns:
HTTP response from downstream handler.
"""
method = request.method
endpoint = request.url.path
# Track in-progress requests
http_requests_in_progress.labels(method=method, endpoint=endpoint).inc()
start_time = time.time()
try:
response = await call_next(request)
> duration = time.time() | start_time
^^^^^^^^^^^^^^^^^^^^^^^^
E TypeError: unsupported operand type(s) for |: 'float' and 'float'
stampbot/main.py:122: TypeError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:39:04.156707Z[0m [[32m[1minfo [0m] [1mOpenTelemetry disabled [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.telemetry (INFO)>[0m [36m_name[0m=[35minfo[0m
[2m2026-05-16T19:39:04.175536Z[0m [[32m[1minfo [0m] [1mStarting stampbot on 0.0.0.0:8000[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.main (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'host': '0.0.0.0', 'port': 8000, 'log_level': 'INFO'}[0m
[2m2026-05-16T19:39:04.175833Z[0m [[32m[1minfo [0m] [1mGitHub App credentials configured successfully[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.main (INFO)>[0m [36m_name[0m=[35minfo[0m
[2m2026-05-16T19:39:04.178016Z[0m [[32m[1minfo [0m] [1mShutting down stampbot [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.main (INFO)>[0m [36m_name[0m=[35minfo[0m
=========================== short test summary info ============================
FAILED tests/test_main.py::test_lifespan_startup_shutdown_configured - TypeError: unsupported operand type(s) for |: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 91 passed, 3 deselected in 1.04s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_BitOr, occurrence: 1
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -313,7 +313,7 @@
try:
start_time = time.time()
result = await webhook_handler.handle_event(x_github_event, payload)
- duration = time.time() - start_time
+ duration = time.time() | start_time
webhook_processing_duration_seconds.labels(event_type=x_github_event or "unknown").observe(
duration........................................................................ [ 34%]
.............................F
=================================== FAILURES ===================================
___________________________ test_webhook_valid_ping ____________________________
test_client = <starlette.testclient.TestClient object at 0x7fcb932b29c0>
def test_webhook_valid_ping(test_client: TestClient):
"""Test webhook accepts valid ping event."""
import hashlib
import hmac
from stampbot.webhook_handler import webhook_handler
body = json.dumps({"zen": "Design for failure."}).encode()
signature = (
"sha256="
+ hmac.new(
webhook_handler.webhook_secret,
body,
hashlib.sha256,
).hexdigest()
)
response = test_client.post(
"/webhook",
content=body,
headers={
"Content-Type": "application/json",
"X-GitHub-Event": "ping",
"X-Hub-Signature-256": signature,
},
)
> assert response.status_code == 200
E assert 500 == 200
E + where 500 = <Response [500 Internal Server Error]>.status_code
tests/test_main.py:175: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:32:23.606210Z[0m [[32m[1minfo [0m] [1mReceived ping event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mclient_ip[0m=[35mtestclient[0m [36mextra[0m=[35m{'event_type': 'ping', 'action': ''}[0m
[2m2026-05-16T19:32:23.606417Z[0m [[31m[1merror [0m] [1mError handling webhook event: unsupported operand type(s) for |: 'float' and 'float'[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.main (INFO)>[0m [36m_name[0m=[35merror[0m [36mclient_ip[0m=[35mtestclient[0m [36mextra[0m=[35m{'error': "unsupported operand type(s) for |: 'float' and 'float'"}[0m
[2m2026-05-16T19:32:23.607175Z[0m [[32m[1minfo [0m] [1mHTTP Request: POST http://testserver/webhook "HTTP/1.1 500 Internal Server Error"[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'ping', 'action': ''}, 'event': 'Received ping event', 'client_ip': 'testclient', 'level': 'info', 'timestamp': '2026-05-16T19:32:23.606210Z'}
ERROR stampbot.main:main.py:326 {'extra': {'error': "unsupported operand type(s) for |: 'float' and 'float'"}, 'event': "Error handling webhook event: unsupported operand type(s) for |: 'float' and 'float'", 'client_ip': 'testclient', 'level': 'error', 'timestamp': '2026-05-16T19:32:23.606417Z'}
INFO httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 500 Internal Server Error"
=========================== short test summary info ============================
FAILED tests/test_main.py::test_webhook_valid_ping - assert 500 == 200
+ where 500 = <Response [500 Internal Server Error]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 101 passed, 3 deselected in 0.85s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_BitAnd, occurrence: 0
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -119,7 +119,7 @@
try:
response = await call_next(request)
- duration = time.time() - start_time
+ duration = time.time() & start_time
# Track request metrics
http_requests_total.labels(........................................................................ [ 34%]
...................F
=================================== FAILURES ===================================
__________________ test_lifespan_startup_shutdown_configured ___________________
+ Exception Group Traceback (most recent call last):
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_utils.py", line 79, in collapse_excgroups
| yield
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py", line 192, in __call__
| async with anyio.create_task_group() as task_group:
| ~~~~~~~~~~~~~~~~~~~~~~~^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/_backends/_asyncio.py", line 783, in __aexit__
| raise BaseExceptionGroup(
| "unhandled errors in a TaskGroup", self._exceptions
| ) from None
| ExceptionGroup: unhandled errors in a TaskGroup (1 sub-exception)
+-+---------------- 1 ----------------
| Traceback (most recent call last):
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/runner.py", line 353, in from_call
| result: TResult | None = func()
| ~~~~^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/runner.py", line 245, in <lambda>
| lambda: runtest_hook(item=item, **kwds),
| ~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_hooks.py", line 512, in __call__
| return self._hookexec(self.name, self._hookimpls.copy(), kwargs, firstresult)
| ~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_manager.py", line 120, in _hookexec
| return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
| ~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_callers.py", line 167, in _multicall
| raise exception
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_callers.py", line 139, in _multicall
| teardown.throw(exception)
| ~~~~~~~~~~~~~~^^^^^^^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/logging.py", line 850, in pytest_runtest_call
| yield
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_callers.py", line 139, in _multicall
| teardown.throw(exception)
| ~~~~~~~~~~~~~~^^^^^^^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_callers.py", line 53, in run_old_style_hookwrapper
| return result.get_result()
| ~~~~~~~~~~~~~~~~~^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_result.py", line 103, in get_result
| raise exc.with_traceback(tb)
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_callers.py", line 38, in run_old_style_hookwrapper
| res = yield
| ^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_callers.py", line 139, in _multicall
| teardown.throw(exception)
| ~~~~~~~~~~~~~~^^^^^^^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/capture.py", line 900, in pytest_runtest_call
| return (yield)
| ^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_callers.py", line 139, in _multicall
| teardown.throw(exception)
| ~~~~~~~~~~~~~~^^^^^^^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/skipping.py", line 268, in pytest_runtest_call
| return (yield)
| ^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_callers.py", line 121, in _multicall
| res = hook_impl.function(*args)
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/runner.py", line 179, in pytest_runtest_call
| item.runtest()
| ~~~~~~~~~~~~^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/python.py", line 1720, in runtest
| self.ihook.pytest_pyfunc_call(pyfuncitem=self)
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_hooks.py", line 512, in __call__
| return self._hookexec(self.name, self._hookimpls.copy(), kwargs, firstresult)
| ~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_manager.py", line 120, in _hookexec
| return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
| ~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_callers.py", line 167, in _multicall
| raise exception
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_callers.py", line 139, in _multicall
| teardown.throw(exception)
| ~~~~~~~~~~~~~~^^^^^^^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_callers.py", line 53, in run_old_style_hookwrapper
| return result.get_result()
| ~~~~~~~~~~~~~~~~~^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_result.py", line 103, in get_result
| raise exc.with_traceback(tb)
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_callers.py", line 38, in run_old_style_hookwrapper
| res = yield
| ^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_callers.py", line 121, in _multicall
| res = hook_impl.function(*args)
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/python.py", line 166, in pytest_pyfunc_call
| result = testfunction(**testargs)
| File "/home/runner/work/stampbot/stampbot/tests/test_main.py", line 20, in test_lifespan_startup_shutdown_configured
| response = client.get("/health")
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py", line 473, in get
| return super().get(
| ~~~~~~~~~~~^
| url,
| ^^^^
| ...<6 lines>...
| extensions=extensions,
| ^^^^^^^^^^^^^^^^^^^^^^
| )
| ^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py", line 1053, in get
| return self.request(
| ~~~~~~~~~~~~^
| "GET",
| ^^^^^^
| ...<7 lines>...
| extensions=extensions,
| ^^^^^^^^^^^^^^^^^^^^^^
| )
| ^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py", line 445, in request
| return super().request(
| ~~~~~~~~~~~~~~~^
| method,
| ^^^^^^^
| ...<11 lines>...
| extensions=extensions,
| ^^^^^^^^^^^^^^^^^^^^^^
| )
| ^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py", line 825, in request
| return self.send(request, auth=auth, follow_redirects=follow_redirects)
| ~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py", line 914, in send
| response = self._send_handling_auth(
| request,
| ...<2 lines>...
| history=[],
| )
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py", line 942, in _send_handling_auth
| response = self._send_handling_redirects(
| request,
| follow_redirects=follow_redirects,
| history=history,
| )
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py", line 979, in _send_handling_redirects
| response = self._send_single_request(request)
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py", line 1014, in _send_single_request
| response = transport.handle_request(request)
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py", line 348, in handle_request
| raise exc
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py", line 345, in handle_request
| portal.call(self.app, scope, receive, send)
| ~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py", line 334, in call
| return cast(T_Retval, self.start_task_soon(func, *args).result())
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^
| File "/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py", line 450, in result
| return self.__get_result()
| ~~~~~~~~~~~~~~~~~^^
| File "/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py", line 395, in __get_result
| raise self._exception
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py", line 259, in _call_func
| retval = await retval_or_awaitable
| ^^^^^^^^^^^^^^^^^^^^^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py", line 1135, in __call__
| await super().__call__(scope, receive, send)
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py", line 107, in __call__
| await self.middleware_stack(scope, receive, send)
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py", line 186, in __call__
| raise exc
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py", line 164, in __call__
| await self.app(scope, receive, _send)
| File "/home/runner/work/stampbot/stampbot/stampbot/main.py", line 205, in __call__
| await self.app(scope, receive, send)
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py", line 191, in __call__
| with recv_stream, send_stream, collapse_excgroups():
| ~~~~~~~~~~~~~~~~~~^^
| File "/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/contextlib.py", line 162, in __exit__
| self.gen.throw(value)
| ~~~~~~~~~~~~~~^^^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_utils.py", line 85, in collapse_excgroups
| raise exc
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py", line 193, in __call__
| response = await self.dispatch_func(request, call_next)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| File "/home/runner/work/stampbot/stampbot/stampbot/main.py", line 122, in metrics_middleware
| duration = time.time() & start_time
| ~~~~~~~~~~~~^~~~~~~~~~~~
| TypeError: unsupported operand type(s) for &: 'float' and 'float'
+------------------------------------
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/home/runner/work/stampbot/stampbot/tests/test_main.py", line 20, in test_lifespan_startup_shutdown_configured
response = client.get("/health")
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py", line 473, in get
return super().get(
~~~~~~~~~~~^
url,
^^^^
...<6 lines>...
extensions=extensions,
^^^^^^^^^^^^^^^^^^^^^^
)
^
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py", line 1053, in get
return self.request(
~~~~~~~~~~~~^
"GET",
^^^^^^
...<7 lines>...
extensions=extensions,
^^^^^^^^^^^^^^^^^^^^^^
)
^
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py", line 445, in request
return super().request(
~~~~~~~~~~~~~~~^
method,
^^^^^^^
...<11 lines>...
extensions=extensions,
^^^^^^^^^^^^^^^^^^^^^^
)
^
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py", line 825, in request
return self.send(request, auth=auth, follow_redirects=follow_redirects)
~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py", line 914, in send
response = self._send_handling_auth(
request,
...<2 lines>...
history=[],
)
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py", line 942, in _send_handling_auth
response = self._send_handling_redirects(
request,
follow_redirects=follow_redirects,
history=history,
)
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py", line 979, in _send_handling_redirects
response = self._send_single_request(request)
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py", line 1014, in _send_single_request
response = transport.handle_request(request)
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py", line 348, in handle_request
raise exc
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py", line 345, in handle_request
portal.call(self.app, scope, receive, send)
~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py", line 334, in call
return cast(T_Retval, self.start_task_soon(func, *args).result())
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^
File "/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py", line 450, in result
return self.__get_result()
~~~~~~~~~~~~~~~~~^^
File "/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py", line 395, in __get_result
raise self._exception
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py", line 259, in _call_func
retval = await retval_or_awaitable
^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py", line 1135, in __call__
await super().__call__(scope, receive, send)
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py", line 107, in __call__
await self.middleware_stack(scope, receive, send)
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py", line 186, in __call__
raise exc
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py", line 164, in __call__
await self.app(scope, receive, _send)
File "/home/runner/work/stampbot/stampbot/stampbot/main.py", line 205, in __call__
await self.app(scope, receive, send)
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py", line 191, in __call__
with recv_stream, send_stream, collapse_excgroups():
~~~~~~~~~~~~~~~~~~^^
File "/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/contextlib.py", line 162, in __exit__
self.gen.throw(value)
~~~~~~~~~~~~~~^^^^^^^
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_utils.py", line 85, in collapse_excgroups
raise exc
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py", line 193, in __call__
response = await self.dispatch_func(request, call_next)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/runner/work/stampbot/stampbot/stampbot/main.py", line 122, in metrics_middleware
duration = time.time() & start_time
~~~~~~~~~~~~^~~~~~~~~~~~
TypeError: unsupported operand type(s) for &: 'float' and 'float'
During handling of the above exception, another exception occurred:
def test_lifespan_startup_shutdown_configured():
"""Test app lifespan startup and shutdown when configured."""
from stampbot.main import app
# Use TestClient as context manager to trigger lifespan events
with TestClient(app) as client:
# App should be running
> response = client.get("/health")
^^^^^^^^^^^^^^^^^^^^^
tests/test_main.py:20:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:473: in get
return super().get(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1053: in get
return self.request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:445: in request
return super().request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:825: in request
return self.send(request, auth=auth, follow_redirects=follow_redirects)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:914: in send
response = self._send_handling_auth(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:942: in _send_handling_auth
response = self._send_handling_redirects(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:979: in _send_handling_redirects
response = self._send_single_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1014: in _send_single_request
response = transport.handle_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:348: in handle_request
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:345: in handle_request
portal.call(self.app, scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:334: in call
return cast(T_Retval, self.start_task_soon(func, *args).result())
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:450: in result
return self.__get_result()
^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:395: in __get_result
raise self._exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:259: in _call_func
retval = await retval_or_awaitable
^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1135: in __call__
await super().__call__(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:107: in __call__
await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:186: in __call__
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:164: in __call__
await self.app(scope, receive, _send)
stampbot/main.py:205: in __call__
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:191: in __call__
with recv_stream, send_stream, collapse_excgroups():
^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/contextlib.py:162: in __exit__
self.gen.throw(value)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_utils.py:85: in collapse_excgroups
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:193: in __call__
response = await self.dispatch_func(request, call_next)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
request = <starlette.middleware.base._CachedRequest object at 0x7fb887b670e0>
call_next = <function BaseHTTPMiddleware.__call__.<locals>.call_next at 0x7fb887b6a820>
@app.middleware("http")
async def metrics_middleware(request: Request, call_next: Any) -> Response:
"""Middleware to track HTTP metrics.
Args:
request: Incoming HTTP request.
call_next: Next middleware or route handler.
Returns:
HTTP response from downstream handler.
"""
method = request.method
endpoint = request.url.path
# Track in-progress requests
http_requests_in_progress.labels(method=method, endpoint=endpoint).inc()
start_time = time.time()
try:
response = await call_next(request)
> duration = time.time() & start_time
^^^^^^^^^^^^^^^^^^^^^^^^
E TypeError: unsupported operand type(s) for &: 'float' and 'float'
stampbot/main.py:122: TypeError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:42:22.183082Z[0m [[32m[1minfo [0m] [1mOpenTelemetry disabled [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.telemetry (INFO)>[0m [36m_name[0m=[35minfo[0m
[2m2026-05-16T19:42:22.202798Z[0m [[32m[1minfo [0m] [1mStarting stampbot on 0.0.0.0:8000[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.main (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'host': '0.0.0.0', 'port': 8000, 'log_level': 'INFO'}[0m
[2m2026-05-16T19:42:22.203161Z[0m [[32m[1minfo [0m] [1mGitHub App credentials configured successfully[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.main (INFO)>[0m [36m_name[0m=[35minfo[0m
[2m2026-05-16T19:42:22.205440Z[0m [[32m[1minfo [0m] [1mShutting down stampbot [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.main (INFO)>[0m [36m_name[0m=[35minfo[0m
=========================== short test summary info ============================
FAILED tests/test_main.py::test_lifespan_startup_shutdown_configured - TypeError: unsupported operand type(s) for &: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 91 passed, 3 deselected in 1.05s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_BitAnd, occurrence: 1
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -313,7 +313,7 @@
try:
start_time = time.time()
result = await webhook_handler.handle_event(x_github_event, payload)
- duration = time.time() - start_time
+ duration = time.time() & start_time
webhook_processing_duration_seconds.labels(event_type=x_github_event or "unknown").observe(
duration........................................................................ [ 34%]
.............................F
=================================== FAILURES ===================================
___________________________ test_webhook_valid_ping ____________________________
test_client = <starlette.testclient.TestClient object at 0x7f552acc29c0>
def test_webhook_valid_ping(test_client: TestClient):
"""Test webhook accepts valid ping event."""
import hashlib
import hmac
from stampbot.webhook_handler import webhook_handler
body = json.dumps({"zen": "Design for failure."}).encode()
signature = (
"sha256="
+ hmac.new(
webhook_handler.webhook_secret,
body,
hashlib.sha256,
).hexdigest()
)
response = test_client.post(
"/webhook",
content=body,
headers={
"Content-Type": "application/json",
"X-GitHub-Event": "ping",
"X-Hub-Signature-256": signature,
},
)
> assert response.status_code == 200
E assert 500 == 200
E + where 500 = <Response [500 Internal Server Error]>.status_code
tests/test_main.py:175: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:41:32.534096Z[0m [[32m[1minfo [0m] [1mReceived ping event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mclient_ip[0m=[35mtestclient[0m [36mextra[0m=[35m{'event_type': 'ping', 'action': ''}[0m
[2m2026-05-16T19:41:32.534338Z[0m [[31m[1merror [0m] [1mError handling webhook event: unsupported operand type(s) for &: 'float' and 'float'[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.main (INFO)>[0m [36m_name[0m=[35merror[0m [36mclient_ip[0m=[35mtestclient[0m [36mextra[0m=[35m{'error': "unsupported operand type(s) for &: 'float' and 'float'"}[0m
[2m2026-05-16T19:41:32.535112Z[0m [[32m[1minfo [0m] [1mHTTP Request: POST http://testserver/webhook "HTTP/1.1 500 Internal Server Error"[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'ping', 'action': ''}, 'event': 'Received ping event', 'client_ip': 'testclient', 'level': 'info', 'timestamp': '2026-05-16T19:41:32.534096Z'}
ERROR stampbot.main:main.py:326 {'extra': {'error': "unsupported operand type(s) for &: 'float' and 'float'"}, 'event': "Error handling webhook event: unsupported operand type(s) for &: 'float' and 'float'", 'client_ip': 'testclient', 'level': 'error', 'timestamp': '2026-05-16T19:41:32.534338Z'}
INFO httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 500 Internal Server Error"
=========================== short test summary info ============================
FAILED tests/test_main.py::test_webhook_valid_ping - assert 500 == 200
+ where 500 = <Response [500 Internal Server Error]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 101 passed, 3 deselected in 0.87s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_BitXor, occurrence: 0
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -119,7 +119,7 @@
try:
response = await call_next(request)
- duration = time.time() - start_time
+ duration = time.time() ^ start_time
# Track request metrics
http_requests_total.labels(........................................................................ [ 34%]
...................F
=================================== FAILURES ===================================
__________________ test_lifespan_startup_shutdown_configured ___________________
+ Exception Group Traceback (most recent call last):
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_utils.py", line 79, in collapse_excgroups
| yield
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py", line 192, in __call__
| async with anyio.create_task_group() as task_group:
| ~~~~~~~~~~~~~~~~~~~~~~~^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/_backends/_asyncio.py", line 783, in __aexit__
| raise BaseExceptionGroup(
| "unhandled errors in a TaskGroup", self._exceptions
| ) from None
| ExceptionGroup: unhandled errors in a TaskGroup (1 sub-exception)
+-+---------------- 1 ----------------
| Traceback (most recent call last):
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/runner.py", line 353, in from_call
| result: TResult | None = func()
| ~~~~^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/runner.py", line 245, in <lambda>
| lambda: runtest_hook(item=item, **kwds),
| ~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_hooks.py", line 512, in __call__
| return self._hookexec(self.name, self._hookimpls.copy(), kwargs, firstresult)
| ~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_manager.py", line 120, in _hookexec
| return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
| ~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_callers.py", line 167, in _multicall
| raise exception
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_callers.py", line 139, in _multicall
| teardown.throw(exception)
| ~~~~~~~~~~~~~~^^^^^^^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/logging.py", line 850, in pytest_runtest_call
| yield
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_callers.py", line 139, in _multicall
| teardown.throw(exception)
| ~~~~~~~~~~~~~~^^^^^^^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_callers.py", line 53, in run_old_style_hookwrapper
| return result.get_result()
| ~~~~~~~~~~~~~~~~~^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_result.py", line 103, in get_result
| raise exc.with_traceback(tb)
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_callers.py", line 38, in run_old_style_hookwrapper
| res = yield
| ^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_callers.py", line 139, in _multicall
| teardown.throw(exception)
| ~~~~~~~~~~~~~~^^^^^^^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/capture.py", line 900, in pytest_runtest_call
| return (yield)
| ^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_callers.py", line 139, in _multicall
| teardown.throw(exception)
| ~~~~~~~~~~~~~~^^^^^^^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/skipping.py", line 268, in pytest_runtest_call
| return (yield)
| ^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_callers.py", line 121, in _multicall
| res = hook_impl.function(*args)
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/runner.py", line 179, in pytest_runtest_call
| item.runtest()
| ~~~~~~~~~~~~^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/python.py", line 1720, in runtest
| self.ihook.pytest_pyfunc_call(pyfuncitem=self)
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_hooks.py", line 512, in __call__
| return self._hookexec(self.name, self._hookimpls.copy(), kwargs, firstresult)
| ~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_manager.py", line 120, in _hookexec
| return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
| ~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_callers.py", line 167, in _multicall
| raise exception
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_callers.py", line 139, in _multicall
| teardown.throw(exception)
| ~~~~~~~~~~~~~~^^^^^^^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_callers.py", line 53, in run_old_style_hookwrapper
| return result.get_result()
| ~~~~~~~~~~~~~~~~~^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_result.py", line 103, in get_result
| raise exc.with_traceback(tb)
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_callers.py", line 38, in run_old_style_hookwrapper
| res = yield
| ^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_callers.py", line 121, in _multicall
| res = hook_impl.function(*args)
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/python.py", line 166, in pytest_pyfunc_call
| result = testfunction(**testargs)
| File "/home/runner/work/stampbot/stampbot/tests/test_main.py", line 20, in test_lifespan_startup_shutdown_configured
| response = client.get("/health")
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py", line 473, in get
| return super().get(
| ~~~~~~~~~~~^
| url,
| ^^^^
| ...<6 lines>...
| extensions=extensions,
| ^^^^^^^^^^^^^^^^^^^^^^
| )
| ^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py", line 1053, in get
| return self.request(
| ~~~~~~~~~~~~^
| "GET",
| ^^^^^^
| ...<7 lines>...
| extensions=extensions,
| ^^^^^^^^^^^^^^^^^^^^^^
| )
| ^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py", line 445, in request
| return super().request(
| ~~~~~~~~~~~~~~~^
| method,
| ^^^^^^^
| ...<11 lines>...
| extensions=extensions,
| ^^^^^^^^^^^^^^^^^^^^^^
| )
| ^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py", line 825, in request
| return self.send(request, auth=auth, follow_redirects=follow_redirects)
| ~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py", line 914, in send
| response = self._send_handling_auth(
| request,
| ...<2 lines>...
| history=[],
| )
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py", line 942, in _send_handling_auth
| response = self._send_handling_redirects(
| request,
| follow_redirects=follow_redirects,
| history=history,
| )
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py", line 979, in _send_handling_redirects
| response = self._send_single_request(request)
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py", line 1014, in _send_single_request
| response = transport.handle_request(request)
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py", line 348, in handle_request
| raise exc
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py", line 345, in handle_request
| portal.call(self.app, scope, receive, send)
| ~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py", line 334, in call
| return cast(T_Retval, self.start_task_soon(func, *args).result())
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^
| File "/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py", line 450, in result
| return self.__get_result()
| ~~~~~~~~~~~~~~~~~^^
| File "/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py", line 395, in __get_result
| raise self._exception
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py", line 259, in _call_func
| retval = await retval_or_awaitable
| ^^^^^^^^^^^^^^^^^^^^^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py", line 1135, in __call__
| await super().__call__(scope, receive, send)
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py", line 107, in __call__
| await self.middleware_stack(scope, receive, send)
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py", line 186, in __call__
| raise exc
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py", line 164, in __call__
| await self.app(scope, receive, _send)
| File "/home/runner/work/stampbot/stampbot/stampbot/main.py", line 205, in __call__
| await self.app(scope, receive, send)
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py", line 191, in __call__
| with recv_stream, send_stream, collapse_excgroups():
| ~~~~~~~~~~~~~~~~~~^^
| File "/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/contextlib.py", line 162, in __exit__
| self.gen.throw(value)
| ~~~~~~~~~~~~~~^^^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_utils.py", line 85, in collapse_excgroups
| raise exc
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py", line 193, in __call__
| response = await self.dispatch_func(request, call_next)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| File "/home/runner/work/stampbot/stampbot/stampbot/main.py", line 122, in metrics_middleware
| duration = time.time() ^ start_time
| ~~~~~~~~~~~~^~~~~~~~~~~~
| TypeError: unsupported operand type(s) for ^: 'float' and 'float'
+------------------------------------
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/home/runner/work/stampbot/stampbot/tests/test_main.py", line 20, in test_lifespan_startup_shutdown_configured
response = client.get("/health")
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py", line 473, in get
return super().get(
~~~~~~~~~~~^
url,
^^^^
...<6 lines>...
extensions=extensions,
^^^^^^^^^^^^^^^^^^^^^^
)
^
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py", line 1053, in get
return self.request(
~~~~~~~~~~~~^
"GET",
^^^^^^
...<7 lines>...
extensions=extensions,
^^^^^^^^^^^^^^^^^^^^^^
)
^
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py", line 445, in request
return super().request(
~~~~~~~~~~~~~~~^
method,
^^^^^^^
...<11 lines>...
extensions=extensions,
^^^^^^^^^^^^^^^^^^^^^^
)
^
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py", line 825, in request
return self.send(request, auth=auth, follow_redirects=follow_redirects)
~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py", line 914, in send
response = self._send_handling_auth(
request,
...<2 lines>...
history=[],
)
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py", line 942, in _send_handling_auth
response = self._send_handling_redirects(
request,
follow_redirects=follow_redirects,
history=history,
)
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py", line 979, in _send_handling_redirects
response = self._send_single_request(request)
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py", line 1014, in _send_single_request
response = transport.handle_request(request)
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py", line 348, in handle_request
raise exc
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py", line 345, in handle_request
portal.call(self.app, scope, receive, send)
~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py", line 334, in call
return cast(T_Retval, self.start_task_soon(func, *args).result())
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^
File "/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py", line 450, in result
return self.__get_result()
~~~~~~~~~~~~~~~~~^^
File "/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py", line 395, in __get_result
raise self._exception
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py", line 259, in _call_func
retval = await retval_or_awaitable
^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py", line 1135, in __call__
await super().__call__(scope, receive, send)
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py", line 107, in __call__
await self.middleware_stack(scope, receive, send)
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py", line 186, in __call__
raise exc
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py", line 164, in __call__
await self.app(scope, receive, _send)
File "/home/runner/work/stampbot/stampbot/stampbot/main.py", line 205, in __call__
await self.app(scope, receive, send)
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py", line 191, in __call__
with recv_stream, send_stream, collapse_excgroups():
~~~~~~~~~~~~~~~~~~^^
File "/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/contextlib.py", line 162, in __exit__
self.gen.throw(value)
~~~~~~~~~~~~~~^^^^^^^
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_utils.py", line 85, in collapse_excgroups
raise exc
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py", line 193, in __call__
response = await self.dispatch_func(request, call_next)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/runner/work/stampbot/stampbot/stampbot/main.py", line 122, in metrics_middleware
duration = time.time() ^ start_time
~~~~~~~~~~~~^~~~~~~~~~~~
TypeError: unsupported operand type(s) for ^: 'float' and 'float'
During handling of the above exception, another exception occurred:
def test_lifespan_startup_shutdown_configured():
"""Test app lifespan startup and shutdown when configured."""
from stampbot.main import app
# Use TestClient as context manager to trigger lifespan events
with TestClient(app) as client:
# App should be running
> response = client.get("/health")
^^^^^^^^^^^^^^^^^^^^^
tests/test_main.py:20:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:473: in get
return super().get(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1053: in get
return self.request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:445: in request
return super().request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:825: in request
return self.send(request, auth=auth, follow_redirects=follow_redirects)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:914: in send
response = self._send_handling_auth(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:942: in _send_handling_auth
response = self._send_handling_redirects(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:979: in _send_handling_redirects
response = self._send_single_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1014: in _send_single_request
response = transport.handle_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:348: in handle_request
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:345: in handle_request
portal.call(self.app, scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:334: in call
return cast(T_Retval, self.start_task_soon(func, *args).result())
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:450: in result
return self.__get_result()
^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:395: in __get_result
raise self._exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:259: in _call_func
retval = await retval_or_awaitable
^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1135: in __call__
await super().__call__(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:107: in __call__
await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:186: in __call__
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:164: in __call__
await self.app(scope, receive, _send)
stampbot/main.py:205: in __call__
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:191: in __call__
with recv_stream, send_stream, collapse_excgroups():
^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/contextlib.py:162: in __exit__
self.gen.throw(value)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_utils.py:85: in collapse_excgroups
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:193: in __call__
response = await self.dispatch_func(request, call_next)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
request = <starlette.middleware.base._CachedRequest object at 0x7f1152b8b0e0>
call_next = <function BaseHTTPMiddleware.__call__.<locals>.call_next at 0x7f1152b8e820>
@app.middleware("http")
async def metrics_middleware(request: Request, call_next: Any) -> Response:
"""Middleware to track HTTP metrics.
Args:
request: Incoming HTTP request.
call_next: Next middleware or route handler.
Returns:
HTTP response from downstream handler.
"""
method = request.method
endpoint = request.url.path
# Track in-progress requests
http_requests_in_progress.labels(method=method, endpoint=endpoint).inc()
start_time = time.time()
try:
response = await call_next(request)
> duration = time.time() ^ start_time
^^^^^^^^^^^^^^^^^^^^^^^^
E TypeError: unsupported operand type(s) for ^: 'float' and 'float'
stampbot/main.py:122: TypeError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T20:06:16.338423Z[0m [[32m[1minfo [0m] [1mOpenTelemetry disabled [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.telemetry (INFO)>[0m [36m_name[0m=[35minfo[0m
[2m2026-05-16T20:06:16.357653Z[0m [[32m[1minfo [0m] [1mStarting stampbot on 0.0.0.0:8000[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.main (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'host': '0.0.0.0', 'port': 8000, 'log_level': 'INFO'}[0m
[2m2026-05-16T20:06:16.357956Z[0m [[32m[1minfo [0m] [1mGitHub App credentials configured successfully[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.main (INFO)>[0m [36m_name[0m=[35minfo[0m
[2m2026-05-16T20:06:16.360150Z[0m [[32m[1minfo [0m] [1mShutting down stampbot [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.main (INFO)>[0m [36m_name[0m=[35minfo[0m
=========================== short test summary info ============================
FAILED tests/test_main.py::test_lifespan_startup_shutdown_configured - TypeError: unsupported operand type(s) for ^: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 91 passed, 3 deselected in 1.07s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_BitXor, occurrence: 1
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -313,7 +313,7 @@
try:
start_time = time.time()
result = await webhook_handler.handle_event(x_github_event, payload)
- duration = time.time() - start_time
+ duration = time.time() ^ start_time
webhook_processing_duration_seconds.labels(event_type=x_github_event or "unknown").observe(
duration........................................................................ [ 34%]
.............................F
=================================== FAILURES ===================================
___________________________ test_webhook_valid_ping ____________________________
test_client = <starlette.testclient.TestClient object at 0x7f487d8b29c0>
def test_webhook_valid_ping(test_client: TestClient):
"""Test webhook accepts valid ping event."""
import hashlib
import hmac
from stampbot.webhook_handler import webhook_handler
body = json.dumps({"zen": "Design for failure."}).encode()
signature = (
"sha256="
+ hmac.new(
webhook_handler.webhook_secret,
body,
hashlib.sha256,
).hexdigest()
)
response = test_client.post(
"/webhook",
content=body,
headers={
"Content-Type": "application/json",
"X-GitHub-Event": "ping",
"X-Hub-Signature-256": signature,
},
)
> assert response.status_code == 200
E assert 500 == 200
E + where 500 = <Response [500 Internal Server Error]>.status_code
tests/test_main.py:175: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:49:01.674276Z[0m [[32m[1minfo [0m] [1mReceived ping event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mclient_ip[0m=[35mtestclient[0m [36mextra[0m=[35m{'event_type': 'ping', 'action': ''}[0m
[2m2026-05-16T19:49:01.674488Z[0m [[31m[1merror [0m] [1mError handling webhook event: unsupported operand type(s) for ^: 'float' and 'float'[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.main (INFO)>[0m [36m_name[0m=[35merror[0m [36mclient_ip[0m=[35mtestclient[0m [36mextra[0m=[35m{'error': "unsupported operand type(s) for ^: 'float' and 'float'"}[0m
[2m2026-05-16T19:49:01.675300Z[0m [[32m[1minfo [0m] [1mHTTP Request: POST http://testserver/webhook "HTTP/1.1 500 Internal Server Error"[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'ping', 'action': ''}, 'event': 'Received ping event', 'client_ip': 'testclient', 'level': 'info', 'timestamp': '2026-05-16T19:49:01.674276Z'}
ERROR stampbot.main:main.py:326 {'extra': {'error': "unsupported operand type(s) for ^: 'float' and 'float'"}, 'event': "Error handling webhook event: unsupported operand type(s) for ^: 'float' and 'float'", 'client_ip': 'testclient', 'level': 'error', 'timestamp': '2026-05-16T19:49:01.674488Z'}
INFO httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 500 Internal Server Error"
=========================== short test summary info ============================
FAILED tests/test_main.py::test_webhook_valid_ping - assert 500 == 200
+ where 500 = <Response [500 Internal Server Error]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 101 passed, 3 deselected in 0.85s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Mul_Add, occurrence: 0
--- mutation diff --- --- astampbot/main.py +++ bstampbot/main.py @@ -47,7 +47,7 @@ APP_VERSION = "0.1.0" # Security limits -MAX_WEBHOOK_BODY_SIZE = 1024 * 1024 # 1MB - GitHub webhooks are typically much smaller +MAX_WEBHOOK_BODY_SIZE = 1024 + 1024 # 1MB - GitHub webhooks are typically much smaller @asynccontextmanager
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.60s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Mul_Sub, occurrence: 0
--- mutation diff --- --- astampbot/main.py +++ bstampbot/main.py @@ -47,7 +47,7 @@ APP_VERSION = "0.1.0" # Security limits -MAX_WEBHOOK_BODY_SIZE = 1024 * 1024 # 1MB - GitHub webhooks are typically much smaller +MAX_WEBHOOK_BODY_SIZE = 1024 - 1024 # 1MB - GitHub webhooks are typically much smaller @asynccontextmanager
........................................................................ [ 34%]
........................F
=================================== FAILURES ===================================
_______________________ test_request_with_content_length _______________________
test_client = <starlette.testclient.TestClient object at 0x7f23219af770>
def test_request_with_content_length(test_client: TestClient):
"""Test that requests with Content-Length header are tracked."""
# POST with a body will have Content-Length set automatically
response = test_client.post(
"/webhook",
content=b'{"test": "data"}',
headers={
"Content-Type": "application/json",
"X-GitHub-Event": "ping",
"X-Hub-Signature-256": "sha256=invalid",
},
)
# Will fail signature validation, but Content-Length tracking happens before that
> assert response.status_code == 401
E assert 413 == 401
E + where 413 = <Response [413 Request Entity Too Large]>.status_code
tests/test_main.py:79: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:51:05.840813Z[0m [[32m[1minfo [0m] [1mHTTP Request: POST http://testserver/webhook "HTTP/1.1 413 Request Entity Too Large"[0m
------------------------------ Captured log call -------------------------------
INFO httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 413 Request Entity Too Large"
=========================== short test summary info ============================
FAILED tests/test_main.py::test_request_with_content_length - assert 413 == 401
+ where 413 = <Response [413 Request Entity Too Large]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 96 passed, 3 deselected in 0.83s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Mul_Div, occurrence: 0
--- mutation diff --- --- astampbot/main.py +++ bstampbot/main.py @@ -47,7 +47,7 @@ APP_VERSION = "0.1.0" # Security limits -MAX_WEBHOOK_BODY_SIZE = 1024 * 1024 # 1MB - GitHub webhooks are typically much smaller +MAX_WEBHOOK_BODY_SIZE = 1024 / 1024 # 1MB - GitHub webhooks are typically much smaller @asynccontextmanager
........................................................................ [ 34%]
........................F
=================================== FAILURES ===================================
_______________________ test_request_with_content_length _______________________
test_client = <starlette.testclient.TestClient object at 0x7fcd531af770>
def test_request_with_content_length(test_client: TestClient):
"""Test that requests with Content-Length header are tracked."""
# POST with a body will have Content-Length set automatically
response = test_client.post(
"/webhook",
content=b'{"test": "data"}',
headers={
"Content-Type": "application/json",
"X-GitHub-Event": "ping",
"X-Hub-Signature-256": "sha256=invalid",
},
)
# Will fail signature validation, but Content-Length tracking happens before that
> assert response.status_code == 401
E assert 413 == 401
E + where 413 = <Response [413 Request Entity Too Large]>.status_code
tests/test_main.py:79: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:37:27.304396Z[0m [[32m[1minfo [0m] [1mHTTP Request: POST http://testserver/webhook "HTTP/1.1 413 Request Entity Too Large"[0m
------------------------------ Captured log call -------------------------------
INFO httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 413 Request Entity Too Large"
=========================== short test summary info ============================
FAILED tests/test_main.py::test_request_with_content_length - assert 413 == 401
+ where 413 = <Response [413 Request Entity Too Large]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 96 passed, 3 deselected in 0.83s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Mul_FloorDiv, occurrence: 0
--- mutation diff --- --- astampbot/main.py +++ bstampbot/main.py @@ -47,7 +47,7 @@ APP_VERSION = "0.1.0" # Security limits -MAX_WEBHOOK_BODY_SIZE = 1024 * 1024 # 1MB - GitHub webhooks are typically much smaller +MAX_WEBHOOK_BODY_SIZE = 1024 // 1024 # 1MB - GitHub webhooks are typically much smaller @asynccontextmanager
........................................................................ [ 34%]
........................F
=================================== FAILURES ===================================
_______________________ test_request_with_content_length _______________________
test_client = <starlette.testclient.TestClient object at 0x7f98c14a3770>
def test_request_with_content_length(test_client: TestClient):
"""Test that requests with Content-Length header are tracked."""
# POST with a body will have Content-Length set automatically
response = test_client.post(
"/webhook",
content=b'{"test": "data"}',
headers={
"Content-Type": "application/json",
"X-GitHub-Event": "ping",
"X-Hub-Signature-256": "sha256=invalid",
},
)
# Will fail signature validation, but Content-Length tracking happens before that
> assert response.status_code == 401
E assert 413 == 401
E + where 413 = <Response [413 Request Entity Too Large]>.status_code
tests/test_main.py:79: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:37:41.664500Z[0m [[32m[1minfo [0m] [1mHTTP Request: POST http://testserver/webhook "HTTP/1.1 413 Request Entity Too Large"[0m
------------------------------ Captured log call -------------------------------
INFO httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 413 Request Entity Too Large"
=========================== short test summary info ============================
FAILED tests/test_main.py::test_request_with_content_length - assert 413 == 401
+ where 413 = <Response [413 Request Entity Too Large]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 96 passed, 3 deselected in 0.84s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Mul_Mod, occurrence: 0
--- mutation diff --- --- astampbot/main.py +++ bstampbot/main.py @@ -47,7 +47,7 @@ APP_VERSION = "0.1.0" # Security limits -MAX_WEBHOOK_BODY_SIZE = 1024 * 1024 # 1MB - GitHub webhooks are typically much smaller +MAX_WEBHOOK_BODY_SIZE = 1024 % 1024 # 1MB - GitHub webhooks are typically much smaller @asynccontextmanager
........................................................................ [ 34%]
........................F
=================================== FAILURES ===================================
_______________________ test_request_with_content_length _______________________
test_client = <starlette.testclient.TestClient object at 0x7f8f566b7770>
def test_request_with_content_length(test_client: TestClient):
"""Test that requests with Content-Length header are tracked."""
# POST with a body will have Content-Length set automatically
response = test_client.post(
"/webhook",
content=b'{"test": "data"}',
headers={
"Content-Type": "application/json",
"X-GitHub-Event": "ping",
"X-Hub-Signature-256": "sha256=invalid",
},
)
# Will fail signature validation, but Content-Length tracking happens before that
> assert response.status_code == 401
E assert 413 == 401
E + where 413 = <Response [413 Request Entity Too Large]>.status_code
tests/test_main.py:79: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T20:00:59.270681Z[0m [[32m[1minfo [0m] [1mHTTP Request: POST http://testserver/webhook "HTTP/1.1 413 Request Entity Too Large"[0m
------------------------------ Captured log call -------------------------------
INFO httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 413 Request Entity Too Large"
=========================== short test summary info ============================
FAILED tests/test_main.py::test_request_with_content_length - assert 413 == 401
+ where 413 = <Response [413 Request Entity Too Large]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 96 passed, 3 deselected in 0.83s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Mul_Pow, occurrence: 0
--- mutation diff --- --- astampbot/main.py +++ bstampbot/main.py @@ -47,7 +47,7 @@ APP_VERSION = "0.1.0" # Security limits -MAX_WEBHOOK_BODY_SIZE = 1024 * 1024 # 1MB - GitHub webhooks are typically much smaller +MAX_WEBHOOK_BODY_SIZE = 1024 ** 1024 # 1MB - GitHub webhooks are typically much smaller @asynccontextmanager
........................................................................ [ 34%]
..............................F
=================================== FAILURES ===================================
____________________ test_webhook_content_length_too_large _____________________
test_client = <starlette.testclient.TestClient object at 0x7f2db71b7020>
def test_webhook_content_length_too_large(test_client: TestClient):
"""Test webhook rejects requests with Content-Length exceeding limit."""
response = test_client.post(
"/webhook",
content=b"x", # Small actual body
headers={
"Content-Type": "application/json",
"Content-Length": "20000000", # 20MB - exceeds 10MB limit
"X-GitHub-Event": "ping",
"X-Hub-Signature-256": "sha256=fake",
},
)
> assert response.status_code == 413
E assert 401 == 413
E + where 401 = <Response [401 Unauthorized]>.status_code
tests/test_main.py:193: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T20:10:06.501808Z[0m [[33m[1mwarning [0m] [1mInvalid webhook signature [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.main (INFO)>[0m [36m_name[0m=[35mwarning[0m [36mclient_ip[0m=[35mtestclient[0m
[2m2026-05-16T20:10:06.502615Z[0m [[32m[1minfo [0m] [1mHTTP Request: POST http://testserver/webhook "HTTP/1.1 401 Unauthorized"[0m
------------------------------ Captured log call -------------------------------
WARNING stampbot.main:main.py:299 {'event': 'Invalid webhook signature', 'client_ip': 'testclient', 'level': 'warning', 'timestamp': '2026-05-16T20:10:06.501808Z'}
INFO httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 401 Unauthorized"
=========================== short test summary info ============================
FAILED tests/test_main.py::test_webhook_content_length_too_large - assert 401 == 413
+ where 401 = <Response [401 Unauthorized]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 102 passed, 3 deselected in 0.89s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Mul_RShift, occurrence: 0
--- mutation diff --- --- astampbot/main.py +++ bstampbot/main.py @@ -47,7 +47,7 @@ APP_VERSION = "0.1.0" # Security limits -MAX_WEBHOOK_BODY_SIZE = 1024 * 1024 # 1MB - GitHub webhooks are typically much smaller +MAX_WEBHOOK_BODY_SIZE = 1024 >> 1024 # 1MB - GitHub webhooks are typically much smaller @asynccontextmanager
........................................................................ [ 34%]
........................F
=================================== FAILURES ===================================
_______________________ test_request_with_content_length _______________________
test_client = <starlette.testclient.TestClient object at 0x7f437419f770>
def test_request_with_content_length(test_client: TestClient):
"""Test that requests with Content-Length header are tracked."""
# POST with a body will have Content-Length set automatically
response = test_client.post(
"/webhook",
content=b'{"test": "data"}',
headers={
"Content-Type": "application/json",
"X-GitHub-Event": "ping",
"X-Hub-Signature-256": "sha256=invalid",
},
)
# Will fail signature validation, but Content-Length tracking happens before that
> assert response.status_code == 401
E assert 413 == 401
E + where 413 = <Response [413 Request Entity Too Large]>.status_code
tests/test_main.py:79: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:53:09.968057Z[0m [[32m[1minfo [0m] [1mHTTP Request: POST http://testserver/webhook "HTTP/1.1 413 Request Entity Too Large"[0m
------------------------------ Captured log call -------------------------------
INFO httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 413 Request Entity Too Large"
=========================== short test summary info ============================
FAILED tests/test_main.py::test_request_with_content_length - assert 413 == 401
+ where 413 = <Response [413 Request Entity Too Large]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 96 passed, 3 deselected in 0.85s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Mul_LShift, occurrence: 0
--- mutation diff --- --- astampbot/main.py +++ bstampbot/main.py @@ -47,7 +47,7 @@ APP_VERSION = "0.1.0" # Security limits -MAX_WEBHOOK_BODY_SIZE = 1024 * 1024 # 1MB - GitHub webhooks are typically much smaller +MAX_WEBHOOK_BODY_SIZE = 1024 << 1024 # 1MB - GitHub webhooks are typically much smaller @asynccontextmanager
........................................................................ [ 34%]
..............................F
=================================== FAILURES ===================================
____________________ test_webhook_content_length_too_large _____________________
test_client = <starlette.testclient.TestClient object at 0x7f19ecbc3020>
def test_webhook_content_length_too_large(test_client: TestClient):
"""Test webhook rejects requests with Content-Length exceeding limit."""
response = test_client.post(
"/webhook",
content=b"x", # Small actual body
headers={
"Content-Type": "application/json",
"Content-Length": "20000000", # 20MB - exceeds 10MB limit
"X-GitHub-Event": "ping",
"X-Hub-Signature-256": "sha256=fake",
},
)
> assert response.status_code == 413
E assert 401 == 413
E + where 401 = <Response [401 Unauthorized]>.status_code
tests/test_main.py:193: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:52:13.268524Z[0m [[33m[1mwarning [0m] [1mInvalid webhook signature [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.main (INFO)>[0m [36m_name[0m=[35mwarning[0m [36mclient_ip[0m=[35mtestclient[0m
[2m2026-05-16T19:52:13.269334Z[0m [[32m[1minfo [0m] [1mHTTP Request: POST http://testserver/webhook "HTTP/1.1 401 Unauthorized"[0m
------------------------------ Captured log call -------------------------------
WARNING stampbot.main:main.py:299 {'event': 'Invalid webhook signature', 'client_ip': 'testclient', 'level': 'warning', 'timestamp': '2026-05-16T19:52:13.268524Z'}
INFO httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 401 Unauthorized"
=========================== short test summary info ============================
FAILED tests/test_main.py::test_webhook_content_length_too_large - assert 401 == 413
+ where 401 = <Response [401 Unauthorized]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 102 passed, 3 deselected in 0.85s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Mul_BitOr, occurrence: 0
--- mutation diff --- --- astampbot/main.py +++ bstampbot/main.py @@ -47,7 +47,7 @@ APP_VERSION = "0.1.0" # Security limits -MAX_WEBHOOK_BODY_SIZE = 1024 * 1024 # 1MB - GitHub webhooks are typically much smaller +MAX_WEBHOOK_BODY_SIZE = 1024 | 1024 # 1MB - GitHub webhooks are typically much smaller @asynccontextmanager
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Mul_BitAnd, occurrence: 0
--- mutation diff --- --- astampbot/main.py +++ bstampbot/main.py @@ -47,7 +47,7 @@ APP_VERSION = "0.1.0" # Security limits -MAX_WEBHOOK_BODY_SIZE = 1024 * 1024 # 1MB - GitHub webhooks are typically much smaller +MAX_WEBHOOK_BODY_SIZE = 1024 & 1024 # 1MB - GitHub webhooks are typically much smaller @asynccontextmanager
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Mul_BitXor, occurrence: 0
--- mutation diff --- --- astampbot/main.py +++ bstampbot/main.py @@ -47,7 +47,7 @@ APP_VERSION = "0.1.0" # Security limits -MAX_WEBHOOK_BODY_SIZE = 1024 * 1024 # 1MB - GitHub webhooks are typically much smaller +MAX_WEBHOOK_BODY_SIZE = 1024 ^ 1024 # 1MB - GitHub webhooks are typically much smaller @asynccontextmanager
........................................................................ [ 34%]
........................F
=================================== FAILURES ===================================
_______________________ test_request_with_content_length _______________________
test_client = <starlette.testclient.TestClient object at 0x7ffb234a7770>
def test_request_with_content_length(test_client: TestClient):
"""Test that requests with Content-Length header are tracked."""
# POST with a body will have Content-Length set automatically
response = test_client.post(
"/webhook",
content=b'{"test": "data"}',
headers={
"Content-Type": "application/json",
"X-GitHub-Event": "ping",
"X-Hub-Signature-256": "sha256=invalid",
},
)
# Will fail signature validation, but Content-Length tracking happens before that
> assert response.status_code == 401
E assert 413 == 401
E + where 413 = <Response [413 Request Entity Too Large]>.status_code
tests/test_main.py:79: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T20:09:45.177801Z[0m [[32m[1minfo [0m] [1mHTTP Request: POST http://testserver/webhook "HTTP/1.1 413 Request Entity Too Large"[0m
------------------------------ Captured log call -------------------------------
INFO httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 413 Request Entity Too Large"
=========================== short test summary info ============================
FAILED tests/test_main.py::test_request_with_content_length - assert 413 == 401
+ where 413 = <Response [413 Request Entity Too Large]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 96 passed, 3 deselected in 0.88s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceComparisonOperator_Eq_NotEq, occurrence: 0
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -183,7 +183,7 @@
receive: ASGI receive callable.
send: ASGI send callable.
"""
- if scope["type"] == "http":
+ if scope["type"] != "http":
structlog.contextvars.clear_contextvars()
client_ip: str | None = None........................................................................ [ 34%]
...................F
=================================== FAILURES ===================================
__________________ test_lifespan_startup_shutdown_configured ___________________
def test_lifespan_startup_shutdown_configured():
"""Test app lifespan startup and shutdown when configured."""
from stampbot.main import app
# Use TestClient as context manager to trigger lifespan events
> with TestClient(app) as client:
^^^^^^^^^^^^^^^
tests/test_main.py:18:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:688: in __enter__
portal.call(self.wait_startup)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:334: in call
return cast(T_Retval, self.start_task_soon(func, *args).result())
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:443: in result
return self.__get_result()
^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:395: in __get_result
raise self._exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:259: in _call_func
retval = await retval_or_awaitable
^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:717: in wait_startup
message = await receive()
^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:714: in receive
self.task.result()
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:443: in result
return self.__get_result()
^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:395: in __get_result
raise self._exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:259: in _call_func
retval = await retval_or_awaitable
^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:704: in lifespan
await self.app(scope, self.stream_receive.receive, self.stream_send.send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1135: in __call__
await super().__call__(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:107: in __call__
await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:151: in __call__
await self.app(scope, receive, send)
stampbot/main.py:193: in __call__
raw = Request(scope).headers.get(client_ip_header)
^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/requests.py:204: in __init__
super().__init__(scope)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <starlette.requests.Request object at 0x7f2ad4166900>
scope = {'app': <fastapi.applications.FastAPI object at 0x7f2ad43970e0>, 'state': {}, 'type': 'lifespan'}
receive = None
def __init__(self, scope: Scope, receive: Receive | None = None) -> None:
> assert scope["type"] in ("http", "websocket")
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E AssertionError
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/requests.py:78: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:28:24.668944Z[0m [[32m[1minfo [0m] [1mOpenTelemetry disabled [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.telemetry (INFO)>[0m [36m_name[0m=[35minfo[0m
=========================== short test summary info ============================
FAILED tests/test_main.py::test_lifespan_startup_shutdown_configured - AssertionError
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 91 passed, 3 deselected in 0.95s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceComparisonOperator_Eq_Lt, occurrence: 0
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -183,7 +183,7 @@
receive: ASGI receive callable.
send: ASGI send callable.
"""
- if scope["type"] == "http":
+ if scope["type"] < "http":
structlog.contextvars.clear_contextvars()
client_ip: str | None = None........................................................................ [ 34%]
...................................F
=================================== FAILURES ===================================
______________ test_logging_middleware_respects_configured_header ______________
def test_logging_middleware_respects_configured_header():
"""Test that logging_middleware uses the configured client_ip_header setting."""
from unittest.mock import patch
import structlog.contextvars
from stampbot.main import app
with patch("stampbot.main.settings") as mock_settings:
mock_settings.get = lambda key, default=None: (
"X-Real-IP" if key == "client_ip_header" else default
)
client = TestClient(app)
captured: dict[str, str | None] = {}
original_bind = structlog.contextvars.bind_contextvars
def capturing_bind(**kw: object) -> None:
captured.update({k: str(v) for k, v in kw.items()})
original_bind(**kw)
with patch("stampbot.main.structlog.contextvars.bind_contextvars", capturing_bind):
client.get("/health", headers={"X-Real-IP": "198.51.100.7"})
> assert captured.get("client_ip") == "198.51.100.7"
E AssertionError: assert None == '198.51.100.7'
E + where None = <built-in method get of dict object at 0x7f75de913780>('client_ip')
E + where <built-in method get of dict object at 0x7f75de913780> = {}.get
tests/test_main.py:296: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:39:36.573772Z[0m [[32m[1minfo [0m] [1mHTTP Request: GET http://testserver/health "HTTP/1.1 200 OK"[0m
------------------------------ Captured log call -------------------------------
INFO httpx:_client.py:1025 HTTP Request: GET http://testserver/health "HTTP/1.1 200 OK"
=========================== short test summary info ============================
FAILED tests/test_main.py::test_logging_middleware_respects_configured_header - AssertionError: assert None == '198.51.100.7'
+ where None = <built-in method get of dict object at 0x7f75de913780>('client_ip')
+ where <built-in method get of dict object at 0x7f75de913780> = {}.get
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 107 passed, 3 deselected in 0.86s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceComparisonOperator_Eq_LtE, occurrence: 0
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -183,7 +183,7 @@
receive: ASGI receive callable.
send: ASGI send callable.
"""
- if scope["type"] == "http":
+ if scope["type"] <= "http":
structlog.contextvars.clear_contextvars()
client_ip: str | None = None........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.62s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceComparisonOperator_Eq_Gt, occurrence: 0
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -183,7 +183,7 @@
receive: ASGI receive callable.
send: ASGI send callable.
"""
- if scope["type"] == "http":
+ if scope["type"] > "http":
structlog.contextvars.clear_contextvars()
client_ip: str | None = None........................................................................ [ 34%]
...................F
=================================== FAILURES ===================================
__________________ test_lifespan_startup_shutdown_configured ___________________
def test_lifespan_startup_shutdown_configured():
"""Test app lifespan startup and shutdown when configured."""
from stampbot.main import app
# Use TestClient as context manager to trigger lifespan events
> with TestClient(app) as client:
^^^^^^^^^^^^^^^
tests/test_main.py:18:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:688: in __enter__
portal.call(self.wait_startup)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:334: in call
return cast(T_Retval, self.start_task_soon(func, *args).result())
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:443: in result
return self.__get_result()
^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:395: in __get_result
raise self._exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:259: in _call_func
retval = await retval_or_awaitable
^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:717: in wait_startup
message = await receive()
^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:714: in receive
self.task.result()
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:443: in result
return self.__get_result()
^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:395: in __get_result
raise self._exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:259: in _call_func
retval = await retval_or_awaitable
^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:704: in lifespan
await self.app(scope, self.stream_receive.receive, self.stream_send.send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1135: in __call__
await super().__call__(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:107: in __call__
await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:151: in __call__
await self.app(scope, receive, send)
stampbot/main.py:193: in __call__
raw = Request(scope).headers.get(client_ip_header)
^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/requests.py:204: in __init__
super().__init__(scope)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <starlette.requests.Request object at 0x7fadf3b72900>
scope = {'app': <fastapi.applications.FastAPI object at 0x7fadf3da30e0>, 'state': {}, 'type': 'lifespan'}
receive = None
def __init__(self, scope: Scope, receive: Receive | None = None) -> None:
> assert scope["type"] in ("http", "websocket")
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E AssertionError
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/requests.py:78: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:51:58.150502Z[0m [[32m[1minfo [0m] [1mOpenTelemetry disabled [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.telemetry (INFO)>[0m [36m_name[0m=[35minfo[0m
=========================== short test summary info ============================
FAILED tests/test_main.py::test_lifespan_startup_shutdown_configured - AssertionError
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 91 passed, 3 deselected in 0.95s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceComparisonOperator_Eq_GtE, occurrence: 0
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -183,7 +183,7 @@
receive: ASGI receive callable.
send: ASGI send callable.
"""
- if scope["type"] == "http":
+ if scope["type"] >= "http":
structlog.contextvars.clear_contextvars()
client_ip: str | None = None........................................................................ [ 34%]
...................F
=================================== FAILURES ===================================
__________________ test_lifespan_startup_shutdown_configured ___________________
def test_lifespan_startup_shutdown_configured():
"""Test app lifespan startup and shutdown when configured."""
from stampbot.main import app
# Use TestClient as context manager to trigger lifespan events
> with TestClient(app) as client:
^^^^^^^^^^^^^^^
tests/test_main.py:18:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:688: in __enter__
portal.call(self.wait_startup)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:334: in call
return cast(T_Retval, self.start_task_soon(func, *args).result())
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:450: in result
return self.__get_result()
^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:395: in __get_result
raise self._exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:259: in _call_func
retval = await retval_or_awaitable
^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:717: in wait_startup
message = await receive()
^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:714: in receive
self.task.result()
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:443: in result
return self.__get_result()
^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:395: in __get_result
raise self._exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:259: in _call_func
retval = await retval_or_awaitable
^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:704: in lifespan
await self.app(scope, self.stream_receive.receive, self.stream_send.send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1135: in __call__
await super().__call__(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:107: in __call__
await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:151: in __call__
await self.app(scope, receive, send)
stampbot/main.py:193: in __call__
raw = Request(scope).headers.get(client_ip_header)
^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/requests.py:204: in __init__
super().__init__(scope)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <starlette.requests.Request object at 0x7f069096a900>
scope = {'app': <fastapi.applications.FastAPI object at 0x7f0690b9b0e0>, 'state': {}, 'type': 'lifespan'}
receive = None
def __init__(self, scope: Scope, receive: Receive | None = None) -> None:
> assert scope["type"] in ("http", "websocket")
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E AssertionError
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/requests.py:78: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:53:02.020161Z[0m [[32m[1minfo [0m] [1mOpenTelemetry disabled [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.telemetry (INFO)>[0m [36m_name[0m=[35minfo[0m
=========================== short test summary info ============================
FAILED tests/test_main.py::test_lifespan_startup_shutdown_configured - AssertionError
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 91 passed, 3 deselected in 0.98s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceComparisonOperator_Eq_Is, occurrence: 0
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -183,7 +183,7 @@
receive: ASGI receive callable.
send: ASGI send callable.
"""
- if scope["type"] == "http":
+ if scope["type"] is "http":
structlog.contextvars.clear_contextvars()
client_ip: str | None = None........................................................................ [ 34%]
........................................................................ [ 68%]
................................................................... [100%]
=============================== warnings summary ===============================
tests/test_main.py::test_lifespan_startup_shutdown_configured
/home/runner/work/stampbot/stampbot/stampbot/main.py:186: SyntaxWarning: "is" with 'str' literal. Did you mean "=="?
if scope["type"] is "http":
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
211 passed, 3 deselected, 1 warning in 1.64s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceComparisonOperator_Eq_IsNot, occurrence: 0
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -183,7 +183,7 @@
receive: ASGI receive callable.
send: ASGI send callable.
"""
- if scope["type"] == "http":
+ if scope["type"] is not "http":
structlog.contextvars.clear_contextvars()
client_ip: str | None = None........................................................................ [ 34%]
...................F
=================================== FAILURES ===================================
__________________ test_lifespan_startup_shutdown_configured ___________________
def test_lifespan_startup_shutdown_configured():
"""Test app lifespan startup and shutdown when configured."""
from stampbot.main import app
# Use TestClient as context manager to trigger lifespan events
> with TestClient(app) as client:
^^^^^^^^^^^^^^^
tests/test_main.py:18:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:688: in __enter__
portal.call(self.wait_startup)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:334: in call
return cast(T_Retval, self.start_task_soon(func, *args).result())
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:443: in result
return self.__get_result()
^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:395: in __get_result
raise self._exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:259: in _call_func
retval = await retval_or_awaitable
^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:717: in wait_startup
message = await receive()
^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:714: in receive
self.task.result()
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:443: in result
return self.__get_result()
^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:395: in __get_result
raise self._exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:259: in _call_func
retval = await retval_or_awaitable
^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:704: in lifespan
await self.app(scope, self.stream_receive.receive, self.stream_send.send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1135: in __call__
await super().__call__(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:107: in __call__
await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:151: in __call__
await self.app(scope, receive, send)
stampbot/main.py:193: in __call__
raw = Request(scope).headers.get(client_ip_header)
^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/requests.py:204: in __init__
super().__init__(scope)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <starlette.requests.Request object at 0x7fb671c8aa50>
scope = {'app': <fastapi.applications.FastAPI object at 0x7fb671ebf230>, 'state': {}, 'type': 'lifespan'}
receive = None
def __init__(self, scope: Scope, receive: Receive | None = None) -> None:
> assert scope["type"] in ("http", "websocket")
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E AssertionError
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/requests.py:78: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:53:22.134593Z[0m [[32m[1minfo [0m] [1mOpenTelemetry disabled [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.telemetry (INFO)>[0m [36m_name[0m=[35minfo[0m
=============================== warnings summary ===============================
tests/test_main.py::test_lifespan_startup_shutdown_configured
/home/runner/work/stampbot/stampbot/stampbot/main.py:186: SyntaxWarning: "is not" with 'str' literal. Did you mean "!="?
if scope["type"] is not "http":
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_lifespan_startup_shutdown_configured - AssertionError
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 91 passed, 3 deselected, 1 warning in 0.96s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceComparisonOperator_Gt_Eq, occurrence: 0
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -280,7 +280,7 @@
# Check content length before reading body to prevent memory exhaustion
content_length = request.headers.get("content-length")
- if content_length and int(content_length) > MAX_WEBHOOK_BODY_SIZE:
+ if content_length and int(content_length) == MAX_WEBHOOK_BODY_SIZE:
errors_total.labels(error_type="payload_too_large").inc()
raise HTTPException(status_code=413, detail="Request body too large")
........................................................................ [ 34%]
..............................F
=================================== FAILURES ===================================
____________________ test_webhook_content_length_too_large _____________________
test_client = <starlette.testclient.TestClient object at 0x7f7be7c87020>
def test_webhook_content_length_too_large(test_client: TestClient):
"""Test webhook rejects requests with Content-Length exceeding limit."""
response = test_client.post(
"/webhook",
content=b"x", # Small actual body
headers={
"Content-Type": "application/json",
"Content-Length": "20000000", # 20MB - exceeds 10MB limit
"X-GitHub-Event": "ping",
"X-Hub-Signature-256": "sha256=fake",
},
)
> assert response.status_code == 413
E assert 401 == 413
E + where 401 = <Response [401 Unauthorized]>.status_code
tests/test_main.py:193: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T20:09:04.280737Z[0m [[33m[1mwarning [0m] [1mInvalid webhook signature [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.main (INFO)>[0m [36m_name[0m=[35mwarning[0m [36mclient_ip[0m=[35mtestclient[0m
[2m2026-05-16T20:09:04.281544Z[0m [[32m[1minfo [0m] [1mHTTP Request: POST http://testserver/webhook "HTTP/1.1 401 Unauthorized"[0m
------------------------------ Captured log call -------------------------------
WARNING stampbot.main:main.py:299 {'event': 'Invalid webhook signature', 'client_ip': 'testclient', 'level': 'warning', 'timestamp': '2026-05-16T20:09:04.280737Z'}
INFO httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 401 Unauthorized"
=========================== short test summary info ============================
FAILED tests/test_main.py::test_webhook_content_length_too_large - assert 401 == 413
+ where 401 = <Response [401 Unauthorized]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 102 passed, 3 deselected in 0.91s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceComparisonOperator_Gt_Eq, occurrence: 1
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -288,7 +288,7 @@
body = await request.body()
# Double-check actual body size (in case content-length was missing/incorrect)
- if len(body) > MAX_WEBHOOK_BODY_SIZE:
+ if len(body) == MAX_WEBHOOK_BODY_SIZE:
errors_total.labels(error_type="payload_too_large").inc()
raise HTTPException(status_code=413, detail="Request body too large")
........................................................................ [ 34%]
...............................F
=================================== FAILURES ===================================
_________________________ test_webhook_body_too_large __________________________
test_client = <starlette.testclient.TestClient object at 0x7f9382fb78a0>
def test_webhook_body_too_large(test_client: TestClient):
"""Test webhook rejects requests where actual body exceeds limit.
This tests the secondary body size check (lines 231-232) which catches
cases where Content-Length header is missing or incorrect.
"""
# Create a body larger than MAX_WEBHOOK_BODY_SIZE (10MB)
large_body = b"x" * (10 * 1024 * 1024 + 1) # 10MB + 1 byte
# Set Content-Length to a small value to bypass the header check (line 222)
# but the actual body size check (line 230) should still catch it
response = test_client.post(
"/webhook",
content=large_body,
headers={
"Content-Type": "application/json",
"Content-Length": "100", # Lie about size to bypass first check
"X-GitHub-Event": "ping",
"X-Hub-Signature-256": "sha256=fake",
},
)
> assert response.status_code == 413
E assert 401 == 413
E + where 401 = <Response [401 Unauthorized]>.status_code
tests/test_main.py:218: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:54:19.970225Z[0m [[33m[1mwarning [0m] [1mInvalid webhook signature [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.main (INFO)>[0m [36m_name[0m=[35mwarning[0m [36mclient_ip[0m=[35mtestclient[0m
[2m2026-05-16T19:54:19.971700Z[0m [[32m[1minfo [0m] [1mHTTP Request: POST http://testserver/webhook "HTTP/1.1 401 Unauthorized"[0m
------------------------------ Captured log call -------------------------------
WARNING stampbot.main:main.py:299 {'event': 'Invalid webhook signature', 'client_ip': 'testclient', 'level': 'warning', 'timestamp': '2026-05-16T19:54:19.970225Z'}
INFO httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 401 Unauthorized"
=========================== short test summary info ============================
FAILED tests/test_main.py::test_webhook_body_too_large - assert 401 == 413
+ where 401 = <Response [401 Unauthorized]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 103 passed, 3 deselected in 0.89s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceComparisonOperator_Gt_NotEq, occurrence: 0
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -280,7 +280,7 @@
# Check content length before reading body to prevent memory exhaustion
content_length = request.headers.get("content-length")
- if content_length and int(content_length) > MAX_WEBHOOK_BODY_SIZE:
+ if content_length and int(content_length) != MAX_WEBHOOK_BODY_SIZE:
errors_total.labels(error_type="payload_too_large").inc()
raise HTTPException(status_code=413, detail="Request body too large")
........................................................................ [ 34%]
........................F
=================================== FAILURES ===================================
_______________________ test_request_with_content_length _______________________
test_client = <starlette.testclient.TestClient object at 0x7f4e908b7770>
def test_request_with_content_length(test_client: TestClient):
"""Test that requests with Content-Length header are tracked."""
# POST with a body will have Content-Length set automatically
response = test_client.post(
"/webhook",
content=b'{"test": "data"}',
headers={
"Content-Type": "application/json",
"X-GitHub-Event": "ping",
"X-Hub-Signature-256": "sha256=invalid",
},
)
# Will fail signature validation, but Content-Length tracking happens before that
> assert response.status_code == 401
E assert 413 == 401
E + where 413 = <Response [413 Request Entity Too Large]>.status_code
tests/test_main.py:79: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T20:05:18.836585Z[0m [[32m[1minfo [0m] [1mHTTP Request: POST http://testserver/webhook "HTTP/1.1 413 Request Entity Too Large"[0m
------------------------------ Captured log call -------------------------------
INFO httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 413 Request Entity Too Large"
=========================== short test summary info ============================
FAILED tests/test_main.py::test_request_with_content_length - assert 413 == 401
+ where 413 = <Response [413 Request Entity Too Large]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 96 passed, 3 deselected in 0.85s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceComparisonOperator_Gt_NotEq, occurrence: 1
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -288,7 +288,7 @@
body = await request.body()
# Double-check actual body size (in case content-length was missing/incorrect)
- if len(body) > MAX_WEBHOOK_BODY_SIZE:
+ if len(body) != MAX_WEBHOOK_BODY_SIZE:
errors_total.labels(error_type="payload_too_large").inc()
raise HTTPException(status_code=413, detail="Request body too large")
........................................................................ [ 34%]
........................F
=================================== FAILURES ===================================
_______________________ test_request_with_content_length _______________________
test_client = <starlette.testclient.TestClient object at 0x7f3d70d97770>
def test_request_with_content_length(test_client: TestClient):
"""Test that requests with Content-Length header are tracked."""
# POST with a body will have Content-Length set automatically
response = test_client.post(
"/webhook",
content=b'{"test": "data"}',
headers={
"Content-Type": "application/json",
"X-GitHub-Event": "ping",
"X-Hub-Signature-256": "sha256=invalid",
},
)
# Will fail signature validation, but Content-Length tracking happens before that
> assert response.status_code == 401
E assert 413 == 401
E + where 413 = <Response [413 Request Entity Too Large]>.status_code
tests/test_main.py:79: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:27:44.779742Z[0m [[32m[1minfo [0m] [1mHTTP Request: POST http://testserver/webhook "HTTP/1.1 413 Request Entity Too Large"[0m
------------------------------ Captured log call -------------------------------
INFO httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 413 Request Entity Too Large"
=========================== short test summary info ============================
FAILED tests/test_main.py::test_request_with_content_length - assert 413 == 401
+ where 413 = <Response [413 Request Entity Too Large]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 96 passed, 3 deselected in 0.82s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceComparisonOperator_Gt_Lt, occurrence: 0
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -280,7 +280,7 @@
# Check content length before reading body to prevent memory exhaustion
content_length = request.headers.get("content-length")
- if content_length and int(content_length) > MAX_WEBHOOK_BODY_SIZE:
+ if content_length and int(content_length) < MAX_WEBHOOK_BODY_SIZE:
errors_total.labels(error_type="payload_too_large").inc()
raise HTTPException(status_code=413, detail="Request body too large")
........................................................................ [ 34%]
........................F
=================================== FAILURES ===================================
_______________________ test_request_with_content_length _______________________
test_client = <starlette.testclient.TestClient object at 0x7f76738a7770>
def test_request_with_content_length(test_client: TestClient):
"""Test that requests with Content-Length header are tracked."""
# POST with a body will have Content-Length set automatically
response = test_client.post(
"/webhook",
content=b'{"test": "data"}',
headers={
"Content-Type": "application/json",
"X-GitHub-Event": "ping",
"X-Hub-Signature-256": "sha256=invalid",
},
)
# Will fail signature validation, but Content-Length tracking happens before that
> assert response.status_code == 401
E assert 413 == 401
E + where 413 = <Response [413 Request Entity Too Large]>.status_code
tests/test_main.py:79: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:23:07.935030Z[0m [[32m[1minfo [0m] [1mHTTP Request: POST http://testserver/webhook "HTTP/1.1 413 Request Entity Too Large"[0m
------------------------------ Captured log call -------------------------------
INFO httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 413 Request Entity Too Large"
=========================== short test summary info ============================
FAILED tests/test_main.py::test_request_with_content_length - assert 413 == 401
+ where 413 = <Response [413 Request Entity Too Large]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 96 passed, 3 deselected in 0.84s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceComparisonOperator_Gt_Lt, occurrence: 1
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -288,7 +288,7 @@
body = await request.body()
# Double-check actual body size (in case content-length was missing/incorrect)
- if len(body) > MAX_WEBHOOK_BODY_SIZE:
+ if len(body) < MAX_WEBHOOK_BODY_SIZE:
errors_total.labels(error_type="payload_too_large").inc()
raise HTTPException(status_code=413, detail="Request body too large")
........................................................................ [ 34%]
........................F
=================================== FAILURES ===================================
_______________________ test_request_with_content_length _______________________
test_client = <starlette.testclient.TestClient object at 0x7f4665aa3770>
def test_request_with_content_length(test_client: TestClient):
"""Test that requests with Content-Length header are tracked."""
# POST with a body will have Content-Length set automatically
response = test_client.post(
"/webhook",
content=b'{"test": "data"}',
headers={
"Content-Type": "application/json",
"X-GitHub-Event": "ping",
"X-Hub-Signature-256": "sha256=invalid",
},
)
# Will fail signature validation, but Content-Length tracking happens before that
> assert response.status_code == 401
E assert 413 == 401
E + where 413 = <Response [413 Request Entity Too Large]>.status_code
tests/test_main.py:79: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:34:56.107122Z[0m [[32m[1minfo [0m] [1mHTTP Request: POST http://testserver/webhook "HTTP/1.1 413 Request Entity Too Large"[0m
------------------------------ Captured log call -------------------------------
INFO httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 413 Request Entity Too Large"
=========================== short test summary info ============================
FAILED tests/test_main.py::test_request_with_content_length - assert 413 == 401
+ where 413 = <Response [413 Request Entity Too Large]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 96 passed, 3 deselected in 0.82s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceComparisonOperator_Gt_LtE, occurrence: 0
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -280,7 +280,7 @@
# Check content length before reading body to prevent memory exhaustion
content_length = request.headers.get("content-length")
- if content_length and int(content_length) > MAX_WEBHOOK_BODY_SIZE:
+ if content_length and int(content_length) <= MAX_WEBHOOK_BODY_SIZE:
errors_total.labels(error_type="payload_too_large").inc()
raise HTTPException(status_code=413, detail="Request body too large")
........................................................................ [ 34%]
........................F
=================================== FAILURES ===================================
_______________________ test_request_with_content_length _______________________
test_client = <starlette.testclient.TestClient object at 0x7fa7259af770>
def test_request_with_content_length(test_client: TestClient):
"""Test that requests with Content-Length header are tracked."""
# POST with a body will have Content-Length set automatically
response = test_client.post(
"/webhook",
content=b'{"test": "data"}',
headers={
"Content-Type": "application/json",
"X-GitHub-Event": "ping",
"X-Hub-Signature-256": "sha256=invalid",
},
)
# Will fail signature validation, but Content-Length tracking happens before that
> assert response.status_code == 401
E assert 413 == 401
E + where 413 = <Response [413 Request Entity Too Large]>.status_code
tests/test_main.py:79: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:28:39.847705Z[0m [[32m[1minfo [0m] [1mHTTP Request: POST http://testserver/webhook "HTTP/1.1 413 Request Entity Too Large"[0m
------------------------------ Captured log call -------------------------------
INFO httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 413 Request Entity Too Large"
=========================== short test summary info ============================
FAILED tests/test_main.py::test_request_with_content_length - assert 413 == 401
+ where 413 = <Response [413 Request Entity Too Large]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 96 passed, 3 deselected in 0.84s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceComparisonOperator_Gt_LtE, occurrence: 1
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -288,7 +288,7 @@
body = await request.body()
# Double-check actual body size (in case content-length was missing/incorrect)
- if len(body) > MAX_WEBHOOK_BODY_SIZE:
+ if len(body) <= MAX_WEBHOOK_BODY_SIZE:
errors_total.labels(error_type="payload_too_large").inc()
raise HTTPException(status_code=413, detail="Request body too large")
........................................................................ [ 34%]
........................F
=================================== FAILURES ===================================
_______________________ test_request_with_content_length _______________________
test_client = <starlette.testclient.TestClient object at 0x7ff54f3b3770>
def test_request_with_content_length(test_client: TestClient):
"""Test that requests with Content-Length header are tracked."""
# POST with a body will have Content-Length set automatically
response = test_client.post(
"/webhook",
content=b'{"test": "data"}',
headers={
"Content-Type": "application/json",
"X-GitHub-Event": "ping",
"X-Hub-Signature-256": "sha256=invalid",
},
)
# Will fail signature validation, but Content-Length tracking happens before that
> assert response.status_code == 401
E assert 413 == 401
E + where 413 = <Response [413 Request Entity Too Large]>.status_code
tests/test_main.py:79: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:52:52.279762Z[0m [[32m[1minfo [0m] [1mHTTP Request: POST http://testserver/webhook "HTTP/1.1 413 Request Entity Too Large"[0m
------------------------------ Captured log call -------------------------------
INFO httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 413 Request Entity Too Large"
=========================== short test summary info ============================
FAILED tests/test_main.py::test_request_with_content_length - assert 413 == 401
+ where 413 = <Response [413 Request Entity Too Large]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 96 passed, 3 deselected in 0.85s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceComparisonOperator_Gt_GtE, occurrence: 0
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -280,7 +280,7 @@
# Check content length before reading body to prevent memory exhaustion
content_length = request.headers.get("content-length")
- if content_length and int(content_length) > MAX_WEBHOOK_BODY_SIZE:
+ if content_length and int(content_length) >= MAX_WEBHOOK_BODY_SIZE:
errors_total.labels(error_type="payload_too_large").inc()
raise HTTPException(status_code=413, detail="Request body too large")
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceComparisonOperator_Gt_GtE, occurrence: 1
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -288,7 +288,7 @@
body = await request.body()
# Double-check actual body size (in case content-length was missing/incorrect)
- if len(body) > MAX_WEBHOOK_BODY_SIZE:
+ if len(body) >= MAX_WEBHOOK_BODY_SIZE:
errors_total.labels(error_type="payload_too_large").inc()
raise HTTPException(status_code=413, detail="Request body too large")
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceComparisonOperator_Gt_Is, occurrence: 0
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -280,7 +280,7 @@
# Check content length before reading body to prevent memory exhaustion
content_length = request.headers.get("content-length")
- if content_length and int(content_length) > MAX_WEBHOOK_BODY_SIZE:
+ if content_length and int(content_length) is MAX_WEBHOOK_BODY_SIZE:
errors_total.labels(error_type="payload_too_large").inc()
raise HTTPException(status_code=413, detail="Request body too large")
........................................................................ [ 34%]
..............................F
=================================== FAILURES ===================================
____________________ test_webhook_content_length_too_large _____________________
test_client = <starlette.testclient.TestClient object at 0x7fb3223bb020>
def test_webhook_content_length_too_large(test_client: TestClient):
"""Test webhook rejects requests with Content-Length exceeding limit."""
response = test_client.post(
"/webhook",
content=b"x", # Small actual body
headers={
"Content-Type": "application/json",
"Content-Length": "20000000", # 20MB - exceeds 10MB limit
"X-GitHub-Event": "ping",
"X-Hub-Signature-256": "sha256=fake",
},
)
> assert response.status_code == 413
E assert 401 == 413
E + where 401 = <Response [401 Unauthorized]>.status_code
tests/test_main.py:193: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:31:39.780475Z[0m [[33m[1mwarning [0m] [1mInvalid webhook signature [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.main (INFO)>[0m [36m_name[0m=[35mwarning[0m [36mclient_ip[0m=[35mtestclient[0m
[2m2026-05-16T19:31:39.781283Z[0m [[32m[1minfo [0m] [1mHTTP Request: POST http://testserver/webhook "HTTP/1.1 401 Unauthorized"[0m
------------------------------ Captured log call -------------------------------
WARNING stampbot.main:main.py:299 {'event': 'Invalid webhook signature', 'client_ip': 'testclient', 'level': 'warning', 'timestamp': '2026-05-16T19:31:39.780475Z'}
INFO httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 401 Unauthorized"
=========================== short test summary info ============================
FAILED tests/test_main.py::test_webhook_content_length_too_large - assert 401 == 413
+ where 401 = <Response [401 Unauthorized]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 102 passed, 3 deselected in 0.84s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceComparisonOperator_Gt_Is, occurrence: 1
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -288,7 +288,7 @@
body = await request.body()
# Double-check actual body size (in case content-length was missing/incorrect)
- if len(body) > MAX_WEBHOOK_BODY_SIZE:
+ if len(body) is MAX_WEBHOOK_BODY_SIZE:
errors_total.labels(error_type="payload_too_large").inc()
raise HTTPException(status_code=413, detail="Request body too large")
........................................................................ [ 34%]
...............................F
=================================== FAILURES ===================================
_________________________ test_webhook_body_too_large __________________________
test_client = <starlette.testclient.TestClient object at 0x7f40e12c38a0>
def test_webhook_body_too_large(test_client: TestClient):
"""Test webhook rejects requests where actual body exceeds limit.
This tests the secondary body size check (lines 231-232) which catches
cases where Content-Length header is missing or incorrect.
"""
# Create a body larger than MAX_WEBHOOK_BODY_SIZE (10MB)
large_body = b"x" * (10 * 1024 * 1024 + 1) # 10MB + 1 byte
# Set Content-Length to a small value to bypass the header check (line 222)
# but the actual body size check (line 230) should still catch it
response = test_client.post(
"/webhook",
content=large_body,
headers={
"Content-Type": "application/json",
"Content-Length": "100", # Lie about size to bypass first check
"X-GitHub-Event": "ping",
"X-Hub-Signature-256": "sha256=fake",
},
)
> assert response.status_code == 413
E assert 401 == 413
E + where 401 = <Response [401 Unauthorized]>.status_code
tests/test_main.py:218: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:44:12.675455Z[0m [[33m[1mwarning [0m] [1mInvalid webhook signature [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.main (INFO)>[0m [36m_name[0m=[35mwarning[0m [36mclient_ip[0m=[35mtestclient[0m
[2m2026-05-16T19:44:12.676542Z[0m [[32m[1minfo [0m] [1mHTTP Request: POST http://testserver/webhook "HTTP/1.1 401 Unauthorized"[0m
------------------------------ Captured log call -------------------------------
WARNING stampbot.main:main.py:299 {'event': 'Invalid webhook signature', 'client_ip': 'testclient', 'level': 'warning', 'timestamp': '2026-05-16T19:44:12.675455Z'}
INFO httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 401 Unauthorized"
=========================== short test summary info ============================
FAILED tests/test_main.py::test_webhook_body_too_large - assert 401 == 413
+ where 401 = <Response [401 Unauthorized]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 103 passed, 3 deselected in 0.87s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceComparisonOperator_Gt_IsNot, occurrence: 0
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -280,7 +280,7 @@
# Check content length before reading body to prevent memory exhaustion
content_length = request.headers.get("content-length")
- if content_length and int(content_length) > MAX_WEBHOOK_BODY_SIZE:
+ if content_length and int(content_length) is not MAX_WEBHOOK_BODY_SIZE:
errors_total.labels(error_type="payload_too_large").inc()
raise HTTPException(status_code=413, detail="Request body too large")
........................................................................ [ 34%]
........................F
=================================== FAILURES ===================================
_______________________ test_request_with_content_length _______________________
test_client = <starlette.testclient.TestClient object at 0x7fdc87dab770>
def test_request_with_content_length(test_client: TestClient):
"""Test that requests with Content-Length header are tracked."""
# POST with a body will have Content-Length set automatically
response = test_client.post(
"/webhook",
content=b'{"test": "data"}',
headers={
"Content-Type": "application/json",
"X-GitHub-Event": "ping",
"X-Hub-Signature-256": "sha256=invalid",
},
)
# Will fail signature validation, but Content-Length tracking happens before that
> assert response.status_code == 401
E assert 413 == 401
E + where 413 = <Response [413 Request Entity Too Large]>.status_code
tests/test_main.py:79: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:43:26.570015Z[0m [[32m[1minfo [0m] [1mHTTP Request: POST http://testserver/webhook "HTTP/1.1 413 Request Entity Too Large"[0m
------------------------------ Captured log call -------------------------------
INFO httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 413 Request Entity Too Large"
=========================== short test summary info ============================
FAILED tests/test_main.py::test_request_with_content_length - assert 413 == 401
+ where 413 = <Response [413 Request Entity Too Large]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 96 passed, 3 deselected in 0.84s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceComparisonOperator_Gt_IsNot, occurrence: 1
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -288,7 +288,7 @@
body = await request.body()
# Double-check actual body size (in case content-length was missing/incorrect)
- if len(body) > MAX_WEBHOOK_BODY_SIZE:
+ if len(body) is not MAX_WEBHOOK_BODY_SIZE:
errors_total.labels(error_type="payload_too_large").inc()
raise HTTPException(status_code=413, detail="Request body too large")
........................................................................ [ 34%]
........................F
=================================== FAILURES ===================================
_______________________ test_request_with_content_length _______________________
test_client = <starlette.testclient.TestClient object at 0x7f1145db3770>
def test_request_with_content_length(test_client: TestClient):
"""Test that requests with Content-Length header are tracked."""
# POST with a body will have Content-Length set automatically
response = test_client.post(
"/webhook",
content=b'{"test": "data"}',
headers={
"Content-Type": "application/json",
"X-GitHub-Event": "ping",
"X-Hub-Signature-256": "sha256=invalid",
},
)
# Will fail signature validation, but Content-Length tracking happens before that
> assert response.status_code == 401
E assert 413 == 401
E + where 413 = <Response [413 Request Entity Too Large]>.status_code
tests/test_main.py:79: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:38:47.669552Z[0m [[32m[1minfo [0m] [1mHTTP Request: POST http://testserver/webhook "HTTP/1.1 413 Request Entity Too Large"[0m
------------------------------ Captured log call -------------------------------
INFO httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 413 Request Entity Too Large"
=========================== short test summary info ============================
FAILED tests/test_main.py::test_request_with_content_length - assert 413 == 401
+ where 413 = <Response [413 Request Entity Too Large]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 96 passed, 3 deselected in 0.83s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceUnaryOperator_Delete_Not, occurrence: 0
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -71,7 +71,7 @@
)
# Log setup mode status
- if not is_configured():
+ if is_configured():
logger.warning("GitHub App credentials not configured. Running in setup mode.")
logger.info("Visit /setup to create your GitHub App")
else:........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceUnaryOperator_Delete_Not, occurrence: 1
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -196,7 +196,7 @@
# entry is the original client (proxies append to the right).
client_ip = raw.split(",")[0].strip()
- if not client_ip and scope.get("client"):
+ if client_ip and scope.get("client"):
client_ip = scope["client"][0]
if client_ip:........................................................................ [ 34%]
...................................F
=================================== FAILURES ===================================
______________ test_logging_middleware_respects_configured_header ______________
def test_logging_middleware_respects_configured_header():
"""Test that logging_middleware uses the configured client_ip_header setting."""
from unittest.mock import patch
import structlog.contextvars
from stampbot.main import app
with patch("stampbot.main.settings") as mock_settings:
mock_settings.get = lambda key, default=None: (
"X-Real-IP" if key == "client_ip_header" else default
)
client = TestClient(app)
captured: dict[str, str | None] = {}
original_bind = structlog.contextvars.bind_contextvars
def capturing_bind(**kw: object) -> None:
captured.update({k: str(v) for k, v in kw.items()})
original_bind(**kw)
with patch("stampbot.main.structlog.contextvars.bind_contextvars", capturing_bind):
client.get("/health", headers={"X-Real-IP": "198.51.100.7"})
> assert captured.get("client_ip") == "198.51.100.7"
E AssertionError: assert 'testclient' == '198.51.100.7'
E
E - 198.51.100.7
E + testclient
tests/test_main.py:296: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:31:03.250397Z[0m [[32m[1minfo [0m] [1mHTTP Request: GET http://testserver/health "HTTP/1.1 200 OK"[0m
------------------------------ Captured log call -------------------------------
INFO httpx:_client.py:1025 HTTP Request: GET http://testserver/health "HTTP/1.1 200 OK"
=========================== short test summary info ============================
FAILED tests/test_main.py::test_logging_middleware_respects_configured_header - AssertionError: assert 'testclient' == '198.51.100.7'
- 198.51.100.7
+ testclient
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 107 passed, 3 deselected in 0.87s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceUnaryOperator_Delete_Not, occurrence: 2
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -215,7 +215,7 @@
Returns:
Redirect to /setup if unconfigured, otherwise JSON status response.
"""
- if not is_configured() and settings.setup_enabled:
+ if is_configured() and settings.setup_enabled:
return RedirectResponse(url="/setup", status_code=307)
return Response(........................................................................ [ 34%]
.....................F
=================================== FAILURES ===================================
______________________________ test_root_endpoint ______________________________
test_client = <starlette.testclient.TestClient object at 0x7f97eacb4190>
def test_root_endpoint(test_client: TestClient):
"""Test root endpoint returns basic info."""
response = test_client.get("/")
assert response.status_code == 200
> data = response.json()
^^^^^^^^^^^^^^^
tests/test_main.py:46:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:832: in json
return jsonlib.loads(self.content, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/json/__init__.py:352: in loads
return _default_decoder.decode(s)
^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/json/decoder.py:345: in decode
obj, end = self.raw_decode(s, idx=_w(s, 0).end())
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <json.decoder.JSONDecoder object at 0x7f97f2362f90>
s = '\n <!DOCTYPE html>\n <html>\n <head><title>Stampbot - Already Configured</title>\n ...pbot is ready to receive webhooks.</p>\n </div>\n </body>\n </html>\n '
idx = 13
def raw_decode(self, s, idx=0):
"""Decode a JSON document from ``s`` (a ``str`` beginning with
a JSON document) and return a 2-tuple of the Python
representation and the index in ``s`` where the document ended.
This can be used to decode a JSON document from a string that may
have extraneous data at the end.
"""
try:
obj, end = self.scan_once(s, idx)
except StopIteration as err:
> raise JSONDecodeError("Expecting value", s, err.value) from None
E json.decoder.JSONDecodeError: Expecting value: line 2 column 13 (char 13)
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/json/decoder.py:363: JSONDecodeError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:52:45.274373Z[0m [[32m[1minfo [0m] [1mHTTP Request: GET http://testserver/ "HTTP/1.1 307 Temporary Redirect"[0m
[2m2026-05-16T19:52:45.276600Z[0m [[32m[1minfo [0m] [1mHTTP Request: GET http://testserver/setup "HTTP/1.1 200 OK"[0m
------------------------------ Captured log call -------------------------------
INFO httpx:_client.py:1025 HTTP Request: GET http://testserver/ "HTTP/1.1 307 Temporary Redirect"
INFO httpx:_client.py:1025 HTTP Request: GET http://testserver/setup "HTTP/1.1 200 OK"
=========================== short test summary info ============================
FAILED tests/test_main.py::test_root_endpoint - json.decoder.JSONDecodeError: Expecting value: line 2 column 13 (char 13)
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 93 passed, 3 deselected in 0.87s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceUnaryOperator_Delete_Not, occurrence: 3
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -268,7 +268,7 @@
413 if body too large, 503 if not configured, 500 on internal error.
"""
# Check if app is configured
- if not is_configured():
+ if is_configured():
raise HTTPException(
status_code=503,
detail="Stampbot not configured. Visit /setup to complete setup.",........................................................................ [ 34%]
........................F
=================================== FAILURES ===================================
_______________________ test_request_with_content_length _______________________
test_client = <starlette.testclient.TestClient object at 0x7f3e403a3770>
def test_request_with_content_length(test_client: TestClient):
"""Test that requests with Content-Length header are tracked."""
# POST with a body will have Content-Length set automatically
response = test_client.post(
"/webhook",
content=b'{"test": "data"}',
headers={
"Content-Type": "application/json",
"X-GitHub-Event": "ping",
"X-Hub-Signature-256": "sha256=invalid",
},
)
# Will fail signature validation, but Content-Length tracking happens before that
> assert response.status_code == 401
E assert 503 == 401
E + where 503 = <Response [503 Service Unavailable]>.status_code
tests/test_main.py:79: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:40:00.042618Z[0m [[32m[1minfo [0m] [1mHTTP Request: POST http://testserver/webhook "HTTP/1.1 503 Service Unavailable"[0m
------------------------------ Captured log call -------------------------------
INFO httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 503 Service Unavailable"
=========================== short test summary info ============================
FAILED tests/test_main.py::test_request_with_content_length - assert 503 == 401
+ where 503 = <Response [503 Service Unavailable]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 96 passed, 3 deselected in 0.84s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceUnaryOperator_Delete_Not, occurrence: 4
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -274,7 +274,7 @@
detail="Stampbot not configured. Visit /setup to complete setup.",
)
- if not x_github_event:
+ if x_github_event:
errors_total.labels(error_type="missing_event").inc()
raise HTTPException(status_code=400, detail="Missing X-GitHub-Event header")
........................................................................ [ 34%]
........................F
=================================== FAILURES ===================================
_______________________ test_request_with_content_length _______________________
test_client = <starlette.testclient.TestClient object at 0x7fe839183770>
def test_request_with_content_length(test_client: TestClient):
"""Test that requests with Content-Length header are tracked."""
# POST with a body will have Content-Length set automatically
response = test_client.post(
"/webhook",
content=b'{"test": "data"}',
headers={
"Content-Type": "application/json",
"X-GitHub-Event": "ping",
"X-Hub-Signature-256": "sha256=invalid",
},
)
# Will fail signature validation, but Content-Length tracking happens before that
> assert response.status_code == 401
E assert 400 == 401
E + where 400 = <Response [400 Bad Request]>.status_code
tests/test_main.py:79: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:34:27.027542Z[0m [[32m[1minfo [0m] [1mHTTP Request: POST http://testserver/webhook "HTTP/1.1 400 Bad Request"[0m
------------------------------ Captured log call -------------------------------
INFO httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 400 Bad Request"
=========================== short test summary info ============================
FAILED tests/test_main.py::test_request_with_content_length - assert 400 == 401
+ where 400 = <Response [400 Bad Request]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 96 passed, 3 deselected in 0.83s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceUnaryOperator_Delete_Not, occurrence: 5
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -293,7 +293,7 @@
raise HTTPException(status_code=413, detail="Request body too large")
# Verify signature
- if not webhook_handler.verify_signature(body, x_hub_signature_256):
+ if webhook_handler.verify_signature(body, x_hub_signature_256):
webhook_signature_validations_total.labels(result="invalid").inc()
errors_total.labels(error_type="signature_invalid").inc()
logger.warning("Invalid webhook signature")........................................................................ [ 34%]
........................F
=================================== FAILURES ===================================
_______________________ test_request_with_content_length _______________________
test_client = <starlette.testclient.TestClient object at 0x7f96b4b93770>
def test_request_with_content_length(test_client: TestClient):
"""Test that requests with Content-Length header are tracked."""
# POST with a body will have Content-Length set automatically
response = test_client.post(
"/webhook",
content=b'{"test": "data"}',
headers={
"Content-Type": "application/json",
"X-GitHub-Event": "ping",
"X-Hub-Signature-256": "sha256=invalid",
},
)
# Will fail signature validation, but Content-Length tracking happens before that
> assert response.status_code == 401
E assert 200 == 401
E + where 200 = <Response [200 OK]>.status_code
tests/test_main.py:79: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:31:12.656595Z[0m [[32m[1minfo [0m] [1mReceived ping event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mclient_ip[0m=[35mtestclient[0m [36mextra[0m=[35m{'event_type': 'ping', 'action': ''}[0m
[2m2026-05-16T19:31:12.657562Z[0m [[32m[1minfo [0m] [1mHTTP Request: POST http://testserver/webhook "HTTP/1.1 200 OK"[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'ping', 'action': ''}, 'event': 'Received ping event', 'client_ip': 'testclient', 'level': 'info', 'timestamp': '2026-05-16T19:31:12.656595Z'}
INFO httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 200 OK"
=========================== short test summary info ============================
FAILED tests/test_main.py::test_request_with_content_length - assert 200 == 401
+ where 200 = <Response [200 OK]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 96 passed, 3 deselected in 0.83s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceUnaryOperator_Delete_Not, occurrence: 6
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -345,7 +345,7 @@
Raises:
HTTPException: If setup is disabled (403).
"""
- if not settings.setup_enabled:
+ if settings.setup_enabled:
raise HTTPException(status_code=403, detail="Setup not allowed in this environment")
if is_configured():........................................................................ [ 34%]
...........................................................F
=================================== FAILURES ===================================
_______ TestSetupEndpointsConfigured.test_setup_shows_already_configured _______
self = <tests.test_setup_endpoints.TestSetupEndpointsConfigured object at 0x7f7ecbe5fb10>
test_client = <starlette.testclient.TestClient object at 0x7f7ec6c16be0>
def test_setup_shows_already_configured(self, test_client):
"""Test /setup shows already configured message."""
response = test_client.get("/setup")
> assert response.status_code == 200
E assert 403 == 200
E + where 403 = <Response [403 Forbidden]>.status_code
tests/test_setup_endpoints.py:22: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:39:55.326944Z[0m [[32m[1minfo [0m] [1mHTTP Request: GET http://testserver/setup "HTTP/1.1 403 Forbidden"[0m
------------------------------ Captured log call -------------------------------
INFO httpx:_client.py:1025 HTTP Request: GET http://testserver/setup "HTTP/1.1 403 Forbidden"
=========================== short test summary info ============================
FAILED tests/test_setup_endpoints.py::TestSetupEndpointsConfigured::test_setup_shows_already_configured - assert 403 == 200
+ where 403 = <Response [403 Forbidden]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 131 passed, 3 deselected in 0.99s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceUnaryOperator_Delete_Not, occurrence: 7
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -464,7 +464,7 @@
Raises:
HTTPException: If setup disabled (403) or code exchange fails (500).
"""
- if not settings.setup_enabled:
+ if settings.setup_enabled:
raise HTTPException(status_code=403, detail="Setup not allowed")
try:........................................................................ [ 34%]
..................................................................F
=================================== FAILURES ===================================
________________ TestSetupCallback.test_callback_exchanges_code ________________
self = <tests.test_setup_endpoints.TestSetupCallback object at 0x7f74d45f7ed0>
test_client = <starlette.testclient.TestClient object at 0x7f74cbe17bd0>
def test_callback_exchanges_code(self, test_client):
"""Test callback exchanges code for credentials."""
mock_credentials = {
"id": 12345,
"pem": "-----BEGIN RSA PRIVATE KEY-----\ntest\n-----END RSA PRIVATE KEY-----",
"webhook_secret": "test-secret",
"slug": "test-stampbot",
"name": "Test Stampbot",
}
with patch(
"stampbot.main.exchange_code_for_credentials",
new_callable=AsyncMock,
return_value=mock_credentials,
):
response = test_client.get("/setup/callback?code=test-code")
> assert response.status_code == 200
E assert 403 == 200
E + where 403 = <Response [403 Forbidden]>.status_code
tests/test_setup_endpoints.py:179: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:45:53.548678Z[0m [[32m[1minfo [0m] [1mHTTP Request: GET http://testserver/setup/callback?code=test-code "HTTP/1.1 403 Forbidden"[0m
------------------------------ Captured log call -------------------------------
INFO httpx:_client.py:1025 HTTP Request: GET http://testserver/setup/callback?code=test-code "HTTP/1.1 403 Forbidden"
=========================== short test summary info ============================
FAILED tests/test_setup_endpoints.py::TestSetupCallback::test_callback_exchanges_code - assert 403 == 200
+ where 403 = <Response [403 Forbidden]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 138 passed, 3 deselected in 1.04s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/AddNot, occurrence: 0
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -71,7 +71,7 @@
)
# Log setup mode status
- if not is_configured():
+ if not not is_configured():
logger.warning("GitHub App credentials not configured. Running in setup mode.")
logger.info("Visit /setup to create your GitHub App")
else:........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.64s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/AddNot, occurrence: 1
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -135,7 +135,7 @@
# Track request size (from Content-Length header if available)
content_length = request.headers.get("content-length")
- if content_length:
+ if not content_length:
http_request_size_bytes.labels(
method=method,
endpoint=endpoint,........................................................................ [ 34%]
...................F
=================================== FAILURES ===================================
__________________ test_lifespan_startup_shutdown_configured ___________________
+ Exception Group Traceback (most recent call last):
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_utils.py", line 79, in collapse_excgroups
| yield
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py", line 192, in __call__
| async with anyio.create_task_group() as task_group:
| ~~~~~~~~~~~~~~~~~~~~~~~^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/_backends/_asyncio.py", line 783, in __aexit__
| raise BaseExceptionGroup(
| "unhandled errors in a TaskGroup", self._exceptions
| ) from None
| ExceptionGroup: unhandled errors in a TaskGroup (1 sub-exception)
+-+---------------- 1 ----------------
| Traceback (most recent call last):
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/runner.py", line 353, in from_call
| result: TResult | None = func()
| ~~~~^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/runner.py", line 245, in <lambda>
| lambda: runtest_hook(item=item, **kwds),
| ~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_hooks.py", line 512, in __call__
| return self._hookexec(self.name, self._hookimpls.copy(), kwargs, firstresult)
| ~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_manager.py", line 120, in _hookexec
| return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
| ~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_callers.py", line 167, in _multicall
| raise exception
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_callers.py", line 139, in _multicall
| teardown.throw(exception)
| ~~~~~~~~~~~~~~^^^^^^^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/logging.py", line 850, in pytest_runtest_call
| yield
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_callers.py", line 139, in _multicall
| teardown.throw(exception)
| ~~~~~~~~~~~~~~^^^^^^^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_callers.py", line 53, in run_old_style_hookwrapper
| return result.get_result()
| ~~~~~~~~~~~~~~~~~^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_result.py", line 103, in get_result
| raise exc.with_traceback(tb)
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_callers.py", line 38, in run_old_style_hookwrapper
| res = yield
| ^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_callers.py", line 139, in _multicall
| teardown.throw(exception)
| ~~~~~~~~~~~~~~^^^^^^^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/capture.py", line 900, in pytest_runtest_call
| return (yield)
| ^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_callers.py", line 139, in _multicall
| teardown.throw(exception)
| ~~~~~~~~~~~~~~^^^^^^^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/skipping.py", line 268, in pytest_runtest_call
| return (yield)
| ^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_callers.py", line 121, in _multicall
| res = hook_impl.function(*args)
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/runner.py", line 179, in pytest_runtest_call
| item.runtest()
| ~~~~~~~~~~~~^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/python.py", line 1720, in runtest
| self.ihook.pytest_pyfunc_call(pyfuncitem=self)
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_hooks.py", line 512, in __call__
| return self._hookexec(self.name, self._hookimpls.copy(), kwargs, firstresult)
| ~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_manager.py", line 120, in _hookexec
| return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
| ~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_callers.py", line 167, in _multicall
| raise exception
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_callers.py", line 139, in _multicall
| teardown.throw(exception)
| ~~~~~~~~~~~~~~^^^^^^^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_callers.py", line 53, in run_old_style_hookwrapper
| return result.get_result()
| ~~~~~~~~~~~~~~~~~^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_result.py", line 103, in get_result
| raise exc.with_traceback(tb)
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_callers.py", line 38, in run_old_style_hookwrapper
| res = yield
| ^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_callers.py", line 121, in _multicall
| res = hook_impl.function(*args)
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/python.py", line 166, in pytest_pyfunc_call
| result = testfunction(**testargs)
| File "/home/runner/work/stampbot/stampbot/tests/test_main.py", line 20, in test_lifespan_startup_shutdown_configured
| response = client.get("/health")
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py", line 473, in get
| return super().get(
| ~~~~~~~~~~~^
| url,
| ^^^^
| ...<6 lines>...
| extensions=extensions,
| ^^^^^^^^^^^^^^^^^^^^^^
| )
| ^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py", line 1053, in get
| return self.request(
| ~~~~~~~~~~~~^
| "GET",
| ^^^^^^
| ...<7 lines>...
| extensions=extensions,
| ^^^^^^^^^^^^^^^^^^^^^^
| )
| ^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py", line 445, in request
| return super().request(
| ~~~~~~~~~~~~~~~^
| method,
| ^^^^^^^
| ...<11 lines>...
| extensions=extensions,
| ^^^^^^^^^^^^^^^^^^^^^^
| )
| ^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py", line 825, in request
| return self.send(request, auth=auth, follow_redirects=follow_redirects)
| ~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py", line 914, in send
| response = self._send_handling_auth(
| request,
| ...<2 lines>...
| history=[],
| )
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py", line 942, in _send_handling_auth
| response = self._send_handling_redirects(
| request,
| follow_redirects=follow_redirects,
| history=history,
| )
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py", line 979, in _send_handling_redirects
| response = self._send_single_request(request)
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py", line 1014, in _send_single_request
| response = transport.handle_request(request)
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py", line 348, in handle_request
| raise exc
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py", line 345, in handle_request
| portal.call(self.app, scope, receive, send)
| ~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py", line 334, in call
| return cast(T_Retval, self.start_task_soon(func, *args).result())
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^
| File "/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py", line 450, in result
| return self.__get_result()
| ~~~~~~~~~~~~~~~~~^^
| File "/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py", line 395, in __get_result
| raise self._exception
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py", line 259, in _call_func
| retval = await retval_or_awaitable
| ^^^^^^^^^^^^^^^^^^^^^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py", line 1135, in __call__
| await super().__call__(scope, receive, send)
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py", line 107, in __call__
| await self.middleware_stack(scope, receive, send)
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py", line 186, in __call__
| raise exc
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py", line 164, in __call__
| await self.app(scope, receive, _send)
| File "/home/runner/work/stampbot/stampbot/stampbot/main.py", line 205, in __call__
| await self.app(scope, receive, send)
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py", line 191, in __call__
| with recv_stream, send_stream, collapse_excgroups():
| ~~~~~~~~~~~~~~~~~~^^
| File "/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/contextlib.py", line 162, in __exit__
| self.gen.throw(value)
| ~~~~~~~~~~~~~~^^^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_utils.py", line 85, in collapse_excgroups
| raise exc
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py", line 193, in __call__
| response = await self.dispatch_func(request, call_next)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| File "/home/runner/work/stampbot/stampbot/stampbot/main.py", line 142, in metrics_middleware
| ).observe(int(content_length))
| ~~~^^^^^^^^^^^^^^^^
| TypeError: int() argument must be a string, a bytes-like object or a real number, not 'NoneType'
+------------------------------------
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/home/runner/work/stampbot/stampbot/tests/test_main.py", line 20, in test_lifespan_startup_shutdown_configured
response = client.get("/health")
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py", line 473, in get
return super().get(
~~~~~~~~~~~^
url,
^^^^
...<6 lines>...
extensions=extensions,
^^^^^^^^^^^^^^^^^^^^^^
)
^
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py", line 1053, in get
return self.request(
~~~~~~~~~~~~^
"GET",
^^^^^^
...<7 lines>...
extensions=extensions,
^^^^^^^^^^^^^^^^^^^^^^
)
^
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py", line 445, in request
return super().request(
~~~~~~~~~~~~~~~^
method,
^^^^^^^
...<11 lines>...
extensions=extensions,
^^^^^^^^^^^^^^^^^^^^^^
)
^
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py", line 825, in request
return self.send(request, auth=auth, follow_redirects=follow_redirects)
~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py", line 914, in send
response = self._send_handling_auth(
request,
...<2 lines>...
history=[],
)
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py", line 942, in _send_handling_auth
response = self._send_handling_redirects(
request,
follow_redirects=follow_redirects,
history=history,
)
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py", line 979, in _send_handling_redirects
response = self._send_single_request(request)
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py", line 1014, in _send_single_request
response = transport.handle_request(request)
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py", line 348, in handle_request
raise exc
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py", line 345, in handle_request
portal.call(self.app, scope, receive, send)
~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py", line 334, in call
return cast(T_Retval, self.start_task_soon(func, *args).result())
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^
File "/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py", line 450, in result
return self.__get_result()
~~~~~~~~~~~~~~~~~^^
File "/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py", line 395, in __get_result
raise self._exception
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py", line 259, in _call_func
retval = await retval_or_awaitable
^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py", line 1135, in __call__
await super().__call__(scope, receive, send)
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py", line 107, in __call__
await self.middleware_stack(scope, receive, send)
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py", line 186, in __call__
raise exc
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py", line 164, in __call__
await self.app(scope, receive, _send)
File "/home/runner/work/stampbot/stampbot/stampbot/main.py", line 205, in __call__
await self.app(scope, receive, send)
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py", line 191, in __call__
with recv_stream, send_stream, collapse_excgroups():
~~~~~~~~~~~~~~~~~~^^
File "/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/contextlib.py", line 162, in __exit__
self.gen.throw(value)
~~~~~~~~~~~~~~^^^^^^^
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_utils.py", line 85, in collapse_excgroups
raise exc
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py", line 193, in __call__
response = await self.dispatch_func(request, call_next)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/runner/work/stampbot/stampbot/stampbot/main.py", line 142, in metrics_middleware
).observe(int(content_length))
~~~^^^^^^^^^^^^^^^^
TypeError: int() argument must be a string, a bytes-like object or a real number, not 'NoneType'
During handling of the above exception, another exception occurred:
def test_lifespan_startup_shutdown_configured():
"""Test app lifespan startup and shutdown when configured."""
from stampbot.main import app
# Use TestClient as context manager to trigger lifespan events
with TestClient(app) as client:
# App should be running
> response = client.get("/health")
^^^^^^^^^^^^^^^^^^^^^
tests/test_main.py:20:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:473: in get
return super().get(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1053: in get
return self.request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:445: in request
return super().request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:825: in request
return self.send(request, auth=auth, follow_redirects=follow_redirects)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:914: in send
response = self._send_handling_auth(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:942: in _send_handling_auth
response = self._send_handling_redirects(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:979: in _send_handling_redirects
response = self._send_single_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1014: in _send_single_request
response = transport.handle_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:348: in handle_request
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:345: in handle_request
portal.call(self.app, scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:334: in call
return cast(T_Retval, self.start_task_soon(func, *args).result())
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:450: in result
return self.__get_result()
^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:395: in __get_result
raise self._exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:259: in _call_func
retval = await retval_or_awaitable
^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1135: in __call__
await super().__call__(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:107: in __call__
await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:186: in __call__
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:164: in __call__
await self.app(scope, receive, _send)
stampbot/main.py:205: in __call__
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:191: in __call__
with recv_stream, send_stream, collapse_excgroups():
^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/contextlib.py:162: in __exit__
self.gen.throw(value)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_utils.py:85: in collapse_excgroups
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:193: in __call__
response = await self.dispatch_func(request, call_next)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
request = <starlette.middleware.base._CachedRequest object at 0x7f7edae9f0e0>
call_next = <function BaseHTTPMiddleware.__call__.<locals>.call_next at 0x7f7edaea2820>
@app.middleware("http")
async def metrics_middleware(request: Request, call_next: Any) -> Response:
"""Middleware to track HTTP metrics.
Args:
request: Incoming HTTP request.
call_next: Next middleware or route handler.
Returns:
HTTP response from downstream handler.
"""
method = request.method
endpoint = request.url.path
# Track in-progress requests
http_requests_in_progress.labels(method=method, endpoint=endpoint).inc()
start_time = time.time()
try:
response = await call_next(request)
duration = time.time() - start_time
# Track request metrics
http_requests_total.labels(
method=method,
endpoint=endpoint,
status=response.status_code,
).inc()
http_request_duration_seconds.labels(
method=method,
endpoint=endpoint,
).observe(duration)
# Track request size (from Content-Length header if available)
content_length = request.headers.get("content-length")
if not content_length:
http_request_size_bytes.labels(
method=method,
endpoint=endpoint,
> ).observe(int(content_length))
^^^^^^^^^^^^^^^^^^^
E TypeError: int() argument must be a string, a bytes-like object or a real number, not 'NoneType'
stampbot/main.py:142: TypeError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:41:41.822516Z[0m [[32m[1minfo [0m] [1mOpenTelemetry disabled [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.telemetry (INFO)>[0m [36m_name[0m=[35minfo[0m
[2m2026-05-16T19:41:41.841816Z[0m [[32m[1minfo [0m] [1mStarting stampbot on 0.0.0.0:8000[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.main (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'host': '0.0.0.0', 'port': 8000, 'log_level': 'INFO'}[0m
[2m2026-05-16T19:41:41.842128Z[0m [[32m[1minfo [0m] [1mGitHub App credentials configured successfully[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.main (INFO)>[0m [36m_name[0m=[35minfo[0m
[2m2026-05-16T19:41:41.844456Z[0m [[32m[1minfo [0m] [1mShutting down stampbot [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.main (INFO)>[0m [36m_name[0m=[35minfo[0m
=========================== short test summary info ============================
FAILED tests/test_main.py::test_lifespan_startup_shutdown_configured - TypeError: int() argument must be a string, a bytes-like object or a real number, not 'NoneType'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 91 passed, 3 deselected in 1.05s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/AddNot, occurrence: 2
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -143,7 +143,7 @@
# Track response size
response_size = response.headers.get("content-length")
- if response_size:
+ if not response_size:
http_response_size_bytes.labels(
method=method,
endpoint=endpoint,........................................................................ [ 34%]
................................F
=================================== FAILURES ===================================
_____________________ test_response_without_content_length _____________________
+ Exception Group Traceback (most recent call last):
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_utils.py", line 79, in collapse_excgroups
| yield
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py", line 192, in __call__
| async with anyio.create_task_group() as task_group:
| ~~~~~~~~~~~~~~~~~~~~~~~^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/_backends/_asyncio.py", line 783, in __aexit__
| raise BaseExceptionGroup(
| "unhandled errors in a TaskGroup", self._exceptions
| ) from None
| ExceptionGroup: unhandled errors in a TaskGroup (1 sub-exception)
+-+---------------- 1 ----------------
| Traceback (most recent call last):
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/runner.py", line 353, in from_call
| result: TResult | None = func()
| ~~~~^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/runner.py", line 245, in <lambda>
| lambda: runtest_hook(item=item, **kwds),
| ~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_hooks.py", line 512, in __call__
| return self._hookexec(self.name, self._hookimpls.copy(), kwargs, firstresult)
| ~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_manager.py", line 120, in _hookexec
| return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
| ~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_callers.py", line 167, in _multicall
| raise exception
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_callers.py", line 139, in _multicall
| teardown.throw(exception)
| ~~~~~~~~~~~~~~^^^^^^^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/logging.py", line 850, in pytest_runtest_call
| yield
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_callers.py", line 139, in _multicall
| teardown.throw(exception)
| ~~~~~~~~~~~~~~^^^^^^^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_callers.py", line 53, in run_old_style_hookwrapper
| return result.get_result()
| ~~~~~~~~~~~~~~~~~^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_result.py", line 103, in get_result
| raise exc.with_traceback(tb)
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_callers.py", line 38, in run_old_style_hookwrapper
| res = yield
| ^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_callers.py", line 139, in _multicall
| teardown.throw(exception)
| ~~~~~~~~~~~~~~^^^^^^^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/capture.py", line 900, in pytest_runtest_call
| return (yield)
| ^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_callers.py", line 139, in _multicall
| teardown.throw(exception)
| ~~~~~~~~~~~~~~^^^^^^^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/skipping.py", line 268, in pytest_runtest_call
| return (yield)
| ^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_callers.py", line 121, in _multicall
| res = hook_impl.function(*args)
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/runner.py", line 179, in pytest_runtest_call
| item.runtest()
| ~~~~~~~~~~~~^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/python.py", line 1720, in runtest
| self.ihook.pytest_pyfunc_call(pyfuncitem=self)
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_hooks.py", line 512, in __call__
| return self._hookexec(self.name, self._hookimpls.copy(), kwargs, firstresult)
| ~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_manager.py", line 120, in _hookexec
| return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
| ~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_callers.py", line 167, in _multicall
| raise exception
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_callers.py", line 139, in _multicall
| teardown.throw(exception)
| ~~~~~~~~~~~~~~^^^^^^^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_callers.py", line 53, in run_old_style_hookwrapper
| return result.get_result()
| ~~~~~~~~~~~~~~~~~^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_result.py", line 103, in get_result
| raise exc.with_traceback(tb)
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_callers.py", line 38, in run_old_style_hookwrapper
| res = yield
| ^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/pluggy/_callers.py", line 121, in _multicall
| res = hook_impl.function(*args)
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/python.py", line 166, in pytest_pyfunc_call
| result = testfunction(**testargs)
| File "/home/runner/work/stampbot/stampbot/tests/test_main.py", line 247, in test_response_without_content_length
| response = client.get(streaming_route_path)
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py", line 473, in get
| return super().get(
| ~~~~~~~~~~~^
| url,
| ^^^^
| ...<6 lines>...
| extensions=extensions,
| ^^^^^^^^^^^^^^^^^^^^^^
| )
| ^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py", line 1053, in get
| return self.request(
| ~~~~~~~~~~~~^
| "GET",
| ^^^^^^
| ...<7 lines>...
| extensions=extensions,
| ^^^^^^^^^^^^^^^^^^^^^^
| )
| ^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py", line 445, in request
| return super().request(
| ~~~~~~~~~~~~~~~^
| method,
| ^^^^^^^
| ...<11 lines>...
| extensions=extensions,
| ^^^^^^^^^^^^^^^^^^^^^^
| )
| ^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py", line 825, in request
| return self.send(request, auth=auth, follow_redirects=follow_redirects)
| ~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py", line 914, in send
| response = self._send_handling_auth(
| request,
| ...<2 lines>...
| history=[],
| )
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py", line 942, in _send_handling_auth
| response = self._send_handling_redirects(
| request,
| follow_redirects=follow_redirects,
| history=history,
| )
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py", line 979, in _send_handling_redirects
| response = self._send_single_request(request)
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py", line 1014, in _send_single_request
| response = transport.handle_request(request)
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py", line 348, in handle_request
| raise exc
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py", line 345, in handle_request
| portal.call(self.app, scope, receive, send)
| ~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py", line 334, in call
| return cast(T_Retval, self.start_task_soon(func, *args).result())
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^
| File "/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py", line 450, in result
| return self.__get_result()
| ~~~~~~~~~~~~~~~~~^^
| File "/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py", line 395, in __get_result
| raise self._exception
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py", line 259, in _call_func
| retval = await retval_or_awaitable
| ^^^^^^^^^^^^^^^^^^^^^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py", line 1135, in __call__
| await super().__call__(scope, receive, send)
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py", line 107, in __call__
| await self.middleware_stack(scope, receive, send)
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py", line 186, in __call__
| raise exc
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py", line 164, in __call__
| await self.app(scope, receive, _send)
| File "/home/runner/work/stampbot/stampbot/stampbot/main.py", line 205, in __call__
| await self.app(scope, receive, send)
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py", line 191, in __call__
| with recv_stream, send_stream, collapse_excgroups():
| ~~~~~~~~~~~~~~~~~~^^
| File "/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/contextlib.py", line 162, in __exit__
| self.gen.throw(value)
| ~~~~~~~~~~~~~~^^^^^^^
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_utils.py", line 85, in collapse_excgroups
| raise exc
| File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py", line 193, in __call__
| response = await self.dispatch_func(request, call_next)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| File "/home/runner/work/stampbot/stampbot/stampbot/main.py", line 150, in metrics_middleware
| ).observe(int(response_size))
| ~~~^^^^^^^^^^^^^^^
| TypeError: int() argument must be a string, a bytes-like object or a real number, not 'NoneType'
+------------------------------------
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/home/runner/work/stampbot/stampbot/tests/test_main.py", line 247, in test_response_without_content_length
response = client.get(streaming_route_path)
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py", line 473, in get
return super().get(
~~~~~~~~~~~^
url,
^^^^
...<6 lines>...
extensions=extensions,
^^^^^^^^^^^^^^^^^^^^^^
)
^
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py", line 1053, in get
return self.request(
~~~~~~~~~~~~^
"GET",
^^^^^^
...<7 lines>...
extensions=extensions,
^^^^^^^^^^^^^^^^^^^^^^
)
^
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py", line 445, in request
return super().request(
~~~~~~~~~~~~~~~^
method,
^^^^^^^
...<11 lines>...
extensions=extensions,
^^^^^^^^^^^^^^^^^^^^^^
)
^
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py", line 825, in request
return self.send(request, auth=auth, follow_redirects=follow_redirects)
~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py", line 914, in send
response = self._send_handling_auth(
request,
...<2 lines>...
history=[],
)
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py", line 942, in _send_handling_auth
response = self._send_handling_redirects(
request,
follow_redirects=follow_redirects,
history=history,
)
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py", line 979, in _send_handling_redirects
response = self._send_single_request(request)
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py", line 1014, in _send_single_request
response = transport.handle_request(request)
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py", line 348, in handle_request
raise exc
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py", line 345, in handle_request
portal.call(self.app, scope, receive, send)
~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py", line 334, in call
return cast(T_Retval, self.start_task_soon(func, *args).result())
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^
File "/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py", line 450, in result
return self.__get_result()
~~~~~~~~~~~~~~~~~^^
File "/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py", line 395, in __get_result
raise self._exception
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py", line 259, in _call_func
retval = await retval_or_awaitable
^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py", line 1135, in __call__
await super().__call__(scope, receive, send)
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py", line 107, in __call__
await self.middleware_stack(scope, receive, send)
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py", line 186, in __call__
raise exc
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py", line 164, in __call__
await self.app(scope, receive, _send)
File "/home/runner/work/stampbot/stampbot/stampbot/main.py", line 205, in __call__
await self.app(scope, receive, send)
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py", line 191, in __call__
with recv_stream, send_stream, collapse_excgroups():
~~~~~~~~~~~~~~~~~~^^
File "/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/contextlib.py", line 162, in __exit__
self.gen.throw(value)
~~~~~~~~~~~~~~^^^^^^^
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_utils.py", line 85, in collapse_excgroups
raise exc
File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py", line 193, in __call__
response = await self.dispatch_func(request, call_next)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/runner/work/stampbot/stampbot/stampbot/main.py", line 150, in metrics_middleware
).observe(int(response_size))
~~~^^^^^^^^^^^^^^^
TypeError: int() argument must be a string, a bytes-like object or a real number, not 'NoneType'
During handling of the above exception, another exception occurred:
def test_response_without_content_length():
"""Test that responses without Content-Length header are handled correctly.
This tests the branch where response_size is None (line 142->148).
StreamingResponse doesn't include Content-Length header.
"""
from collections.abc import AsyncIterator
from fastapi.responses import StreamingResponse
from stampbot.main import app
# Track if our streaming endpoint was added
streaming_route_path = "/_test_streaming_no_cl"
async def generate() -> AsyncIterator[bytes]:
yield b"chunk1"
yield b"chunk2"
# Add a temporary streaming endpoint
@app.get(streaming_route_path)
async def streaming_no_content_length() -> StreamingResponse:
return StreamingResponse(generate(), media_type="text/plain")
client = TestClient(app)
> response = client.get(streaming_route_path)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_main.py:247:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:473: in get
return super().get(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1053: in get
return self.request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:445: in request
return super().request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:825: in request
return self.send(request, auth=auth, follow_redirects=follow_redirects)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:914: in send
response = self._send_handling_auth(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:942: in _send_handling_auth
response = self._send_handling_redirects(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:979: in _send_handling_redirects
response = self._send_single_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1014: in _send_single_request
response = transport.handle_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:348: in handle_request
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:345: in handle_request
portal.call(self.app, scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:334: in call
return cast(T_Retval, self.start_task_soon(func, *args).result())
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:450: in result
return self.__get_result()
^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:395: in __get_result
raise self._exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:259: in _call_func
retval = await retval_or_awaitable
^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1135: in __call__
await super().__call__(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:107: in __call__
await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:186: in __call__
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:164: in __call__
await self.app(scope, receive, _send)
stampbot/main.py:205: in __call__
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:191: in __call__
with recv_stream, send_stream, collapse_excgroups():
^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/contextlib.py:162: in __exit__
self.gen.throw(value)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_utils.py:85: in collapse_excgroups
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:193: in __call__
response = await self.dispatch_func(request, call_next)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
request = <starlette.middleware.base._CachedRequest object at 0x7ff1d4e16cf0>
call_next = <function BaseHTTPMiddleware.__call__.<locals>.call_next at 0x7ff1d4e052d0>
@app.middleware("http")
async def metrics_middleware(request: Request, call_next: Any) -> Response:
"""Middleware to track HTTP metrics.
Args:
request: Incoming HTTP request.
call_next: Next middleware or route handler.
Returns:
HTTP response from downstream handler.
"""
method = request.method
endpoint = request.url.path
# Track in-progress requests
http_requests_in_progress.labels(method=method, endpoint=endpoint).inc()
start_time = time.time()
try:
response = await call_next(request)
duration = time.time() - start_time
# Track request metrics
http_requests_total.labels(
method=method,
endpoint=endpoint,
status=response.status_code,
).inc()
http_request_duration_seconds.labels(
method=method,
endpoint=endpoint,
).observe(duration)
# Track request size (from Content-Length header if available)
content_length = request.headers.get("content-length")
if content_length:
http_request_size_bytes.labels(
method=method,
endpoint=endpoint,
).observe(int(content_length))
# Track response size
response_size = response.headers.get("content-length")
if not response_size:
http_response_size_bytes.labels(
method=method,
endpoint=endpoint,
> ).observe(int(response_size))
^^^^^^^^^^^^^^^^^^
E TypeError: int() argument must be a string, a bytes-like object or a real number, not 'NoneType'
stampbot/main.py:150: TypeError
=========================== short test summary info ============================
FAILED tests/test_main.py::test_response_without_content_length - TypeError: int() argument must be a string, a bytes-like object or a real number, not 'NoneType'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 104 passed, 3 deselected in 1.09s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/AddNot, occurrence: 3
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -183,7 +183,7 @@
receive: ASGI receive callable.
send: ASGI send callable.
"""
- if scope["type"] == "http":
+ if not scope["type"] == "http":
structlog.contextvars.clear_contextvars()
client_ip: str | None = None........................................................................ [ 34%]
...................F
=================================== FAILURES ===================================
__________________ test_lifespan_startup_shutdown_configured ___________________
def test_lifespan_startup_shutdown_configured():
"""Test app lifespan startup and shutdown when configured."""
from stampbot.main import app
# Use TestClient as context manager to trigger lifespan events
> with TestClient(app) as client:
^^^^^^^^^^^^^^^
tests/test_main.py:18:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:688: in __enter__
portal.call(self.wait_startup)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:334: in call
return cast(T_Retval, self.start_task_soon(func, *args).result())
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:443: in result
return self.__get_result()
^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:395: in __get_result
raise self._exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:259: in _call_func
retval = await retval_or_awaitable
^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:717: in wait_startup
message = await receive()
^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:714: in receive
self.task.result()
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:443: in result
return self.__get_result()
^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:395: in __get_result
raise self._exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:259: in _call_func
retval = await retval_or_awaitable
^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:704: in lifespan
await self.app(scope, self.stream_receive.receive, self.stream_send.send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1135: in __call__
await super().__call__(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:107: in __call__
await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:151: in __call__
await self.app(scope, receive, send)
stampbot/main.py:193: in __call__
raw = Request(scope).headers.get(client_ip_header)
^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/requests.py:204: in __init__
super().__init__(scope)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <starlette.requests.Request object at 0x7f5c68582900>
scope = {'app': <fastapi.applications.FastAPI object at 0x7f5c687b30e0>, 'state': {}, 'type': 'lifespan'}
receive = None
def __init__(self, scope: Scope, receive: Receive | None = None) -> None:
> assert scope["type"] in ("http", "websocket")
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E AssertionError
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/requests.py:78: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:45:11.876903Z[0m [[32m[1minfo [0m] [1mOpenTelemetry disabled [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.telemetry (INFO)>[0m [36m_name[0m=[35minfo[0m
=========================== short test summary info ============================
FAILED tests/test_main.py::test_lifespan_startup_shutdown_configured - AssertionError
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 91 passed, 3 deselected in 0.96s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/AddNot, occurrence: 4
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -189,7 +189,7 @@
client_ip: str | None = None
client_ip_header: str = settings.get("client_ip_header", "X-Forwarded-For")
- if client_ip_header:
+ if not client_ip_header:
raw = Request(scope).headers.get(client_ip_header)
if raw:
# X-Forwarded-For may be a comma-separated list; the leftmost........................................................................ [ 34%]
...................................F
=================================== FAILURES ===================================
______________ test_logging_middleware_respects_configured_header ______________
def test_logging_middleware_respects_configured_header():
"""Test that logging_middleware uses the configured client_ip_header setting."""
from unittest.mock import patch
import structlog.contextvars
from stampbot.main import app
with patch("stampbot.main.settings") as mock_settings:
mock_settings.get = lambda key, default=None: (
"X-Real-IP" if key == "client_ip_header" else default
)
client = TestClient(app)
captured: dict[str, str | None] = {}
original_bind = structlog.contextvars.bind_contextvars
def capturing_bind(**kw: object) -> None:
captured.update({k: str(v) for k, v in kw.items()})
original_bind(**kw)
with patch("stampbot.main.structlog.contextvars.bind_contextvars", capturing_bind):
client.get("/health", headers={"X-Real-IP": "198.51.100.7"})
> assert captured.get("client_ip") == "198.51.100.7"
E AssertionError: assert 'testclient' == '198.51.100.7'
E
E - 198.51.100.7
E + testclient
tests/test_main.py:296: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:54:40.048836Z[0m [[32m[1minfo [0m] [1mHTTP Request: GET http://testserver/health "HTTP/1.1 200 OK"[0m
------------------------------ Captured log call -------------------------------
INFO httpx:_client.py:1025 HTTP Request: GET http://testserver/health "HTTP/1.1 200 OK"
=========================== short test summary info ============================
FAILED tests/test_main.py::test_logging_middleware_respects_configured_header - AssertionError: assert 'testclient' == '198.51.100.7'
- 198.51.100.7
+ testclient
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 107 passed, 3 deselected in 0.89s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/AddNot, occurrence: 5
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -191,7 +191,7 @@
if client_ip_header:
raw = Request(scope).headers.get(client_ip_header)
- if raw:
+ if not raw:
# X-Forwarded-For may be a comma-separated list; the leftmost
# entry is the original client (proxies append to the right).
client_ip = raw.split(",")[0].strip()........................................................................ [ 34%]
...................F
=================================== FAILURES ===================================
__________________ test_lifespan_startup_shutdown_configured ___________________
def test_lifespan_startup_shutdown_configured():
"""Test app lifespan startup and shutdown when configured."""
from stampbot.main import app
# Use TestClient as context manager to trigger lifespan events
with TestClient(app) as client:
# App should be running
> response = client.get("/health")
^^^^^^^^^^^^^^^^^^^^^
tests/test_main.py:20:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:473: in get
return super().get(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1053: in get
return self.request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:445: in request
return super().request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:825: in request
return self.send(request, auth=auth, follow_redirects=follow_redirects)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:914: in send
response = self._send_handling_auth(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:942: in _send_handling_auth
response = self._send_handling_redirects(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:979: in _send_handling_redirects
response = self._send_single_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1014: in _send_single_request
response = transport.handle_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:348: in handle_request
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:345: in handle_request
portal.call(self.app, scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:334: in call
return cast(T_Retval, self.start_task_soon(func, *args).result())
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:443: in result
return self.__get_result()
^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:395: in __get_result
raise self._exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:259: in _call_func
retval = await retval_or_awaitable
^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1135: in __call__
await super().__call__(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:107: in __call__
await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:186: in __call__
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:164: in __call__
await self.app(scope, receive, _send)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.main.LoggingMiddleware object at 0x7fedd587a660>
scope = {'app': <fastapi.applications.FastAPI object at 0x7fedd5aab0e0>, 'client': ('testclient', 50000), 'extensions': {'http... b'*/*'), (b'accept-encoding', b'gzip, deflate'), (b'connection', b'keep-alive'), (b'user-agent', b'testclient')], ...}
receive = <function _TestClientTransport.handle_request.<locals>.receive at 0x7fedd587ddd0>
send = <function ServerErrorMiddleware.__call__.<locals>._send at 0x7fedd587e6c0>
async def __call__(self, scope: Any, receive: Any, send: Any) -> None:
"""Process an ASGI event, binding client IP to the structlog context.
For HTTP scopes the middleware clears any inherited structlog context,
extracts the real client IP from the configured forwarding header (or
the direct connection address as a fallback), and binds it so that
every log record emitted during the request includes ``client_ip``.
Non-HTTP scopes (lifespan, WebSocket) are passed through unchanged.
Args:
scope: ASGI connection scope.
receive: ASGI receive callable.
send: ASGI send callable.
"""
if scope["type"] == "http":
structlog.contextvars.clear_contextvars()
client_ip: str | None = None
client_ip_header: str = settings.get("client_ip_header", "X-Forwarded-For")
if client_ip_header:
raw = Request(scope).headers.get(client_ip_header)
if not raw:
# X-Forwarded-For may be a comma-separated list; the leftmost
# entry is the original client (proxies append to the right).
> client_ip = raw.split(",")[0].strip()
^^^^^^^^^
E AttributeError: 'NoneType' object has no attribute 'split'
stampbot/main.py:197: AttributeError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:30:37.772816Z[0m [[32m[1minfo [0m] [1mOpenTelemetry disabled [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.telemetry (INFO)>[0m [36m_name[0m=[35minfo[0m
[2m2026-05-16T19:30:37.792278Z[0m [[32m[1minfo [0m] [1mStarting stampbot on 0.0.0.0:8000[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.main (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'host': '0.0.0.0', 'port': 8000, 'log_level': 'INFO'}[0m
[2m2026-05-16T19:30:37.792586Z[0m [[32m[1minfo [0m] [1mGitHub App credentials configured successfully[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.main (INFO)>[0m [36m_name[0m=[35minfo[0m
[2m2026-05-16T19:30:37.793919Z[0m [[32m[1minfo [0m] [1mShutting down stampbot [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.main (INFO)>[0m [36m_name[0m=[35minfo[0m
=========================== short test summary info ============================
FAILED tests/test_main.py::test_lifespan_startup_shutdown_configured - AttributeError: 'NoneType' object has no attribute 'split'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 91 passed, 3 deselected in 1.01s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/AddNot, occurrence: 6
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -196,7 +196,7 @@
# entry is the original client (proxies append to the right).
client_ip = raw.split(",")[0].strip()
- if not client_ip and scope.get("client"):
+ if not not client_ip and scope.get("client"):
client_ip = scope["client"][0]
if client_ip:........................................................................ [ 34%]
...................................F
=================================== FAILURES ===================================
______________ test_logging_middleware_respects_configured_header ______________
def test_logging_middleware_respects_configured_header():
"""Test that logging_middleware uses the configured client_ip_header setting."""
from unittest.mock import patch
import structlog.contextvars
from stampbot.main import app
with patch("stampbot.main.settings") as mock_settings:
mock_settings.get = lambda key, default=None: (
"X-Real-IP" if key == "client_ip_header" else default
)
client = TestClient(app)
captured: dict[str, str | None] = {}
original_bind = structlog.contextvars.bind_contextvars
def capturing_bind(**kw: object) -> None:
captured.update({k: str(v) for k, v in kw.items()})
original_bind(**kw)
with patch("stampbot.main.structlog.contextvars.bind_contextvars", capturing_bind):
client.get("/health", headers={"X-Real-IP": "198.51.100.7"})
> assert captured.get("client_ip") == "198.51.100.7"
E AssertionError: assert 'testclient' == '198.51.100.7'
E
E - 198.51.100.7
E + testclient
tests/test_main.py:296: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:39:50.372463Z[0m [[32m[1minfo [0m] [1mHTTP Request: GET http://testserver/health "HTTP/1.1 200 OK"[0m
------------------------------ Captured log call -------------------------------
INFO httpx:_client.py:1025 HTTP Request: GET http://testserver/health "HTTP/1.1 200 OK"
=========================== short test summary info ============================
FAILED tests/test_main.py::test_logging_middleware_respects_configured_header - AssertionError: assert 'testclient' == '198.51.100.7'
- 198.51.100.7
+ testclient
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 107 passed, 3 deselected in 0.86s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/AddNot, occurrence: 7
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -199,7 +199,7 @@
if not client_ip and scope.get("client"):
client_ip = scope["client"][0]
- if client_ip:
+ if not client_ip:
structlog.contextvars.bind_contextvars(client_ip=client_ip)
await self.app(scope, receive, send)........................................................................ [ 34%]
...................................F
=================================== FAILURES ===================================
______________ test_logging_middleware_respects_configured_header ______________
def test_logging_middleware_respects_configured_header():
"""Test that logging_middleware uses the configured client_ip_header setting."""
from unittest.mock import patch
import structlog.contextvars
from stampbot.main import app
with patch("stampbot.main.settings") as mock_settings:
mock_settings.get = lambda key, default=None: (
"X-Real-IP" if key == "client_ip_header" else default
)
client = TestClient(app)
captured: dict[str, str | None] = {}
original_bind = structlog.contextvars.bind_contextvars
def capturing_bind(**kw: object) -> None:
captured.update({k: str(v) for k, v in kw.items()})
original_bind(**kw)
with patch("stampbot.main.structlog.contextvars.bind_contextvars", capturing_bind):
client.get("/health", headers={"X-Real-IP": "198.51.100.7"})
> assert captured.get("client_ip") == "198.51.100.7"
E AssertionError: assert None == '198.51.100.7'
E + where None = <built-in method get of dict object at 0x7fb29b21e700>('client_ip')
E + where <built-in method get of dict object at 0x7fb29b21e700> = {}.get
tests/test_main.py:296: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:53:49.331774Z[0m [[32m[1minfo [0m] [1mHTTP Request: GET http://testserver/health "HTTP/1.1 200 OK"[0m
------------------------------ Captured log call -------------------------------
INFO httpx:_client.py:1025 HTTP Request: GET http://testserver/health "HTTP/1.1 200 OK"
=========================== short test summary info ============================
FAILED tests/test_main.py::test_logging_middleware_respects_configured_header - AssertionError: assert None == '198.51.100.7'
+ where None = <built-in method get of dict object at 0x7fb29b21e700>('client_ip')
+ where <built-in method get of dict object at 0x7fb29b21e700> = {}.get
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 107 passed, 3 deselected in 0.89s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/AddNot, occurrence: 8
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -215,7 +215,7 @@
Returns:
Redirect to /setup if unconfigured, otherwise JSON status response.
"""
- if not is_configured() and settings.setup_enabled:
+ if not not is_configured() and settings.setup_enabled:
return RedirectResponse(url="/setup", status_code=307)
return Response(........................................................................ [ 34%]
.....................F
=================================== FAILURES ===================================
______________________________ test_root_endpoint ______________________________
test_client = <starlette.testclient.TestClient object at 0x7f6851e8c190>
def test_root_endpoint(test_client: TestClient):
"""Test root endpoint returns basic info."""
response = test_client.get("/")
assert response.status_code == 200
> data = response.json()
^^^^^^^^^^^^^^^
tests/test_main.py:46:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:832: in json
return jsonlib.loads(self.content, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/json/__init__.py:352: in loads
return _default_decoder.decode(s)
^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/json/decoder.py:345: in decode
obj, end = self.raw_decode(s, idx=_w(s, 0).end())
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <json.decoder.JSONDecoder object at 0x7f6859562f90>
s = '\n <!DOCTYPE html>\n <html>\n <head><title>Stampbot - Already Configured</title>\n ...pbot is ready to receive webhooks.</p>\n </div>\n </body>\n </html>\n '
idx = 13
def raw_decode(self, s, idx=0):
"""Decode a JSON document from ``s`` (a ``str`` beginning with
a JSON document) and return a 2-tuple of the Python
representation and the index in ``s`` where the document ended.
This can be used to decode a JSON document from a string that may
have extraneous data at the end.
"""
try:
obj, end = self.scan_once(s, idx)
except StopIteration as err:
> raise JSONDecodeError("Expecting value", s, err.value) from None
E json.decoder.JSONDecodeError: Expecting value: line 2 column 13 (char 13)
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/json/decoder.py:363: JSONDecodeError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:59:32.438891Z[0m [[32m[1minfo [0m] [1mHTTP Request: GET http://testserver/ "HTTP/1.1 307 Temporary Redirect"[0m
[2m2026-05-16T19:59:32.441179Z[0m [[32m[1minfo [0m] [1mHTTP Request: GET http://testserver/setup "HTTP/1.1 200 OK"[0m
------------------------------ Captured log call -------------------------------
INFO httpx:_client.py:1025 HTTP Request: GET http://testserver/ "HTTP/1.1 307 Temporary Redirect"
INFO httpx:_client.py:1025 HTTP Request: GET http://testserver/setup "HTTP/1.1 200 OK"
=========================== short test summary info ============================
FAILED tests/test_main.py::test_root_endpoint - json.decoder.JSONDecodeError: Expecting value: line 2 column 13 (char 13)
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 93 passed, 3 deselected in 0.86s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/AddNot, occurrence: 9
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -268,7 +268,7 @@
413 if body too large, 503 if not configured, 500 on internal error.
"""
# Check if app is configured
- if not is_configured():
+ if not not is_configured():
raise HTTPException(
status_code=503,
detail="Stampbot not configured. Visit /setup to complete setup.",........................................................................ [ 34%]
........................F
=================================== FAILURES ===================================
_______________________ test_request_with_content_length _______________________
test_client = <starlette.testclient.TestClient object at 0x7f430c9a7770>
def test_request_with_content_length(test_client: TestClient):
"""Test that requests with Content-Length header are tracked."""
# POST with a body will have Content-Length set automatically
response = test_client.post(
"/webhook",
content=b'{"test": "data"}',
headers={
"Content-Type": "application/json",
"X-GitHub-Event": "ping",
"X-Hub-Signature-256": "sha256=invalid",
},
)
# Will fail signature validation, but Content-Length tracking happens before that
> assert response.status_code == 401
E assert 503 == 401
E + where 503 = <Response [503 Service Unavailable]>.status_code
tests/test_main.py:79: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:29:33.948904Z[0m [[32m[1minfo [0m] [1mHTTP Request: POST http://testserver/webhook "HTTP/1.1 503 Service Unavailable"[0m
------------------------------ Captured log call -------------------------------
INFO httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 503 Service Unavailable"
=========================== short test summary info ============================
FAILED tests/test_main.py::test_request_with_content_length - assert 503 == 401
+ where 503 = <Response [503 Service Unavailable]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 96 passed, 3 deselected in 0.84s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/AddNot, occurrence: 10
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -274,7 +274,7 @@
detail="Stampbot not configured. Visit /setup to complete setup.",
)
- if not x_github_event:
+ if not not x_github_event:
errors_total.labels(error_type="missing_event").inc()
raise HTTPException(status_code=400, detail="Missing X-GitHub-Event header")
........................................................................ [ 34%]
........................F
=================================== FAILURES ===================================
_______________________ test_request_with_content_length _______________________
test_client = <starlette.testclient.TestClient object at 0x7fe8eeb83770>
def test_request_with_content_length(test_client: TestClient):
"""Test that requests with Content-Length header are tracked."""
# POST with a body will have Content-Length set automatically
response = test_client.post(
"/webhook",
content=b'{"test": "data"}',
headers={
"Content-Type": "application/json",
"X-GitHub-Event": "ping",
"X-Hub-Signature-256": "sha256=invalid",
},
)
# Will fail signature validation, but Content-Length tracking happens before that
> assert response.status_code == 401
E assert 400 == 401
E + where 400 = <Response [400 Bad Request]>.status_code
tests/test_main.py:79: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:50:04.325858Z[0m [[32m[1minfo [0m] [1mHTTP Request: POST http://testserver/webhook "HTTP/1.1 400 Bad Request"[0m
------------------------------ Captured log call -------------------------------
INFO httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 400 Bad Request"
=========================== short test summary info ============================
FAILED tests/test_main.py::test_request_with_content_length - assert 400 == 401
+ where 400 = <Response [400 Bad Request]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 96 passed, 3 deselected in 0.84s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/AddNot, occurrence: 11
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -280,7 +280,7 @@
# Check content length before reading body to prevent memory exhaustion
content_length = request.headers.get("content-length")
- if content_length and int(content_length) > MAX_WEBHOOK_BODY_SIZE:
+ if not content_length and int(content_length) > MAX_WEBHOOK_BODY_SIZE:
errors_total.labels(error_type="payload_too_large").inc()
raise HTTPException(status_code=413, detail="Request body too large")
........................................................................ [ 34%]
..............................F
=================================== FAILURES ===================================
____________________ test_webhook_content_length_too_large _____________________
test_client = <starlette.testclient.TestClient object at 0x7f114b3ab020>
def test_webhook_content_length_too_large(test_client: TestClient):
"""Test webhook rejects requests with Content-Length exceeding limit."""
response = test_client.post(
"/webhook",
content=b"x", # Small actual body
headers={
"Content-Type": "application/json",
"Content-Length": "20000000", # 20MB - exceeds 10MB limit
"X-GitHub-Event": "ping",
"X-Hub-Signature-256": "sha256=fake",
},
)
> assert response.status_code == 413
E assert 401 == 413
E + where 401 = <Response [401 Unauthorized]>.status_code
tests/test_main.py:193: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:41:14.569885Z[0m [[33m[1mwarning [0m] [1mInvalid webhook signature [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.main (INFO)>[0m [36m_name[0m=[35mwarning[0m [36mclient_ip[0m=[35mtestclient[0m
[2m2026-05-16T19:41:14.570743Z[0m [[32m[1minfo [0m] [1mHTTP Request: POST http://testserver/webhook "HTTP/1.1 401 Unauthorized"[0m
------------------------------ Captured log call -------------------------------
WARNING stampbot.main:main.py:299 {'event': 'Invalid webhook signature', 'client_ip': 'testclient', 'level': 'warning', 'timestamp': '2026-05-16T19:41:14.569885Z'}
INFO httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 401 Unauthorized"
=========================== short test summary info ============================
FAILED tests/test_main.py::test_webhook_content_length_too_large - assert 401 == 413
+ where 401 = <Response [401 Unauthorized]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 102 passed, 3 deselected in 0.91s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/AddNot, occurrence: 12
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -288,7 +288,7 @@
body = await request.body()
# Double-check actual body size (in case content-length was missing/incorrect)
- if len(body) > MAX_WEBHOOK_BODY_SIZE:
+ if not len(body) > MAX_WEBHOOK_BODY_SIZE:
errors_total.labels(error_type="payload_too_large").inc()
raise HTTPException(status_code=413, detail="Request body too large")
........................................................................ [ 34%]
........................F
=================================== FAILURES ===================================
_______________________ test_request_with_content_length _______________________
test_client = <starlette.testclient.TestClient object at 0x7f0fb3f87770>
def test_request_with_content_length(test_client: TestClient):
"""Test that requests with Content-Length header are tracked."""
# POST with a body will have Content-Length set automatically
response = test_client.post(
"/webhook",
content=b'{"test": "data"}',
headers={
"Content-Type": "application/json",
"X-GitHub-Event": "ping",
"X-Hub-Signature-256": "sha256=invalid",
},
)
# Will fail signature validation, but Content-Length tracking happens before that
> assert response.status_code == 401
E assert 413 == 401
E + where 413 = <Response [413 Request Entity Too Large]>.status_code
tests/test_main.py:79: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:53:42.503264Z[0m [[32m[1minfo [0m] [1mHTTP Request: POST http://testserver/webhook "HTTP/1.1 413 Request Entity Too Large"[0m
------------------------------ Captured log call -------------------------------
INFO httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 413 Request Entity Too Large"
=========================== short test summary info ============================
FAILED tests/test_main.py::test_request_with_content_length - assert 413 == 401
+ where 413 = <Response [413 Request Entity Too Large]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 96 passed, 3 deselected in 0.85s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/AddNot, occurrence: 13
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -293,7 +293,7 @@
raise HTTPException(status_code=413, detail="Request body too large")
# Verify signature
- if not webhook_handler.verify_signature(body, x_hub_signature_256):
+ if not not webhook_handler.verify_signature(body, x_hub_signature_256):
webhook_signature_validations_total.labels(result="invalid").inc()
errors_total.labels(error_type="signature_invalid").inc()
logger.warning("Invalid webhook signature")........................................................................ [ 34%]
........................F
=================================== FAILURES ===================================
_______________________ test_request_with_content_length _______________________
test_client = <starlette.testclient.TestClient object at 0x7f2d726ab770>
def test_request_with_content_length(test_client: TestClient):
"""Test that requests with Content-Length header are tracked."""
# POST with a body will have Content-Length set automatically
response = test_client.post(
"/webhook",
content=b'{"test": "data"}',
headers={
"Content-Type": "application/json",
"X-GitHub-Event": "ping",
"X-Hub-Signature-256": "sha256=invalid",
},
)
# Will fail signature validation, but Content-Length tracking happens before that
> assert response.status_code == 401
E assert 200 == 401
E + where 200 = <Response [200 OK]>.status_code
tests/test_main.py:79: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:29:40.311091Z[0m [[32m[1minfo [0m] [1mReceived ping event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mclient_ip[0m=[35mtestclient[0m [36mextra[0m=[35m{'event_type': 'ping', 'action': ''}[0m
[2m2026-05-16T19:29:40.312042Z[0m [[32m[1minfo [0m] [1mHTTP Request: POST http://testserver/webhook "HTTP/1.1 200 OK"[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'ping', 'action': ''}, 'event': 'Received ping event', 'client_ip': 'testclient', 'level': 'info', 'timestamp': '2026-05-16T19:29:40.311091Z'}
INFO httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 200 OK"
=========================== short test summary info ============================
FAILED tests/test_main.py::test_request_with_content_length - assert 200 == 401
+ where 200 = <Response [200 OK]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 96 passed, 3 deselected in 0.82s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/AddNot, occurrence: 14
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -345,7 +345,7 @@
Raises:
HTTPException: If setup is disabled (403).
"""
- if not settings.setup_enabled:
+ if not not settings.setup_enabled:
raise HTTPException(status_code=403, detail="Setup not allowed in this environment")
if is_configured():........................................................................ [ 34%]
...........................................................F
=================================== FAILURES ===================================
_______ TestSetupEndpointsConfigured.test_setup_shows_already_configured _______
self = <tests.test_setup_endpoints.TestSetupEndpointsConfigured object at 0x7fcef21c3b10>
test_client = <starlette.testclient.TestClient object at 0x7fceecf12be0>
def test_setup_shows_already_configured(self, test_client):
"""Test /setup shows already configured message."""
response = test_client.get("/setup")
> assert response.status_code == 200
E assert 403 == 200
E + where 403 = <Response [403 Forbidden]>.status_code
tests/test_setup_endpoints.py:22: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:31:30.345916Z[0m [[32m[1minfo [0m] [1mHTTP Request: GET http://testserver/setup "HTTP/1.1 403 Forbidden"[0m
------------------------------ Captured log call -------------------------------
INFO httpx:_client.py:1025 HTTP Request: GET http://testserver/setup "HTTP/1.1 403 Forbidden"
=========================== short test summary info ============================
FAILED tests/test_setup_endpoints.py::TestSetupEndpointsConfigured::test_setup_shows_already_configured - assert 403 == 200
+ where 403 = <Response [403 Forbidden]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 131 passed, 3 deselected in 0.98s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/AddNot, occurrence: 15
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -348,7 +348,7 @@
if not settings.setup_enabled:
raise HTTPException(status_code=403, detail="Setup not allowed in this environment")
- if is_configured():
+ if not is_configured():
return HTMLResponse(
content="""
<!DOCTYPE html>........................................................................ [ 34%]
...........................................................F
=================================== FAILURES ===================================
_______ TestSetupEndpointsConfigured.test_setup_shows_already_configured _______
self = <tests.test_setup_endpoints.TestSetupEndpointsConfigured object at 0x7fd2426dbb10>
test_client = <starlette.testclient.TestClient object at 0x7fd23d412be0>
def test_setup_shows_already_configured(self, test_client):
"""Test /setup shows already configured message."""
> response = test_client.get("/setup")
^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_setup_endpoints.py:20:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:473: in get
return super().get(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1053: in get
return self.request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:445: in request
return super().request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:825: in request
return self.send(request, auth=auth, follow_redirects=follow_redirects)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:914: in send
response = self._send_handling_auth(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:942: in _send_handling_auth
response = self._send_handling_redirects(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:979: in _send_handling_redirects
response = self._send_single_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1014: in _send_single_request
response = transport.handle_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:348: in handle_request
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:345: in handle_request
portal.call(self.app, scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:334: in call
return cast(T_Retval, self.start_task_soon(func, *args).result())
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:450: in result
return self.__get_result()
^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:395: in __get_result
raise self._exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:259: in _call_func
retval = await retval_or_awaitable
^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1135: in __call__
await super().__call__(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:107: in __call__
await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:186: in __call__
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:164: in __call__
await self.app(scope, receive, _send)
stampbot/main.py:205: in __call__
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:191: in __call__
with recv_stream, send_stream, collapse_excgroups():
^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/contextlib.py:162: in __exit__
self.gen.throw(value)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_utils.py:85: in collapse_excgroups
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:193: in __call__
response = await self.dispatch_func(request, call_next)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:120: in metrics_middleware
response = await call_next(request)
^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:168: in call_next
raise app_exc from app_exc.__cause__ or app_exc.__context__
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:144: in coro
await self.app(scope, receive_or_disconnect, send_no_error)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/exceptions.py:63: in __call__
await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/middleware/asyncexitstack.py:18: in __call__
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:716: in __call__
await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:736: in app
await route.handle(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:290: in handle
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:115: in app
await wrap_app_handling_exceptions(app, request)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:101: in app
response = await f(request)
^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:355: in app
raw_response = await run_endpoint_function(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:243: in run_endpoint_function
return await dependant.call(**values)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:396: in setup_page
manifest = create_manifest(redirect_url, webhook_url=webhook_url)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/manifest.py:85: in create_manifest
_validate_url(redirect_url, "redirect_url")
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
url = 'http://testserver/setup/callback', name = 'redirect_url'
def _validate_url(url: str, name: str) -> None:
"""Validate that a URL is well-formed and uses HTTPS (or localhost).
Args:
url: URL to validate
name: Name of the URL parameter for error messages
Raises:
ValueError: If URL is invalid or doesn't use HTTPS
"""
parsed = urlparse(url)
if not parsed.scheme or not parsed.netloc:
raise ValueError(f"Invalid {name}: must be a complete URL")
# Allow http only for localhost (development)
if parsed.scheme == "http":
if parsed.hostname not in ("localhost", "127.0.0.1", "::1"):
> raise ValueError(f"Invalid {name}: must use HTTPS (HTTP only allowed for localhost)")
E ValueError: Invalid redirect_url: must use HTTPS (HTTP only allowed for localhost)
stampbot/manifest.py:36: ValueError
=========================== short test summary info ============================
FAILED tests/test_setup_endpoints.py::TestSetupEndpointsConfigured::test_setup_shows_already_configured - ValueError: Invalid redirect_url: must use HTTPS (HTTP only allowed for localhost)
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 131 passed, 3 deselected in 1.36s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/AddNot, occurrence: 16
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -379,7 +379,7 @@
# 2. Proxy headers (X-Forwarded-Proto + Host/X-Forwarded-Host)
# 3. Request base URL (fallback)
configured_base_url = settings.get("base_url", "")
- if configured_base_url:
+ if not configured_base_url:
base_url = configured_base_url.rstrip("/")
else:
# Check for proxy headers from ingress/load balancer........................................................................ [ 34%]
..............................................................F
=================================== FAILURES ===================================
___ TestSetupEndpointsUnconfigured.test_setup_shows_wizard_when_unconfigured ___
self = <tests.test_setup_endpoints.TestSetupEndpointsUnconfigured object at 0x7f415606fd90>
def test_setup_shows_wizard_when_unconfigured(self):
"""Test /setup shows setup wizard page when not configured."""
with (
patch("stampbot.main.is_configured", return_value=False),
patch("stampbot.main.settings") as mock_settings,
):
mock_settings.setup_enabled = True
mock_settings.get.return_value = "" # No base_url override
from fastapi.testclient import TestClient
from stampbot.main import app
client = TestClient(app, raise_server_exceptions=False)
response = client.get("/setup", headers={"Host": "localhost:8000"})
> assert response.status_code == 200
E assert 500 == 200
E + where 500 = <Response [500 Internal Server Error]>.status_code
tests/test_setup_endpoints.py:75: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:55:53.885864Z[0m [[32m[1minfo [0m] [1mHTTP Request: GET http://testserver/setup "HTTP/1.1 500 Internal Server Error"[0m
------------------------------ Captured log call -------------------------------
INFO httpx:_client.py:1025 HTTP Request: GET http://testserver/setup "HTTP/1.1 500 Internal Server Error"
=========================== short test summary info ============================
FAILED tests/test_setup_endpoints.py::TestSetupEndpointsUnconfigured::test_setup_shows_wizard_when_unconfigured - assert 500 == 200
+ where 500 = <Response [500 Internal Server Error]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 134 passed, 3 deselected in 1.02s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/AddNot, occurrence: 17
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -386,7 +386,7 @@
# Cloud Run sends X-Forwarded-Proto but uses standard Host header
forwarded_proto = request.headers.get("X-Forwarded-Proto", "")
forwarded_host = request.headers.get("X-Forwarded-Host", request.headers.get("Host", ""))
- if forwarded_proto and forwarded_host:
+ if not forwarded_proto and forwarded_host:
base_url = f"{forwarded_proto}://{forwarded_host}"
else:
base_url = str(request.base_url).rstrip("/")........................................................................ [ 34%]
..............................................................F
=================================== FAILURES ===================================
___ TestSetupEndpointsUnconfigured.test_setup_shows_wizard_when_unconfigured ___
self = <tests.test_setup_endpoints.TestSetupEndpointsUnconfigured object at 0x7fc45a0c7d90>
def test_setup_shows_wizard_when_unconfigured(self):
"""Test /setup shows setup wizard page when not configured."""
with (
patch("stampbot.main.is_configured", return_value=False),
patch("stampbot.main.settings") as mock_settings,
):
mock_settings.setup_enabled = True
mock_settings.get.return_value = "" # No base_url override
from fastapi.testclient import TestClient
from stampbot.main import app
client = TestClient(app, raise_server_exceptions=False)
response = client.get("/setup", headers={"Host": "localhost:8000"})
> assert response.status_code == 200
E assert 500 == 200
E + where 500 = <Response [500 Internal Server Error]>.status_code
tests/test_setup_endpoints.py:75: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:46:02.275836Z[0m [[32m[1minfo [0m] [1mHTTP Request: GET http://testserver/setup "HTTP/1.1 500 Internal Server Error"[0m
------------------------------ Captured log call -------------------------------
INFO httpx:_client.py:1025 HTTP Request: GET http://testserver/setup "HTTP/1.1 500 Internal Server Error"
=========================== short test summary info ============================
FAILED tests/test_setup_endpoints.py::TestSetupEndpointsUnconfigured::test_setup_shows_wizard_when_unconfigured - assert 500 == 200
+ where 500 = <Response [500 Internal Server Error]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 134 passed, 3 deselected in 1.00s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/AddNot, occurrence: 18
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -464,7 +464,7 @@
Raises:
HTTPException: If setup disabled (403) or code exchange fails (500).
"""
- if not settings.setup_enabled:
+ if not not settings.setup_enabled:
raise HTTPException(status_code=403, detail="Setup not allowed")
try:........................................................................ [ 34%]
..................................................................F
=================================== FAILURES ===================================
________________ TestSetupCallback.test_callback_exchanges_code ________________
self = <tests.test_setup_endpoints.TestSetupCallback object at 0x7f02f625fed0>
test_client = <starlette.testclient.TestClient object at 0x7f02f1c07bd0>
def test_callback_exchanges_code(self, test_client):
"""Test callback exchanges code for credentials."""
mock_credentials = {
"id": 12345,
"pem": "-----BEGIN RSA PRIVATE KEY-----\ntest\n-----END RSA PRIVATE KEY-----",
"webhook_secret": "test-secret",
"slug": "test-stampbot",
"name": "Test Stampbot",
}
with patch(
"stampbot.main.exchange_code_for_credentials",
new_callable=AsyncMock,
return_value=mock_credentials,
):
response = test_client.get("/setup/callback?code=test-code")
> assert response.status_code == 200
E assert 403 == 200
E + where 403 = <Response [403 Forbidden]>.status_code
tests/test_setup_endpoints.py:179: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:23:12.034143Z[0m [[32m[1minfo [0m] [1mHTTP Request: GET http://testserver/setup/callback?code=test-code "HTTP/1.1 403 Forbidden"[0m
------------------------------ Captured log call -------------------------------
INFO httpx:_client.py:1025 HTTP Request: GET http://testserver/setup/callback?code=test-code "HTTP/1.1 403 Forbidden"
=========================== short test summary info ============================
FAILED tests/test_setup_endpoints.py::TestSetupCallback::test_callback_exchanges_code - assert 403 == 200
+ where 403 = <Response [403 Forbidden]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 138 passed, 3 deselected in 1.03s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/AddNot, occurrence: 19
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -573,6 +573,6 @@
return {
"configured": is_configured(),
"setup_enabled": settings.setup_enabled,
- "app_id": settings.app_id if is_configured() else None,
+ "app_id": settings.app_id if not is_configured() else None,
}
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceAndWithOr, occurrence: 0
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -196,7 +196,7 @@
# entry is the original client (proxies append to the right).
client_ip = raw.split(",")[0].strip()
- if not client_ip and scope.get("client"):
+ if not client_ip or scope.get("client"):
client_ip = scope["client"][0]
if client_ip:........................................................................ [ 34%]
...................................F
=================================== FAILURES ===================================
______________ test_logging_middleware_respects_configured_header ______________
def test_logging_middleware_respects_configured_header():
"""Test that logging_middleware uses the configured client_ip_header setting."""
from unittest.mock import patch
import structlog.contextvars
from stampbot.main import app
with patch("stampbot.main.settings") as mock_settings:
mock_settings.get = lambda key, default=None: (
"X-Real-IP" if key == "client_ip_header" else default
)
client = TestClient(app)
captured: dict[str, str | None] = {}
original_bind = structlog.contextvars.bind_contextvars
def capturing_bind(**kw: object) -> None:
captured.update({k: str(v) for k, v in kw.items()})
original_bind(**kw)
with patch("stampbot.main.structlog.contextvars.bind_contextvars", capturing_bind):
client.get("/health", headers={"X-Real-IP": "198.51.100.7"})
> assert captured.get("client_ip") == "198.51.100.7"
E AssertionError: assert 'testclient' == '198.51.100.7'
E
E - 198.51.100.7
E + testclient
tests/test_main.py:296: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:26:40.762077Z[0m [[32m[1minfo [0m] [1mHTTP Request: GET http://testserver/health "HTTP/1.1 200 OK"[0m
------------------------------ Captured log call -------------------------------
INFO httpx:_client.py:1025 HTTP Request: GET http://testserver/health "HTTP/1.1 200 OK"
=========================== short test summary info ============================
FAILED tests/test_main.py::test_logging_middleware_respects_configured_header - AssertionError: assert 'testclient' == '198.51.100.7'
- 198.51.100.7
+ testclient
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 107 passed, 3 deselected in 0.87s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceAndWithOr, occurrence: 1
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -215,7 +215,7 @@
Returns:
Redirect to /setup if unconfigured, otherwise JSON status response.
"""
- if not is_configured() and settings.setup_enabled:
+ if not is_configured() or settings.setup_enabled:
return RedirectResponse(url="/setup", status_code=307)
return Response(........................................................................ [ 34%]
.....................F
=================================== FAILURES ===================================
______________________________ test_root_endpoint ______________________________
test_client = <starlette.testclient.TestClient object at 0x7f84efb74190>
def test_root_endpoint(test_client: TestClient):
"""Test root endpoint returns basic info."""
response = test_client.get("/")
assert response.status_code == 200
> data = response.json()
^^^^^^^^^^^^^^^
tests/test_main.py:46:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:832: in json
return jsonlib.loads(self.content, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/json/__init__.py:352: in loads
return _default_decoder.decode(s)
^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/json/decoder.py:345: in decode
obj, end = self.raw_decode(s, idx=_w(s, 0).end())
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <json.decoder.JSONDecoder object at 0x7f84f7162f90>
s = '\n <!DOCTYPE html>\n <html>\n <head><title>Stampbot - Already Configured</title>\n ...pbot is ready to receive webhooks.</p>\n </div>\n </body>\n </html>\n '
idx = 13
def raw_decode(self, s, idx=0):
"""Decode a JSON document from ``s`` (a ``str`` beginning with
a JSON document) and return a 2-tuple of the Python
representation and the index in ``s`` where the document ended.
This can be used to decode a JSON document from a string that may
have extraneous data at the end.
"""
try:
obj, end = self.scan_once(s, idx)
except StopIteration as err:
> raise JSONDecodeError("Expecting value", s, err.value) from None
E json.decoder.JSONDecodeError: Expecting value: line 2 column 13 (char 13)
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/json/decoder.py:363: JSONDecodeError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T20:09:26.268042Z[0m [[32m[1minfo [0m] [1mHTTP Request: GET http://testserver/ "HTTP/1.1 307 Temporary Redirect"[0m
[2m2026-05-16T20:09:26.270425Z[0m [[32m[1minfo [0m] [1mHTTP Request: GET http://testserver/setup "HTTP/1.1 200 OK"[0m
------------------------------ Captured log call -------------------------------
INFO httpx:_client.py:1025 HTTP Request: GET http://testserver/ "HTTP/1.1 307 Temporary Redirect"
INFO httpx:_client.py:1025 HTTP Request: GET http://testserver/setup "HTTP/1.1 200 OK"
=========================== short test summary info ============================
FAILED tests/test_main.py::test_root_endpoint - json.decoder.JSONDecodeError: Expecting value: line 2 column 13 (char 13)
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 93 passed, 3 deselected in 0.92s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceAndWithOr, occurrence: 2
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -280,7 +280,7 @@
# Check content length before reading body to prevent memory exhaustion
content_length = request.headers.get("content-length")
- if content_length and int(content_length) > MAX_WEBHOOK_BODY_SIZE:
+ if content_length or int(content_length) > MAX_WEBHOOK_BODY_SIZE:
errors_total.labels(error_type="payload_too_large").inc()
raise HTTPException(status_code=413, detail="Request body too large")
........................................................................ [ 34%]
........................F
=================================== FAILURES ===================================
_______________________ test_request_with_content_length _______________________
test_client = <starlette.testclient.TestClient object at 0x7f4e76ba7770>
def test_request_with_content_length(test_client: TestClient):
"""Test that requests with Content-Length header are tracked."""
# POST with a body will have Content-Length set automatically
response = test_client.post(
"/webhook",
content=b'{"test": "data"}',
headers={
"Content-Type": "application/json",
"X-GitHub-Event": "ping",
"X-Hub-Signature-256": "sha256=invalid",
},
)
# Will fail signature validation, but Content-Length tracking happens before that
> assert response.status_code == 401
E assert 413 == 401
E + where 413 = <Response [413 Request Entity Too Large]>.status_code
tests/test_main.py:79: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:50:17.664244Z[0m [[32m[1minfo [0m] [1mHTTP Request: POST http://testserver/webhook "HTTP/1.1 413 Request Entity Too Large"[0m
------------------------------ Captured log call -------------------------------
INFO httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 413 Request Entity Too Large"
=========================== short test summary info ============================
FAILED tests/test_main.py::test_request_with_content_length - assert 413 == 401
+ where 413 = <Response [413 Request Entity Too Large]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 96 passed, 3 deselected in 0.84s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceAndWithOr, occurrence: 3
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -386,7 +386,7 @@
# Cloud Run sends X-Forwarded-Proto but uses standard Host header
forwarded_proto = request.headers.get("X-Forwarded-Proto", "")
forwarded_host = request.headers.get("X-Forwarded-Host", request.headers.get("Host", ""))
- if forwarded_proto and forwarded_host:
+ if forwarded_proto or forwarded_host:
base_url = f"{forwarded_proto}://{forwarded_host}"
else:
base_url = str(request.base_url).rstrip("/")........................................................................ [ 34%]
..............................................................F
=================================== FAILURES ===================================
___ TestSetupEndpointsUnconfigured.test_setup_shows_wizard_when_unconfigured ___
self = <tests.test_setup_endpoints.TestSetupEndpointsUnconfigured object at 0x7f036addfd90>
def test_setup_shows_wizard_when_unconfigured(self):
"""Test /setup shows setup wizard page when not configured."""
with (
patch("stampbot.main.is_configured", return_value=False),
patch("stampbot.main.settings") as mock_settings,
):
mock_settings.setup_enabled = True
mock_settings.get.return_value = "" # No base_url override
from fastapi.testclient import TestClient
from stampbot.main import app
client = TestClient(app, raise_server_exceptions=False)
response = client.get("/setup", headers={"Host": "localhost:8000"})
> assert response.status_code == 200
E assert 500 == 200
E + where 500 = <Response [500 Internal Server Error]>.status_code
tests/test_setup_endpoints.py:75: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T20:09:09.491992Z[0m [[32m[1minfo [0m] [1mHTTP Request: GET http://testserver/setup "HTTP/1.1 500 Internal Server Error"[0m
------------------------------ Captured log call -------------------------------
INFO httpx:_client.py:1025 HTTP Request: GET http://testserver/setup "HTTP/1.1 500 Internal Server Error"
=========================== short test summary info ============================
FAILED tests/test_setup_endpoints.py::TestSetupEndpointsUnconfigured::test_setup_shows_wizard_when_unconfigured - assert 500 == 200
+ where 500 = <Response [500 Internal Server Error]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 134 passed, 3 deselected in 1.04s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceOrWithAnd, occurrence: 0
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -315,7 +315,7 @@
result = await webhook_handler.handle_event(x_github_event, payload)
duration = time.time() - start_time
- webhook_processing_duration_seconds.labels(event_type=x_github_event or "unknown").observe(
+ webhook_processing_duration_seconds.labels(event_type=x_github_event and "unknown").observe(
duration
)
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.62s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ExceptionReplacer, occurrence: 0
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -304,7 +304,7 @@
# Parse JSON payload
try:
payload = await request.json()
- except Exception as e:
+ except CosmicRayTestingException as e:
errors_total.labels(error_type="payload_invalid").inc()
logger.error("Failed to parse webhook payload: %s", e)
raise HTTPException(status_code=400, detail="Invalid JSON payload") from None........................................................................ [ 34%]
............................F
=================================== FAILURES ===================================
__________________________ test_webhook_invalid_json ___________________________
request = <starlette.requests.Request object at 0x7f989d5e3120>
x_github_event = 'ping'
x_hub_signature_256 = 'sha256=dc481541cb8394ab16852fc7c7cbce720b045e7456801ae3998a45f260ad2915'
@app.post("/webhook")
async def webhook(
request: Request,
x_github_event: str = Header(None, alias="X-GitHub-Event"),
x_hub_signature_256: str = Header(None, alias="X-Hub-Signature-256"),
) -> dict[str, Any]:
"""GitHub webhook endpoint.
Args:
request: FastAPI request.
x_github_event: GitHub event type.
x_hub_signature_256: Webhook signature.
Returns:
Response dictionary with status and message.
Raises:
HTTPException: 400 if event header or JSON invalid, 401 if signature invalid,
413 if body too large, 503 if not configured, 500 on internal error.
"""
# Check if app is configured
if not is_configured():
raise HTTPException(
status_code=503,
detail="Stampbot not configured. Visit /setup to complete setup.",
)
if not x_github_event:
errors_total.labels(error_type="missing_event").inc()
raise HTTPException(status_code=400, detail="Missing X-GitHub-Event header")
# Check content length before reading body to prevent memory exhaustion
content_length = request.headers.get("content-length")
if content_length and int(content_length) > MAX_WEBHOOK_BODY_SIZE:
errors_total.labels(error_type="payload_too_large").inc()
raise HTTPException(status_code=413, detail="Request body too large")
# Get raw body for signature verification
body = await request.body()
# Double-check actual body size (in case content-length was missing/incorrect)
if len(body) > MAX_WEBHOOK_BODY_SIZE:
errors_total.labels(error_type="payload_too_large").inc()
raise HTTPException(status_code=413, detail="Request body too large")
# Verify signature
if not webhook_handler.verify_signature(body, x_hub_signature_256):
webhook_signature_validations_total.labels(result="invalid").inc()
errors_total.labels(error_type="signature_invalid").inc()
logger.warning("Invalid webhook signature")
raise HTTPException(status_code=401, detail="Invalid signature")
webhook_signature_validations_total.labels(result="valid").inc()
# Parse JSON payload
try:
> payload = await request.json()
^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:306:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/requests.py:251: in json
self._json = json.loads(body)
^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/json/__init__.py:352: in loads
return _default_decoder.decode(s)
^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/json/decoder.py:345: in decode
obj, end = self.raw_decode(s, idx=_w(s, 0).end())
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <json.decoder.JSONDecoder object at 0x7f98a4c66f90>, s = 'not valid json'
idx = 0
def raw_decode(self, s, idx=0):
"""Decode a JSON document from ``s`` (a ``str`` beginning with
a JSON document) and return a 2-tuple of the Python
representation and the index in ``s`` where the document ended.
This can be used to decode a JSON document from a string that may
have extraneous data at the end.
"""
try:
obj, end = self.scan_once(s, idx)
except StopIteration as err:
> raise JSONDecodeError("Expecting value", s, err.value) from None
E json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/json/decoder.py:363: JSONDecodeError
The above exception was the direct cause of the following exception:
test_client = <starlette.testclient.TestClient object at 0x7f989d5ba690>
def test_webhook_invalid_json(test_client: TestClient):
"""Test webhook rejects invalid JSON payload."""
import hashlib
import hmac
from stampbot.webhook_handler import webhook_handler
# Create a valid signature for invalid JSON
body = b"not valid json"
signature = (
"sha256="
+ hmac.new(
webhook_handler.webhook_secret,
body,
hashlib.sha256,
).hexdigest()
)
> response = test_client.post(
"/webhook",
content=body,
headers={
"Content-Type": "application/json",
"X-GitHub-Event": "ping",
"X-Hub-Signature-256": signature,
},
)
tests/test_main.py:136:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:546: in post
return super().post(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1144: in post
return self.request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:445: in request
return super().request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:825: in request
return self.send(request, auth=auth, follow_redirects=follow_redirects)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:914: in send
response = self._send_handling_auth(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:942: in _send_handling_auth
response = self._send_handling_redirects(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:979: in _send_handling_redirects
response = self._send_single_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1014: in _send_single_request
response = transport.handle_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:348: in handle_request
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:345: in handle_request
portal.call(self.app, scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:334: in call
return cast(T_Retval, self.start_task_soon(func, *args).result())
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:443: in result
return self.__get_result()
^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:395: in __get_result
raise self._exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:259: in _call_func
retval = await retval_or_awaitable
^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1135: in __call__
await super().__call__(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:107: in __call__
await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:186: in __call__
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:164: in __call__
await self.app(scope, receive, _send)
stampbot/main.py:205: in __call__
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:191: in __call__
with recv_stream, send_stream, collapse_excgroups():
^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/contextlib.py:162: in __exit__
self.gen.throw(value)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_utils.py:85: in collapse_excgroups
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:193: in __call__
response = await self.dispatch_func(request, call_next)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:120: in metrics_middleware
response = await call_next(request)
^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:168: in call_next
raise app_exc from app_exc.__cause__ or app_exc.__context__
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:144: in coro
await self.app(scope, receive_or_disconnect, send_no_error)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/exceptions.py:63: in __call__
await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/middleware/asyncexitstack.py:18: in __call__
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:716: in __call__
await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:736: in app
await route.handle(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:290: in handle
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:115: in app
await wrap_app_handling_exceptions(app, request)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:101: in app
response = await f(request)
^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:355: in app
raw_response = await run_endpoint_function(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:243: in run_endpoint_function
return await dependant.call(**values)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
request = <starlette.requests.Request object at 0x7f989d5e3120>
x_github_event = 'ping'
x_hub_signature_256 = 'sha256=dc481541cb8394ab16852fc7c7cbce720b045e7456801ae3998a45f260ad2915'
@app.post("/webhook")
async def webhook(
request: Request,
x_github_event: str = Header(None, alias="X-GitHub-Event"),
x_hub_signature_256: str = Header(None, alias="X-Hub-Signature-256"),
) -> dict[str, Any]:
"""GitHub webhook endpoint.
Args:
request: FastAPI request.
x_github_event: GitHub event type.
x_hub_signature_256: Webhook signature.
Returns:
Response dictionary with status and message.
Raises:
HTTPException: 400 if event header or JSON invalid, 401 if signature invalid,
413 if body too large, 503 if not configured, 500 on internal error.
"""
# Check if app is configured
if not is_configured():
raise HTTPException(
status_code=503,
detail="Stampbot not configured. Visit /setup to complete setup.",
)
if not x_github_event:
errors_total.labels(error_type="missing_event").inc()
raise HTTPException(status_code=400, detail="Missing X-GitHub-Event header")
# Check content length before reading body to prevent memory exhaustion
content_length = request.headers.get("content-length")
if content_length and int(content_length) > MAX_WEBHOOK_BODY_SIZE:
errors_total.labels(error_type="payload_too_large").inc()
raise HTTPException(status_code=413, detail="Request body too large")
# Get raw body for signature verification
body = await request.body()
# Double-check actual body size (in case content-length was missing/incorrect)
if len(body) > MAX_WEBHOOK_BODY_SIZE:
errors_total.labels(error_type="payload_too_large").inc()
raise HTTPException(status_code=413, detail="Request body too large")
# Verify signature
if not webhook_handler.verify_signature(body, x_hub_signature_256):
webhook_signature_validations_total.labels(result="invalid").inc()
errors_total.labels(error_type="signature_invalid").inc()
logger.warning("Invalid webhook signature")
raise HTTPException(status_code=401, detail="Invalid signature")
webhook_signature_validations_total.labels(result="valid").inc()
# Parse JSON payload
try:
payload = await request.json()
> except CosmicRayTestingException as e:
^^^^^^^^^^^^^^^^^^^^^^^^^
E NameError: name 'CosmicRayTestingException' is not defined
stampbot/main.py:307: NameError
=========================== short test summary info ============================
FAILED tests/test_main.py::test_webhook_invalid_json - NameError: name 'CosmicRayTestingException' is not defined
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 100 passed, 3 deselected in 1.19s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ExceptionReplacer, occurrence: 1
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -320,7 +320,7 @@
)
return result
- except Exception as e:
+ except CosmicRayTestingException as e:
errors_total.labels(error_type="webhook_handler_error").inc()
sanitized = _sanitize_error(e)
logger.error("Error handling webhook event: %s", sanitized, extra={"error": sanitized})........................................................................ [ 34%]
.....................................F
=================================== FAILURES ===================================
________________________ test_webhook_handler_exception ________________________
request = <starlette.requests.Request object at 0x7fb896b847e0>
x_github_event = 'ping'
x_hub_signature_256 = 'sha256=595bde46b32458346f405d2b0b88ade07c42ddd464fa4cb8df965915cfa558d2'
@app.post("/webhook")
async def webhook(
request: Request,
x_github_event: str = Header(None, alias="X-GitHub-Event"),
x_hub_signature_256: str = Header(None, alias="X-Hub-Signature-256"),
) -> dict[str, Any]:
"""GitHub webhook endpoint.
Args:
request: FastAPI request.
x_github_event: GitHub event type.
x_hub_signature_256: Webhook signature.
Returns:
Response dictionary with status and message.
Raises:
HTTPException: 400 if event header or JSON invalid, 401 if signature invalid,
413 if body too large, 503 if not configured, 500 on internal error.
"""
# Check if app is configured
if not is_configured():
raise HTTPException(
status_code=503,
detail="Stampbot not configured. Visit /setup to complete setup.",
)
if not x_github_event:
errors_total.labels(error_type="missing_event").inc()
raise HTTPException(status_code=400, detail="Missing X-GitHub-Event header")
# Check content length before reading body to prevent memory exhaustion
content_length = request.headers.get("content-length")
if content_length and int(content_length) > MAX_WEBHOOK_BODY_SIZE:
errors_total.labels(error_type="payload_too_large").inc()
raise HTTPException(status_code=413, detail="Request body too large")
# Get raw body for signature verification
body = await request.body()
# Double-check actual body size (in case content-length was missing/incorrect)
if len(body) > MAX_WEBHOOK_BODY_SIZE:
errors_total.labels(error_type="payload_too_large").inc()
raise HTTPException(status_code=413, detail="Request body too large")
# Verify signature
if not webhook_handler.verify_signature(body, x_hub_signature_256):
webhook_signature_validations_total.labels(result="invalid").inc()
errors_total.labels(error_type="signature_invalid").inc()
logger.warning("Invalid webhook signature")
raise HTTPException(status_code=401, detail="Invalid signature")
webhook_signature_validations_total.labels(result="valid").inc()
# Parse JSON payload
try:
payload = await request.json()
except Exception as e:
errors_total.labels(error_type="payload_invalid").inc()
logger.error("Failed to parse webhook payload: %s", e)
raise HTTPException(status_code=400, detail="Invalid JSON payload") from None
# Handle event with timing
try:
start_time = time.time()
> result = await webhook_handler.handle_event(x_github_event, payload)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:315:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <AsyncMock name='handle_event' id='140430779320032'>
args = ('ping', {'zen': 'test'}), kwargs = {}
_call = call('ping', {'zen': 'test'}), effect = Exception('Unexpected error')
async def _execute_mock_call(self, /, *args, **kwargs):
# This is nearly just like super(), except for special handling
# of coroutines
_call = _Call((args, kwargs), two=True)
self.await_count += 1
self.await_args = _call
self.await_args_list.append(_call)
effect = self.side_effect
if effect is not None:
if _is_exception(effect):
> raise effect
E Exception: Unexpected error
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:2326: Exception
The above exception was the direct cause of the following exception:
test_client = <starlette.testclient.TestClient object at 0x7fb896ccfdf0>
def test_webhook_handler_exception(test_client: TestClient):
"""Test webhook returns 500 when handler raises exception."""
from stampbot.webhook_handler import webhook_handler
body = json.dumps({"zen": "test"}).encode()
signature = (
"sha256="
+ hmac.new(
webhook_handler.webhook_secret,
body,
hashlib.sha256,
).hexdigest()
)
with patch("stampbot.main.webhook_handler.handle_event") as mock_handle:
mock_handle.side_effect = Exception("Unexpected error")
> response = test_client.post(
"/webhook",
content=body,
headers={
"Content-Type": "application/json",
"X-GitHub-Event": "ping",
"X-Hub-Signature-256": signature,
},
)
tests/test_main.py:350:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:546: in post
return super().post(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1144: in post
return self.request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:445: in request
return super().request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:825: in request
return self.send(request, auth=auth, follow_redirects=follow_redirects)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:914: in send
response = self._send_handling_auth(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:942: in _send_handling_auth
response = self._send_handling_redirects(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:979: in _send_handling_redirects
response = self._send_single_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1014: in _send_single_request
response = transport.handle_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:348: in handle_request
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:345: in handle_request
portal.call(self.app, scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:334: in call
return cast(T_Retval, self.start_task_soon(func, *args).result())
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:450: in result
return self.__get_result()
^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:395: in __get_result
raise self._exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:259: in _call_func
retval = await retval_or_awaitable
^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1135: in __call__
await super().__call__(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:107: in __call__
await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:186: in __call__
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:164: in __call__
await self.app(scope, receive, _send)
stampbot/main.py:205: in __call__
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:191: in __call__
with recv_stream, send_stream, collapse_excgroups():
^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/contextlib.py:162: in __exit__
self.gen.throw(value)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_utils.py:85: in collapse_excgroups
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:193: in __call__
response = await self.dispatch_func(request, call_next)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:120: in metrics_middleware
response = await call_next(request)
^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:168: in call_next
raise app_exc from app_exc.__cause__ or app_exc.__context__
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:144: in coro
await self.app(scope, receive_or_disconnect, send_no_error)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/exceptions.py:63: in __call__
await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/middleware/asyncexitstack.py:18: in __call__
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:716: in __call__
await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:736: in app
await route.handle(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:290: in handle
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:115: in app
await wrap_app_handling_exceptions(app, request)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:101: in app
response = await f(request)
^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:355: in app
raw_response = await run_endpoint_function(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:243: in run_endpoint_function
return await dependant.call(**values)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
request = <starlette.requests.Request object at 0x7fb896b847e0>
x_github_event = 'ping'
x_hub_signature_256 = 'sha256=595bde46b32458346f405d2b0b88ade07c42ddd464fa4cb8df965915cfa558d2'
@app.post("/webhook")
async def webhook(
request: Request,
x_github_event: str = Header(None, alias="X-GitHub-Event"),
x_hub_signature_256: str = Header(None, alias="X-Hub-Signature-256"),
) -> dict[str, Any]:
"""GitHub webhook endpoint.
Args:
request: FastAPI request.
x_github_event: GitHub event type.
x_hub_signature_256: Webhook signature.
Returns:
Response dictionary with status and message.
Raises:
HTTPException: 400 if event header or JSON invalid, 401 if signature invalid,
413 if body too large, 503 if not configured, 500 on internal error.
"""
# Check if app is configured
if not is_configured():
raise HTTPException(
status_code=503,
detail="Stampbot not configured. Visit /setup to complete setup.",
)
if not x_github_event:
errors_total.labels(error_type="missing_event").inc()
raise HTTPException(status_code=400, detail="Missing X-GitHub-Event header")
# Check content length before reading body to prevent memory exhaustion
content_length = request.headers.get("content-length")
if content_length and int(content_length) > MAX_WEBHOOK_BODY_SIZE:
errors_total.labels(error_type="payload_too_large").inc()
raise HTTPException(status_code=413, detail="Request body too large")
# Get raw body for signature verification
body = await request.body()
# Double-check actual body size (in case content-length was missing/incorrect)
if len(body) > MAX_WEBHOOK_BODY_SIZE:
errors_total.labels(error_type="payload_too_large").inc()
raise HTTPException(status_code=413, detail="Request body too large")
# Verify signature
if not webhook_handler.verify_signature(body, x_hub_signature_256):
webhook_signature_validations_total.labels(result="invalid").inc()
errors_total.labels(error_type="signature_invalid").inc()
logger.warning("Invalid webhook signature")
raise HTTPException(status_code=401, detail="Invalid signature")
webhook_signature_validations_total.labels(result="valid").inc()
# Parse JSON payload
try:
payload = await request.json()
except Exception as e:
errors_total.labels(error_type="payload_invalid").inc()
logger.error("Failed to parse webhook payload: %s", e)
raise HTTPException(status_code=400, detail="Invalid JSON payload") from None
# Handle event with timing
try:
start_time = time.time()
result = await webhook_handler.handle_event(x_github_event, payload)
duration = time.time() - start_time
webhook_processing_duration_seconds.labels(event_type=x_github_event or "unknown").observe(
duration
)
return result
> except CosmicRayTestingException as e:
^^^^^^^^^^^^^^^^^^^^^^^^^
E NameError: name 'CosmicRayTestingException' is not defined
stampbot/main.py:323: NameError
=========================== short test summary info ============================
FAILED tests/test_main.py::test_webhook_handler_exception - NameError: name 'CosmicRayTestingException' is not defined
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 109 passed, 3 deselected in 1.26s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ExceptionReplacer, occurrence: 2
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -469,7 +469,7 @@
try:
credentials = await exchange_code_for_credentials(code)
- except Exception as e:
+ except CosmicRayTestingException as e:
logger.error("Failed to exchange code for credentials: %s", _sanitize_error(e))
raise HTTPException(status_code=500, detail="Failed to complete setup") from None
........................................................................ [ 34%]
....................................................................F
=================================== FAILURES ===================================
____________ TestSetupCallback.test_callback_handles_exchange_error ____________
request = <starlette.requests.Request object at 0x7fed61354ec0>
code = 'bad-code'
@app.get("/setup/callback")
async def setup_callback(request: Request, code: str) -> Response:
"""Handle callback from GitHub after app creation.
Args:
request: FastAPI request.
code: Temporary code from GitHub to exchange for credentials.
Returns:
HTML page with credentials and setup instructions.
Raises:
HTTPException: If setup disabled (403) or code exchange fails (500).
"""
if not settings.setup_enabled:
raise HTTPException(status_code=403, detail="Setup not allowed")
try:
> credentials = await exchange_code_for_credentials(code)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:471:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <AsyncMock name='exchange_code_for_credentials' id='140657448792624'>
args = ('bad-code',), kwargs = {}, _call = call('bad-code')
effect = Exception('API error')
async def _execute_mock_call(self, /, *args, **kwargs):
# This is nearly just like super(), except for special handling
# of coroutines
_call = _Call((args, kwargs), two=True)
self.await_count += 1
self.await_args = _call
self.await_args_list.append(_call)
effect = self.side_effect
if effect is not None:
if _is_exception(effect):
> raise effect
E Exception: API error
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:2326: Exception
The above exception was the direct cause of the following exception:
self = <tests.test_setup_endpoints.TestSetupCallback object at 0x7fed61b6f950>
test_client = <starlette.testclient.TestClient object at 0x7fed5d50bac0>
def test_callback_handles_exchange_error(self, test_client):
"""Test callback handles exchange error gracefully."""
with patch(
"stampbot.main.exchange_code_for_credentials",
new_callable=AsyncMock,
side_effect=Exception("API error"),
):
> response = test_client.get("/setup/callback?code=bad-code")
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_setup_endpoints.py:213:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:473: in get
return super().get(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1053: in get
return self.request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:445: in request
return super().request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:825: in request
return self.send(request, auth=auth, follow_redirects=follow_redirects)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:914: in send
response = self._send_handling_auth(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:942: in _send_handling_auth
response = self._send_handling_redirects(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:979: in _send_handling_redirects
response = self._send_single_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1014: in _send_single_request
response = transport.handle_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:348: in handle_request
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:345: in handle_request
portal.call(self.app, scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:334: in call
return cast(T_Retval, self.start_task_soon(func, *args).result())
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:443: in result
return self.__get_result()
^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:395: in __get_result
raise self._exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:259: in _call_func
retval = await retval_or_awaitable
^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1135: in __call__
await super().__call__(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:107: in __call__
await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:186: in __call__
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:164: in __call__
await self.app(scope, receive, _send)
stampbot/main.py:205: in __call__
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:191: in __call__
with recv_stream, send_stream, collapse_excgroups():
^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/contextlib.py:162: in __exit__
self.gen.throw(value)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_utils.py:85: in collapse_excgroups
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:193: in __call__
response = await self.dispatch_func(request, call_next)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:120: in metrics_middleware
response = await call_next(request)
^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:168: in call_next
raise app_exc from app_exc.__cause__ or app_exc.__context__
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:144: in coro
await self.app(scope, receive_or_disconnect, send_no_error)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/exceptions.py:63: in __call__
await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/middleware/asyncexitstack.py:18: in __call__
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:716: in __call__
await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:736: in app
await route.handle(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:290: in handle
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:115: in app
await wrap_app_handling_exceptions(app, request)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:101: in app
response = await f(request)
^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:355: in app
raw_response = await run_endpoint_function(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:243: in run_endpoint_function
return await dependant.call(**values)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
request = <starlette.requests.Request object at 0x7fed61354ec0>
code = 'bad-code'
@app.get("/setup/callback")
async def setup_callback(request: Request, code: str) -> Response:
"""Handle callback from GitHub after app creation.
Args:
request: FastAPI request.
code: Temporary code from GitHub to exchange for credentials.
Returns:
HTML page with credentials and setup instructions.
Raises:
HTTPException: If setup disabled (403) or code exchange fails (500).
"""
if not settings.setup_enabled:
raise HTTPException(status_code=403, detail="Setup not allowed")
try:
credentials = await exchange_code_for_credentials(code)
> except CosmicRayTestingException as e:
^^^^^^^^^^^^^^^^^^^^^^^^^
E NameError: name 'CosmicRayTestingException' is not defined
stampbot/main.py:472: NameError
=========================== short test summary info ============================
FAILED tests/test_setup_endpoints.py::TestSetupCallback::test_callback_handles_exchange_error - NameError: name 'CosmicRayTestingException' is not defined
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 140 passed, 3 deselected in 1.39s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/NumberReplacer, occurrence: 0
--- mutation diff --- --- astampbot/main.py +++ bstampbot/main.py @@ -47,7 +47,7 @@ APP_VERSION = "0.1.0" # Security limits -MAX_WEBHOOK_BODY_SIZE = 1024 * 1024 # 1MB - GitHub webhooks are typically much smaller +MAX_WEBHOOK_BODY_SIZE = 1025 * 1024 # 1MB - GitHub webhooks are typically much smaller @asynccontextmanager
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.60s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/NumberReplacer, occurrence: 1
--- mutation diff --- --- astampbot/main.py +++ bstampbot/main.py @@ -47,7 +47,7 @@ APP_VERSION = "0.1.0" # Security limits -MAX_WEBHOOK_BODY_SIZE = 1024 * 1024 # 1MB - GitHub webhooks are typically much smaller +MAX_WEBHOOK_BODY_SIZE = 1023 * 1024 # 1MB - GitHub webhooks are typically much smaller @asynccontextmanager
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.62s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/NumberReplacer, occurrence: 2
--- mutation diff --- --- astampbot/main.py +++ bstampbot/main.py @@ -47,7 +47,7 @@ APP_VERSION = "0.1.0" # Security limits -MAX_WEBHOOK_BODY_SIZE = 1024 * 1024 # 1MB - GitHub webhooks are typically much smaller +MAX_WEBHOOK_BODY_SIZE = 1024 * 1025 # 1MB - GitHub webhooks are typically much smaller @asynccontextmanager
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.60s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/NumberReplacer, occurrence: 3
--- mutation diff --- --- astampbot/main.py +++ bstampbot/main.py @@ -47,7 +47,7 @@ APP_VERSION = "0.1.0" # Security limits -MAX_WEBHOOK_BODY_SIZE = 1024 * 1024 # 1MB - GitHub webhooks are typically much smaller +MAX_WEBHOOK_BODY_SIZE = 1024 * 1023 # 1MB - GitHub webhooks are typically much smaller @asynccontextmanager
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.63s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/NumberReplacer, occurrence: 4
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -194,7 +194,7 @@
if raw:
# X-Forwarded-For may be a comma-separated list; the leftmost
# entry is the original client (proxies append to the right).
- client_ip = raw.split(",")[0].strip()
+ client_ip = raw.split(",")[ 1].strip()
if not client_ip and scope.get("client"):
client_ip = scope["client"][0]........................................................................ [ 34%]
...................................F
=================================== FAILURES ===================================
______________ test_logging_middleware_respects_configured_header ______________
def test_logging_middleware_respects_configured_header():
"""Test that logging_middleware uses the configured client_ip_header setting."""
from unittest.mock import patch
import structlog.contextvars
from stampbot.main import app
with patch("stampbot.main.settings") as mock_settings:
mock_settings.get = lambda key, default=None: (
"X-Real-IP" if key == "client_ip_header" else default
)
client = TestClient(app)
captured: dict[str, str | None] = {}
original_bind = structlog.contextvars.bind_contextvars
def capturing_bind(**kw: object) -> None:
captured.update({k: str(v) for k, v in kw.items()})
original_bind(**kw)
with patch("stampbot.main.structlog.contextvars.bind_contextvars", capturing_bind):
> client.get("/health", headers={"X-Real-IP": "198.51.100.7"})
tests/test_main.py:294:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:473: in get
return super().get(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1053: in get
return self.request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:445: in request
return super().request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:825: in request
return self.send(request, auth=auth, follow_redirects=follow_redirects)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:914: in send
response = self._send_handling_auth(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:942: in _send_handling_auth
response = self._send_handling_redirects(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:979: in _send_handling_redirects
response = self._send_single_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1014: in _send_single_request
response = transport.handle_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:348: in handle_request
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:345: in handle_request
portal.call(self.app, scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:334: in call
return cast(T_Retval, self.start_task_soon(func, *args).result())
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:443: in result
return self.__get_result()
^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:395: in __get_result
raise self._exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:259: in _call_func
retval = await retval_or_awaitable
^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1135: in __call__
await super().__call__(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:107: in __call__
await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:186: in __call__
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:164: in __call__
await self.app(scope, receive, _send)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.main.LoggingMiddleware object at 0x7fd57ca82660>
scope = {'app': <fastapi.applications.FastAPI object at 0x7fd57ccb30e0>, 'client': ('testclient', 50000), 'extensions': {'http...gzip, deflate'), (b'connection', b'keep-alive'), (b'user-agent', b'testclient'), (b'x-real-ip', b'198.51.100.7')], ...}
receive = <function _TestClientTransport.handle_request.<locals>.receive at 0x7fd57c91dd20>
send = <function ServerErrorMiddleware.__call__.<locals>._send at 0x7fd57c91e820>
async def __call__(self, scope: Any, receive: Any, send: Any) -> None:
"""Process an ASGI event, binding client IP to the structlog context.
For HTTP scopes the middleware clears any inherited structlog context,
extracts the real client IP from the configured forwarding header (or
the direct connection address as a fallback), and binds it so that
every log record emitted during the request includes ``client_ip``.
Non-HTTP scopes (lifespan, WebSocket) are passed through unchanged.
Args:
scope: ASGI connection scope.
receive: ASGI receive callable.
send: ASGI send callable.
"""
if scope["type"] == "http":
structlog.contextvars.clear_contextvars()
client_ip: str | None = None
client_ip_header: str = settings.get("client_ip_header", "X-Forwarded-For")
if client_ip_header:
raw = Request(scope).headers.get(client_ip_header)
if raw:
# X-Forwarded-For may be a comma-separated list; the leftmost
# entry is the original client (proxies append to the right).
> client_ip = raw.split(",")[ 1].strip()
^^^^^^^^^^^^^^^^^^
E IndexError: list index out of range
stampbot/main.py:197: IndexError
=========================== short test summary info ============================
FAILED tests/test_main.py::test_logging_middleware_respects_configured_header - IndexError: list index out of range
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 107 passed, 3 deselected in 1.14s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/NumberReplacer, occurrence: 5
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -194,7 +194,7 @@
if raw:
# X-Forwarded-For may be a comma-separated list; the leftmost
# entry is the original client (proxies append to the right).
- client_ip = raw.split(",")[0].strip()
+ client_ip = raw.split(",")[ -1].strip()
if not client_ip and scope.get("client"):
client_ip = scope["client"][0]........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.65s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/NumberReplacer, occurrence: 6
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -197,7 +197,7 @@
client_ip = raw.split(",")[0].strip()
if not client_ip and scope.get("client"):
- client_ip = scope["client"][0]
+ client_ip = scope["client"][ 1]
if client_ip:
structlog.contextvars.bind_contextvars(client_ip=client_ip)........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.62s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/NumberReplacer, occurrence: 7
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -197,7 +197,7 @@
client_ip = raw.split(",")[0].strip()
if not client_ip and scope.get("client"):
- client_ip = scope["client"][0]
+ client_ip = scope["client"][ -1]
if client_ip:
structlog.contextvars.bind_contextvars(client_ip=client_ip)........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/NumberReplacer, occurrence: 8
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -216,7 +216,7 @@
Redirect to /setup if unconfigured, otherwise JSON status response.
"""
if not is_configured() and settings.setup_enabled:
- return RedirectResponse(url="/setup", status_code=307)
+ return RedirectResponse(url="/setup", status_code= 308)
return Response(
content='{"app": "stampbot", "version": "' + APP_VERSION + '", "status": "running"}',........................................................................ [ 34%]
.............................................................F
=================================== FAILURES ===================================
_ TestSetupEndpointsUnconfigured.test_root_redirects_to_setup_when_unconfigured _
self = <tests.test_setup_endpoints.TestSetupEndpointsUnconfigured object at 0x7fdc2b757c50>
def test_root_redirects_to_setup_when_unconfigured(self):
"""Test root redirects to /setup when not configured."""
with (
patch("stampbot.main.is_configured", return_value=False),
patch("stampbot.main.settings") as mock_settings,
):
mock_settings.setup_enabled = True
mock_settings.app_name = "stampbot"
mock_settings.host = "0.0.0.0"
mock_settings.port = 8000
mock_settings.log_level = "INFO"
from fastapi.testclient import TestClient
from stampbot.main import app
client = TestClient(app, raise_server_exceptions=False)
response = client.get("/", follow_redirects=False)
> assert response.status_code == 307
E assert 308 == 307
E + where 308 = <Response [308 Permanent Redirect]>.status_code
tests/test_setup_endpoints.py:56: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T20:02:12.168567Z[0m [[32m[1minfo [0m] [1mHTTP Request: GET http://testserver/ "HTTP/1.1 308 Permanent Redirect"[0m
------------------------------ Captured log call -------------------------------
INFO httpx:_client.py:1025 HTTP Request: GET http://testserver/ "HTTP/1.1 308 Permanent Redirect"
=========================== short test summary info ============================
FAILED tests/test_setup_endpoints.py::TestSetupEndpointsUnconfigured::test_root_redirects_to_setup_when_unconfigured - assert 308 == 307
+ where 308 = <Response [308 Permanent Redirect]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 133 passed, 3 deselected in 0.97s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/NumberReplacer, occurrence: 9
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -216,7 +216,7 @@
Redirect to /setup if unconfigured, otherwise JSON status response.
"""
if not is_configured() and settings.setup_enabled:
- return RedirectResponse(url="/setup", status_code=307)
+ return RedirectResponse(url="/setup", status_code= 306)
return Response(
content='{"app": "stampbot", "version": "' + APP_VERSION + '", "status": "running"}',........................................................................ [ 34%]
.............................................................F
=================================== FAILURES ===================================
_ TestSetupEndpointsUnconfigured.test_root_redirects_to_setup_when_unconfigured _
self = <tests.test_setup_endpoints.TestSetupEndpointsUnconfigured object at 0x7fd029833c50>
def test_root_redirects_to_setup_when_unconfigured(self):
"""Test root redirects to /setup when not configured."""
with (
patch("stampbot.main.is_configured", return_value=False),
patch("stampbot.main.settings") as mock_settings,
):
mock_settings.setup_enabled = True
mock_settings.app_name = "stampbot"
mock_settings.host = "0.0.0.0"
mock_settings.port = 8000
mock_settings.log_level = "INFO"
from fastapi.testclient import TestClient
from stampbot.main import app
client = TestClient(app, raise_server_exceptions=False)
response = client.get("/", follow_redirects=False)
> assert response.status_code == 307
E assert 306 == 307
E + where 306 = <Response [306 ]>.status_code
tests/test_setup_endpoints.py:56: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:23:28.698166Z[0m [[32m[1minfo [0m] [1mHTTP Request: GET http://testserver/ "HTTP/1.1 306 "[0m
------------------------------ Captured log call -------------------------------
INFO httpx:_client.py:1025 HTTP Request: GET http://testserver/ "HTTP/1.1 306 "
=========================== short test summary info ============================
FAILED tests/test_setup_endpoints.py::TestSetupEndpointsUnconfigured::test_root_redirects_to_setup_when_unconfigured - assert 306 == 307
+ where 306 = <Response [306 ]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 133 passed, 3 deselected in 1.00s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/NumberReplacer, occurrence: 10
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -270,7 +270,7 @@
# Check if app is configured
if not is_configured():
raise HTTPException(
- status_code=503,
+ status_code= 504,
detail="Stampbot not configured. Visit /setup to complete setup.",
)
........................................................................ [ 34%]
.................................................................F
=================================== FAILURES ===================================
__ TestSetupEndpointsUnconfigured.test_webhook_returns_503_when_unconfigured ___
self = <tests.test_setup_endpoints.TestSetupEndpointsUnconfigured object at 0x7efc4c5da8d0>
def test_webhook_returns_503_when_unconfigured(self):
"""Test webhook returns 503 when not configured."""
with (
patch("stampbot.main.is_configured", return_value=False),
patch("stampbot.main.settings") as mock_settings,
):
mock_settings.setup_enabled = True
from fastapi.testclient import TestClient
from stampbot.main import app
client = TestClient(app, raise_server_exceptions=False)
response = client.post("/webhook", json={})
> assert response.status_code == 503
E assert 504 == 503
E + where 504 = <Response [504 Gateway Timeout]>.status_code
tests/test_setup_endpoints.py:155: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:40:31.939463Z[0m [[32m[1minfo [0m] [1mHTTP Request: POST http://testserver/webhook "HTTP/1.1 504 Gateway Timeout"[0m
------------------------------ Captured log call -------------------------------
INFO httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 504 Gateway Timeout"
=========================== short test summary info ============================
FAILED tests/test_setup_endpoints.py::TestSetupEndpointsUnconfigured::test_webhook_returns_503_when_unconfigured - assert 504 == 503
+ where 504 = <Response [504 Gateway Timeout]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 137 passed, 3 deselected in 1.01s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/NumberReplacer, occurrence: 11
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -270,7 +270,7 @@
# Check if app is configured
if not is_configured():
raise HTTPException(
- status_code=503,
+ status_code= 502,
detail="Stampbot not configured. Visit /setup to complete setup.",
)
........................................................................ [ 34%]
.................................................................F
=================================== FAILURES ===================================
__ TestSetupEndpointsUnconfigured.test_webhook_returns_503_when_unconfigured ___
self = <tests.test_setup_endpoints.TestSetupEndpointsUnconfigured object at 0x7fa1e4dc28d0>
def test_webhook_returns_503_when_unconfigured(self):
"""Test webhook returns 503 when not configured."""
with (
patch("stampbot.main.is_configured", return_value=False),
patch("stampbot.main.settings") as mock_settings,
):
mock_settings.setup_enabled = True
from fastapi.testclient import TestClient
from stampbot.main import app
client = TestClient(app, raise_server_exceptions=False)
response = client.post("/webhook", json={})
> assert response.status_code == 503
E assert 502 == 503
E + where 502 = <Response [502 Bad Gateway]>.status_code
tests/test_setup_endpoints.py:155: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T20:04:07.329701Z[0m [[32m[1minfo [0m] [1mHTTP Request: POST http://testserver/webhook "HTTP/1.1 502 Bad Gateway"[0m
------------------------------ Captured log call -------------------------------
INFO httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 502 Bad Gateway"
=========================== short test summary info ============================
FAILED tests/test_setup_endpoints.py::TestSetupEndpointsUnconfigured::test_webhook_returns_503_when_unconfigured - assert 502 == 503
+ where 502 = <Response [502 Bad Gateway]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 137 passed, 3 deselected in 1.02s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/NumberReplacer, occurrence: 12
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -276,7 +276,7 @@
if not x_github_event:
errors_total.labels(error_type="missing_event").inc()
- raise HTTPException(status_code=400, detail="Missing X-GitHub-Event header")
+ raise HTTPException(status_code= 401, detail="Missing X-GitHub-Event header")
# Check content length before reading body to prevent memory exhaustion
content_length = request.headers.get("content-length")........................................................................ [ 34%]
..........................F
=================================== FAILURES ===================================
______________________ test_webhook_missing_event_header _______________________
test_client = <starlette.testclient.TestClient object at 0x7f4d48eb1040>
def test_webhook_missing_event_header(test_client: TestClient):
"""Test webhook rejects requests without X-GitHub-Event header."""
response = test_client.post(
"/webhook",
json={"zen": "test"},
headers={"X-Hub-Signature-256": "sha256=invalid"},
)
> assert response.status_code == 400
E assert 401 == 400
E + where 401 = <Response [401 Unauthorized]>.status_code
tests/test_main.py:100: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:53:44.532613Z[0m [[32m[1minfo [0m] [1mHTTP Request: POST http://testserver/webhook "HTTP/1.1 401 Unauthorized"[0m
------------------------------ Captured log call -------------------------------
INFO httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 401 Unauthorized"
=========================== short test summary info ============================
FAILED tests/test_main.py::test_webhook_missing_event_header - assert 401 == 400
+ where 401 = <Response [401 Unauthorized]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 98 passed, 3 deselected in 0.87s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/NumberReplacer, occurrence: 13
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -276,7 +276,7 @@
if not x_github_event:
errors_total.labels(error_type="missing_event").inc()
- raise HTTPException(status_code=400, detail="Missing X-GitHub-Event header")
+ raise HTTPException(status_code= 399, detail="Missing X-GitHub-Event header")
# Check content length before reading body to prevent memory exhaustion
content_length = request.headers.get("content-length")........................................................................ [ 34%]
..........................F
=================================== FAILURES ===================================
______________________ test_webhook_missing_event_header _______________________
test_client = <starlette.testclient.TestClient object at 0x7fe1061a1040>
def test_webhook_missing_event_header(test_client: TestClient):
"""Test webhook rejects requests without X-GitHub-Event header."""
response = test_client.post(
"/webhook",
json={"zen": "test"},
headers={"X-Hub-Signature-256": "sha256=invalid"},
)
> assert response.status_code == 400
E assert 399 == 400
E + where 399 = <Response [399 ]>.status_code
tests/test_main.py:100: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:24:32.549975Z[0m [[32m[1minfo [0m] [1mHTTP Request: POST http://testserver/webhook "HTTP/1.1 399 "[0m
------------------------------ Captured log call -------------------------------
INFO httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 399 "
=========================== short test summary info ============================
FAILED tests/test_main.py::test_webhook_missing_event_header - assert 399 == 400
+ where 399 = <Response [399 ]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 98 passed, 3 deselected in 0.83s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/NumberReplacer, occurrence: 14
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -282,7 +282,7 @@
content_length = request.headers.get("content-length")
if content_length and int(content_length) > MAX_WEBHOOK_BODY_SIZE:
errors_total.labels(error_type="payload_too_large").inc()
- raise HTTPException(status_code=413, detail="Request body too large")
+ raise HTTPException(status_code= 414, detail="Request body too large")
# Get raw body for signature verification
body = await request.body()........................................................................ [ 34%]
..............................F
=================================== FAILURES ===================================
____________________ test_webhook_content_length_too_large _____________________
test_client = <starlette.testclient.TestClient object at 0x7fb6d71bb020>
def test_webhook_content_length_too_large(test_client: TestClient):
"""Test webhook rejects requests with Content-Length exceeding limit."""
response = test_client.post(
"/webhook",
content=b"x", # Small actual body
headers={
"Content-Type": "application/json",
"Content-Length": "20000000", # 20MB - exceeds 10MB limit
"X-GitHub-Event": "ping",
"X-Hub-Signature-256": "sha256=fake",
},
)
> assert response.status_code == 413
E assert 414 == 413
E + where 414 = <Response [414 Request-URI Too Long]>.status_code
tests/test_main.py:193: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:46:19.720278Z[0m [[32m[1minfo [0m] [1mHTTP Request: POST http://testserver/webhook "HTTP/1.1 414 Request-URI Too Long"[0m
------------------------------ Captured log call -------------------------------
INFO httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 414 Request-URI Too Long"
=========================== short test summary info ============================
FAILED tests/test_main.py::test_webhook_content_length_too_large - assert 414 == 413
+ where 414 = <Response [414 Request-URI Too Long]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 102 passed, 3 deselected in 0.86s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/NumberReplacer, occurrence: 15
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -282,7 +282,7 @@
content_length = request.headers.get("content-length")
if content_length and int(content_length) > MAX_WEBHOOK_BODY_SIZE:
errors_total.labels(error_type="payload_too_large").inc()
- raise HTTPException(status_code=413, detail="Request body too large")
+ raise HTTPException(status_code= 412, detail="Request body too large")
# Get raw body for signature verification
body = await request.body()........................................................................ [ 34%]
..............................F
=================================== FAILURES ===================================
____________________ test_webhook_content_length_too_large _____________________
test_client = <starlette.testclient.TestClient object at 0x7fb2852c3020>
def test_webhook_content_length_too_large(test_client: TestClient):
"""Test webhook rejects requests with Content-Length exceeding limit."""
response = test_client.post(
"/webhook",
content=b"x", # Small actual body
headers={
"Content-Type": "application/json",
"Content-Length": "20000000", # 20MB - exceeds 10MB limit
"X-GitHub-Event": "ping",
"X-Hub-Signature-256": "sha256=fake",
},
)
> assert response.status_code == 413
E assert 412 == 413
E + where 412 = <Response [412 Precondition Failed]>.status_code
tests/test_main.py:193: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:24:10.796605Z[0m [[32m[1minfo [0m] [1mHTTP Request: POST http://testserver/webhook "HTTP/1.1 412 Precondition Failed"[0m
------------------------------ Captured log call -------------------------------
INFO httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 412 Precondition Failed"
=========================== short test summary info ============================
FAILED tests/test_main.py::test_webhook_content_length_too_large - assert 412 == 413
+ where 412 = <Response [412 Precondition Failed]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 102 passed, 3 deselected in 0.85s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/NumberReplacer, occurrence: 16
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -290,7 +290,7 @@
# Double-check actual body size (in case content-length was missing/incorrect)
if len(body) > MAX_WEBHOOK_BODY_SIZE:
errors_total.labels(error_type="payload_too_large").inc()
- raise HTTPException(status_code=413, detail="Request body too large")
+ raise HTTPException(status_code= 414, detail="Request body too large")
# Verify signature
if not webhook_handler.verify_signature(body, x_hub_signature_256):........................................................................ [ 34%]
...............................F
=================================== FAILURES ===================================
_________________________ test_webhook_body_too_large __________________________
test_client = <starlette.testclient.TestClient object at 0x7f5e211bb8a0>
def test_webhook_body_too_large(test_client: TestClient):
"""Test webhook rejects requests where actual body exceeds limit.
This tests the secondary body size check (lines 231-232) which catches
cases where Content-Length header is missing or incorrect.
"""
# Create a body larger than MAX_WEBHOOK_BODY_SIZE (10MB)
large_body = b"x" * (10 * 1024 * 1024 + 1) # 10MB + 1 byte
# Set Content-Length to a small value to bypass the header check (line 222)
# but the actual body size check (line 230) should still catch it
response = test_client.post(
"/webhook",
content=large_body,
headers={
"Content-Type": "application/json",
"Content-Length": "100", # Lie about size to bypass first check
"X-GitHub-Event": "ping",
"X-Hub-Signature-256": "sha256=fake",
},
)
> assert response.status_code == 413
E assert 414 == 413
E + where 414 = <Response [414 Request-URI Too Long]>.status_code
tests/test_main.py:218: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:34:30.837989Z[0m [[32m[1minfo [0m] [1mHTTP Request: POST http://testserver/webhook "HTTP/1.1 414 Request-URI Too Long"[0m
------------------------------ Captured log call -------------------------------
INFO httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 414 Request-URI Too Long"
=========================== short test summary info ============================
FAILED tests/test_main.py::test_webhook_body_too_large - assert 414 == 413
+ where 414 = <Response [414 Request-URI Too Long]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 103 passed, 3 deselected in 0.87s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/NumberReplacer, occurrence: 17
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -290,7 +290,7 @@
# Double-check actual body size (in case content-length was missing/incorrect)
if len(body) > MAX_WEBHOOK_BODY_SIZE:
errors_total.labels(error_type="payload_too_large").inc()
- raise HTTPException(status_code=413, detail="Request body too large")
+ raise HTTPException(status_code= 412, detail="Request body too large")
# Verify signature
if not webhook_handler.verify_signature(body, x_hub_signature_256):........................................................................ [ 34%]
...............................F
=================================== FAILURES ===================================
_________________________ test_webhook_body_too_large __________________________
test_client = <starlette.testclient.TestClient object at 0x7f259fc9b8a0>
def test_webhook_body_too_large(test_client: TestClient):
"""Test webhook rejects requests where actual body exceeds limit.
This tests the secondary body size check (lines 231-232) which catches
cases where Content-Length header is missing or incorrect.
"""
# Create a body larger than MAX_WEBHOOK_BODY_SIZE (10MB)
large_body = b"x" * (10 * 1024 * 1024 + 1) # 10MB + 1 byte
# Set Content-Length to a small value to bypass the header check (line 222)
# but the actual body size check (line 230) should still catch it
response = test_client.post(
"/webhook",
content=large_body,
headers={
"Content-Type": "application/json",
"Content-Length": "100", # Lie about size to bypass first check
"X-GitHub-Event": "ping",
"X-Hub-Signature-256": "sha256=fake",
},
)
> assert response.status_code == 413
E assert 412 == 413
E + where 412 = <Response [412 Precondition Failed]>.status_code
tests/test_main.py:218: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:25:52.743314Z[0m [[32m[1minfo [0m] [1mHTTP Request: POST http://testserver/webhook "HTTP/1.1 412 Precondition Failed"[0m
------------------------------ Captured log call -------------------------------
INFO httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 412 Precondition Failed"
=========================== short test summary info ============================
FAILED tests/test_main.py::test_webhook_body_too_large - assert 412 == 413
+ where 412 = <Response [412 Precondition Failed]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 103 passed, 3 deselected in 0.87s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/NumberReplacer, occurrence: 18
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -297,7 +297,7 @@
webhook_signature_validations_total.labels(result="invalid").inc()
errors_total.labels(error_type="signature_invalid").inc()
logger.warning("Invalid webhook signature")
- raise HTTPException(status_code=401, detail="Invalid signature")
+ raise HTTPException(status_code= 402, detail="Invalid signature")
webhook_signature_validations_total.labels(result="valid").inc()
........................................................................ [ 34%]
........................F
=================================== FAILURES ===================================
_______________________ test_request_with_content_length _______________________
test_client = <starlette.testclient.TestClient object at 0x7f0dd54b7770>
def test_request_with_content_length(test_client: TestClient):
"""Test that requests with Content-Length header are tracked."""
# POST with a body will have Content-Length set automatically
response = test_client.post(
"/webhook",
content=b'{"test": "data"}',
headers={
"Content-Type": "application/json",
"X-GitHub-Event": "ping",
"X-Hub-Signature-256": "sha256=invalid",
},
)
# Will fail signature validation, but Content-Length tracking happens before that
> assert response.status_code == 401
E assert 402 == 401
E + where 402 = <Response [402 Payment Required]>.status_code
tests/test_main.py:79: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T20:07:27.884716Z[0m [[33m[1mwarning [0m] [1mInvalid webhook signature [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.main (INFO)>[0m [36m_name[0m=[35mwarning[0m [36mclient_ip[0m=[35mtestclient[0m
[2m2026-05-16T20:07:27.885695Z[0m [[32m[1minfo [0m] [1mHTTP Request: POST http://testserver/webhook "HTTP/1.1 402 Payment Required"[0m
------------------------------ Captured log call -------------------------------
WARNING stampbot.main:main.py:299 {'event': 'Invalid webhook signature', 'client_ip': 'testclient', 'level': 'warning', 'timestamp': '2026-05-16T20:07:27.884716Z'}
INFO httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 402 Payment Required"
=========================== short test summary info ============================
FAILED tests/test_main.py::test_request_with_content_length - assert 402 == 401
+ where 402 = <Response [402 Payment Required]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 96 passed, 3 deselected in 0.87s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/NumberReplacer, occurrence: 19
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -297,7 +297,7 @@
webhook_signature_validations_total.labels(result="invalid").inc()
errors_total.labels(error_type="signature_invalid").inc()
logger.warning("Invalid webhook signature")
- raise HTTPException(status_code=401, detail="Invalid signature")
+ raise HTTPException(status_code= 400, detail="Invalid signature")
webhook_signature_validations_total.labels(result="valid").inc()
........................................................................ [ 34%]
........................F
=================================== FAILURES ===================================
_______________________ test_request_with_content_length _______________________
test_client = <starlette.testclient.TestClient object at 0x7fc16d9a3770>
def test_request_with_content_length(test_client: TestClient):
"""Test that requests with Content-Length header are tracked."""
# POST with a body will have Content-Length set automatically
response = test_client.post(
"/webhook",
content=b'{"test": "data"}',
headers={
"Content-Type": "application/json",
"X-GitHub-Event": "ping",
"X-Hub-Signature-256": "sha256=invalid",
},
)
# Will fail signature validation, but Content-Length tracking happens before that
> assert response.status_code == 401
E assert 400 == 401
E + where 400 = <Response [400 Bad Request]>.status_code
tests/test_main.py:79: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:26:11.413999Z[0m [[33m[1mwarning [0m] [1mInvalid webhook signature [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.main (INFO)>[0m [36m_name[0m=[35mwarning[0m [36mclient_ip[0m=[35mtestclient[0m
[2m2026-05-16T19:26:11.414913Z[0m [[32m[1minfo [0m] [1mHTTP Request: POST http://testserver/webhook "HTTP/1.1 400 Bad Request"[0m
------------------------------ Captured log call -------------------------------
WARNING stampbot.main:main.py:299 {'event': 'Invalid webhook signature', 'client_ip': 'testclient', 'level': 'warning', 'timestamp': '2026-05-16T19:26:11.413999Z'}
INFO httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 400 Bad Request"
=========================== short test summary info ============================
FAILED tests/test_main.py::test_request_with_content_length - assert 400 == 401
+ where 400 = <Response [400 Bad Request]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 96 passed, 3 deselected in 0.84s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/NumberReplacer, occurrence: 20
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -307,7 +307,7 @@
except Exception as e:
errors_total.labels(error_type="payload_invalid").inc()
logger.error("Failed to parse webhook payload: %s", e)
- raise HTTPException(status_code=400, detail="Invalid JSON payload") from None
+ raise HTTPException(status_code= 401, detail="Invalid JSON payload") from None
# Handle event with timing
try:........................................................................ [ 34%]
............................F
=================================== FAILURES ===================================
__________________________ test_webhook_invalid_json ___________________________
test_client = <starlette.testclient.TestClient object at 0x7f1fedd92690>
def test_webhook_invalid_json(test_client: TestClient):
"""Test webhook rejects invalid JSON payload."""
import hashlib
import hmac
from stampbot.webhook_handler import webhook_handler
# Create a valid signature for invalid JSON
body = b"not valid json"
signature = (
"sha256="
+ hmac.new(
webhook_handler.webhook_secret,
body,
hashlib.sha256,
).hexdigest()
)
response = test_client.post(
"/webhook",
content=body,
headers={
"Content-Type": "application/json",
"X-GitHub-Event": "ping",
"X-Hub-Signature-256": signature,
},
)
> assert response.status_code == 400
E assert 401 == 400
E + where 401 = <Response [401 Unauthorized]>.status_code
tests/test_main.py:145: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:49:22.046764Z[0m [[31m[1merror [0m] [1mFailed to parse webhook payload: Expecting value: line 1 column 1 (char 0)[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.main (INFO)>[0m [36m_name[0m=[35merror[0m [36mclient_ip[0m=[35mtestclient[0m
[2m2026-05-16T19:49:22.047585Z[0m [[32m[1minfo [0m] [1mHTTP Request: POST http://testserver/webhook "HTTP/1.1 401 Unauthorized"[0m
------------------------------ Captured log call -------------------------------
ERROR stampbot.main:main.py:309 {'event': 'Failed to parse webhook payload: Expecting value: line 1 column 1 (char 0)', 'client_ip': 'testclient', 'level': 'error', 'timestamp': '2026-05-16T19:49:22.046764Z'}
INFO httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 401 Unauthorized"
=========================== short test summary info ============================
FAILED tests/test_main.py::test_webhook_invalid_json - assert 401 == 400
+ where 401 = <Response [401 Unauthorized]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 100 passed, 3 deselected in 0.85s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/NumberReplacer, occurrence: 21
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -307,7 +307,7 @@
except Exception as e:
errors_total.labels(error_type="payload_invalid").inc()
logger.error("Failed to parse webhook payload: %s", e)
- raise HTTPException(status_code=400, detail="Invalid JSON payload") from None
+ raise HTTPException(status_code= 399, detail="Invalid JSON payload") from None
# Handle event with timing
try:........................................................................ [ 34%]
............................F
=================================== FAILURES ===================================
__________________________ test_webhook_invalid_json ___________________________
test_client = <starlette.testclient.TestClient object at 0x7f92fb0d6690>
def test_webhook_invalid_json(test_client: TestClient):
"""Test webhook rejects invalid JSON payload."""
import hashlib
import hmac
from stampbot.webhook_handler import webhook_handler
# Create a valid signature for invalid JSON
body = b"not valid json"
signature = (
"sha256="
+ hmac.new(
webhook_handler.webhook_secret,
body,
hashlib.sha256,
).hexdigest()
)
response = test_client.post(
"/webhook",
content=body,
headers={
"Content-Type": "application/json",
"X-GitHub-Event": "ping",
"X-Hub-Signature-256": signature,
},
)
> assert response.status_code == 400
E assert 399 == 400
E + where 399 = <Response [399 ]>.status_code
tests/test_main.py:145: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:23:50.996323Z[0m [[31m[1merror [0m] [1mFailed to parse webhook payload: Expecting value: line 1 column 1 (char 0)[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.main (INFO)>[0m [36m_name[0m=[35merror[0m [36mclient_ip[0m=[35mtestclient[0m
[2m2026-05-16T19:23:50.997146Z[0m [[32m[1minfo [0m] [1mHTTP Request: POST http://testserver/webhook "HTTP/1.1 399 "[0m
------------------------------ Captured log call -------------------------------
ERROR stampbot.main:main.py:309 {'event': 'Failed to parse webhook payload: Expecting value: line 1 column 1 (char 0)', 'client_ip': 'testclient', 'level': 'error', 'timestamp': '2026-05-16T19:23:50.996323Z'}
INFO httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 399 "
=========================== short test summary info ============================
FAILED tests/test_main.py::test_webhook_invalid_json - assert 399 == 400
+ where 399 = <Response [399 ]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 100 passed, 3 deselected in 0.88s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/NumberReplacer, occurrence: 22
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -324,7 +324,7 @@
errors_total.labels(error_type="webhook_handler_error").inc()
sanitized = _sanitize_error(e)
logger.error("Error handling webhook event: %s", sanitized, extra={"error": sanitized})
- raise HTTPException(status_code=500, detail="Internal server error") from None
+ raise HTTPException(status_code= 501, detail="Internal server error") from None
# =============================================================================........................................................................ [ 34%]
.....................................F
=================================== FAILURES ===================================
________________________ test_webhook_handler_exception ________________________
test_client = <starlette.testclient.TestClient object at 0x7fa6741bbdf0>
def test_webhook_handler_exception(test_client: TestClient):
"""Test webhook returns 500 when handler raises exception."""
from stampbot.webhook_handler import webhook_handler
body = json.dumps({"zen": "test"}).encode()
signature = (
"sha256="
+ hmac.new(
webhook_handler.webhook_secret,
body,
hashlib.sha256,
).hexdigest()
)
with patch("stampbot.main.webhook_handler.handle_event") as mock_handle:
mock_handle.side_effect = Exception("Unexpected error")
response = test_client.post(
"/webhook",
content=body,
headers={
"Content-Type": "application/json",
"X-GitHub-Event": "ping",
"X-Hub-Signature-256": signature,
},
)
> assert response.status_code == 500
E assert 501 == 500
E + where 501 = <Response [501 Not Implemented]>.status_code
tests/test_main.py:360: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:30:13.121116Z[0m [[31m[1merror [0m] [1mError handling webhook event: Unexpected error[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.main (INFO)>[0m [36m_name[0m=[35merror[0m [36mclient_ip[0m=[35mtestclient[0m [36mextra[0m=[35m{'error': 'Unexpected error'}[0m
[2m2026-05-16T19:30:13.121978Z[0m [[32m[1minfo [0m] [1mHTTP Request: POST http://testserver/webhook "HTTP/1.1 501 Not Implemented"[0m
------------------------------ Captured log call -------------------------------
ERROR stampbot.main:main.py:326 {'extra': {'error': 'Unexpected error'}, 'event': 'Error handling webhook event: Unexpected error', 'client_ip': 'testclient', 'level': 'error', 'timestamp': '2026-05-16T19:30:13.121116Z'}
INFO httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 501 Not Implemented"
=========================== short test summary info ============================
FAILED tests/test_main.py::test_webhook_handler_exception - assert 501 == 500
+ where 501 = <Response [501 Not Implemented]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 109 passed, 3 deselected in 0.87s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/NumberReplacer, occurrence: 23
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -324,7 +324,7 @@
errors_total.labels(error_type="webhook_handler_error").inc()
sanitized = _sanitize_error(e)
logger.error("Error handling webhook event: %s", sanitized, extra={"error": sanitized})
- raise HTTPException(status_code=500, detail="Internal server error") from None
+ raise HTTPException(status_code= 499, detail="Internal server error") from None
# =============================================================================........................................................................ [ 34%]
.....................................F
=================================== FAILURES ===================================
________________________ test_webhook_handler_exception ________________________
test_client = <starlette.testclient.TestClient object at 0x7f5feaf93df0>
def test_webhook_handler_exception(test_client: TestClient):
"""Test webhook returns 500 when handler raises exception."""
from stampbot.webhook_handler import webhook_handler
body = json.dumps({"zen": "test"}).encode()
signature = (
"sha256="
+ hmac.new(
webhook_handler.webhook_secret,
body,
hashlib.sha256,
).hexdigest()
)
with patch("stampbot.main.webhook_handler.handle_event") as mock_handle:
mock_handle.side_effect = Exception("Unexpected error")
response = test_client.post(
"/webhook",
content=body,
headers={
"Content-Type": "application/json",
"X-GitHub-Event": "ping",
"X-Hub-Signature-256": signature,
},
)
> assert response.status_code == 500
E assert 499 == 500
E + where 499 = <Response [499 ]>.status_code
tests/test_main.py:360: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:37:23.613348Z[0m [[31m[1merror [0m] [1mError handling webhook event: Unexpected error[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.main (INFO)>[0m [36m_name[0m=[35merror[0m [36mclient_ip[0m=[35mtestclient[0m [36mextra[0m=[35m{'error': 'Unexpected error'}[0m
[2m2026-05-16T19:37:23.614144Z[0m [[32m[1minfo [0m] [1mHTTP Request: POST http://testserver/webhook "HTTP/1.1 499 "[0m
------------------------------ Captured log call -------------------------------
ERROR stampbot.main:main.py:326 {'extra': {'error': 'Unexpected error'}, 'event': 'Error handling webhook event: Unexpected error', 'client_ip': 'testclient', 'level': 'error', 'timestamp': '2026-05-16T19:37:23.613348Z'}
INFO httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 499 "
=========================== short test summary info ============================
FAILED tests/test_main.py::test_webhook_handler_exception - assert 499 == 500
+ where 499 = <Response [499 ]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 109 passed, 3 deselected in 0.89s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/NumberReplacer, occurrence: 24
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -346,7 +346,7 @@
HTTPException: If setup is disabled (403).
"""
if not settings.setup_enabled:
- raise HTTPException(status_code=403, detail="Setup not allowed in this environment")
+ raise HTTPException(status_code= 404, detail="Setup not allowed in this environment")
if is_configured():
return HTMLResponse(........................................................................ [ 34%]
.....................................................................F
=================================== FAILURES ===================================
____________ TestSetupDisabled.test_setup_returns_403_when_disabled ____________
self = <tests.test_setup_endpoints.TestSetupDisabled object at 0x7f8931ff4190>
def test_setup_returns_403_when_disabled(self):
"""Test /setup returns 403 when setup is disabled."""
with (
patch("stampbot.main.is_configured", return_value=False),
patch("stampbot.main.settings") as mock_settings,
):
mock_settings.setup_enabled = False
from fastapi.testclient import TestClient
from stampbot.main import app
client = TestClient(app, raise_server_exceptions=False)
response = client.get("/setup")
> assert response.status_code == 403
E assert 404 == 403
E + where 404 = <Response [404 Not Found]>.status_code
tests/test_setup_endpoints.py:236: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:38:43.033843Z[0m [[32m[1minfo [0m] [1mHTTP Request: GET http://testserver/setup "HTTP/1.1 404 Not Found"[0m
------------------------------ Captured log call -------------------------------
INFO httpx:_client.py:1025 HTTP Request: GET http://testserver/setup "HTTP/1.1 404 Not Found"
=========================== short test summary info ============================
FAILED tests/test_setup_endpoints.py::TestSetupDisabled::test_setup_returns_403_when_disabled - assert 404 == 403
+ where 404 = <Response [404 Not Found]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 141 passed, 3 deselected in 1.06s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/NumberReplacer, occurrence: 25
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -346,7 +346,7 @@
HTTPException: If setup is disabled (403).
"""
if not settings.setup_enabled:
- raise HTTPException(status_code=403, detail="Setup not allowed in this environment")
+ raise HTTPException(status_code= 402, detail="Setup not allowed in this environment")
if is_configured():
return HTMLResponse(........................................................................ [ 34%]
.....................................................................F
=================================== FAILURES ===================================
____________ TestSetupDisabled.test_setup_returns_403_when_disabled ____________
self = <tests.test_setup_endpoints.TestSetupDisabled object at 0x7f63ed7f4190>
def test_setup_returns_403_when_disabled(self):
"""Test /setup returns 403 when setup is disabled."""
with (
patch("stampbot.main.is_configured", return_value=False),
patch("stampbot.main.settings") as mock_settings,
):
mock_settings.setup_enabled = False
from fastapi.testclient import TestClient
from stampbot.main import app
client = TestClient(app, raise_server_exceptions=False)
response = client.get("/setup")
> assert response.status_code == 403
E assert 402 == 403
E + where 402 = <Response [402 Payment Required]>.status_code
tests/test_setup_endpoints.py:236: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:56:46.025946Z[0m [[32m[1minfo [0m] [1mHTTP Request: GET http://testserver/setup "HTTP/1.1 402 Payment Required"[0m
------------------------------ Captured log call -------------------------------
INFO httpx:_client.py:1025 HTTP Request: GET http://testserver/setup "HTTP/1.1 402 Payment Required"
=========================== short test summary info ============================
FAILED tests/test_setup_endpoints.py::TestSetupDisabled::test_setup_returns_403_when_disabled - assert 402 == 403
+ where 402 = <Response [402 Payment Required]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 141 passed, 3 deselected in 1.03s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/NumberReplacer, occurrence: 26
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -371,7 +371,7 @@
</body>
</html>
""",
- status_code=200,
+ status_code= 201,
)
# Determine base URL with priority:........................................................................ [ 34%]
...........................................................F
=================================== FAILURES ===================================
_______ TestSetupEndpointsConfigured.test_setup_shows_already_configured _______
self = <tests.test_setup_endpoints.TestSetupEndpointsConfigured object at 0x7f2159bc3b10>
test_client = <starlette.testclient.TestClient object at 0x7f2154902be0>
def test_setup_shows_already_configured(self, test_client):
"""Test /setup shows already configured message."""
response = test_client.get("/setup")
> assert response.status_code == 200
E assert 201 == 200
E + where 201 = <Response [201 Created]>.status_code
tests/test_setup_endpoints.py:22: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T20:02:03.526306Z[0m [[32m[1minfo [0m] [1mHTTP Request: GET http://testserver/setup "HTTP/1.1 201 Created"[0m
------------------------------ Captured log call -------------------------------
INFO httpx:_client.py:1025 HTTP Request: GET http://testserver/setup "HTTP/1.1 201 Created"
=========================== short test summary info ============================
FAILED tests/test_setup_endpoints.py::TestSetupEndpointsConfigured::test_setup_shows_already_configured - assert 201 == 200
+ where 201 = <Response [201 Created]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 131 passed, 3 deselected in 0.98s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/NumberReplacer, occurrence: 27
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -371,7 +371,7 @@
</body>
</html>
""",
- status_code=200,
+ status_code= 199,
)
# Determine base URL with priority:........................................................................ [ 34%]
...........................................................F
=================================== FAILURES ===================================
_______ TestSetupEndpointsConfigured.test_setup_shows_already_configured _______
self = <tests.test_setup_endpoints.TestSetupEndpointsConfigured object at 0x7fbed25bbb10>
test_client = <starlette.testclient.TestClient object at 0x7fbecd316be0>
def test_setup_shows_already_configured(self, test_client):
"""Test /setup shows already configured message."""
response = test_client.get("/setup")
> assert response.status_code == 200
E assert 199 == 200
E + where 199 = <Response [199 ]>.status_code
tests/test_setup_endpoints.py:22: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:33:18.920004Z[0m [[32m[1minfo [0m] [1mHTTP Request: GET http://testserver/setup "HTTP/1.1 199 "[0m
------------------------------ Captured log call -------------------------------
INFO httpx:_client.py:1025 HTTP Request: GET http://testserver/setup "HTTP/1.1 199 "
=========================== short test summary info ============================
FAILED tests/test_setup_endpoints.py::TestSetupEndpointsConfigured::test_setup_shows_already_configured - assert 199 == 200
+ where 199 = <Response [199 ]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 131 passed, 3 deselected in 0.98s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/NumberReplacer, occurrence: 28
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -465,7 +465,7 @@
HTTPException: If setup disabled (403) or code exchange fails (500).
"""
if not settings.setup_enabled:
- raise HTTPException(status_code=403, detail="Setup not allowed")
+ raise HTTPException(status_code= 404, detail="Setup not allowed")
try:
credentials = await exchange_code_for_credentials(code)........................................................................ [ 34%]
......................................................................F
=================================== FAILURES ===================================
_______ TestSetupDisabled.test_setup_callback_returns_403_when_disabled ________
self = <tests.test_setup_endpoints.TestSetupDisabled object at 0x7ff923bf42d0>
def test_setup_callback_returns_403_when_disabled(self):
"""Test /setup/callback returns 403 when setup is disabled."""
with patch("stampbot.main.settings") as mock_settings:
mock_settings.setup_enabled = False
from fastapi.testclient import TestClient
from stampbot.main import app
client = TestClient(app, raise_server_exceptions=False)
response = client.get("/setup/callback?code=test")
> assert response.status_code == 403
E assert 404 == 403
E + where 404 = <Response [404 Not Found]>.status_code
tests/test_setup_endpoints.py:250: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:48:57.045628Z[0m [[32m[1minfo [0m] [1mHTTP Request: GET http://testserver/setup/callback?code=test "HTTP/1.1 404 Not Found"[0m
------------------------------ Captured log call -------------------------------
INFO httpx:_client.py:1025 HTTP Request: GET http://testserver/setup/callback?code=test "HTTP/1.1 404 Not Found"
=========================== short test summary info ============================
FAILED tests/test_setup_endpoints.py::TestSetupDisabled::test_setup_callback_returns_403_when_disabled - assert 404 == 403
+ where 404 = <Response [404 Not Found]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 142 passed, 3 deselected in 1.05s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/NumberReplacer, occurrence: 29
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -465,7 +465,7 @@
HTTPException: If setup disabled (403) or code exchange fails (500).
"""
if not settings.setup_enabled:
- raise HTTPException(status_code=403, detail="Setup not allowed")
+ raise HTTPException(status_code= 402, detail="Setup not allowed")
try:
credentials = await exchange_code_for_credentials(code)........................................................................ [ 34%]
......................................................................F
=================================== FAILURES ===================================
_______ TestSetupDisabled.test_setup_callback_returns_403_when_disabled ________
self = <tests.test_setup_endpoints.TestSetupDisabled object at 0x7f4bcb6202d0>
def test_setup_callback_returns_403_when_disabled(self):
"""Test /setup/callback returns 403 when setup is disabled."""
with patch("stampbot.main.settings") as mock_settings:
mock_settings.setup_enabled = False
from fastapi.testclient import TestClient
from stampbot.main import app
client = TestClient(app, raise_server_exceptions=False)
response = client.get("/setup/callback?code=test")
> assert response.status_code == 403
E assert 402 == 403
E + where 402 = <Response [402 Payment Required]>.status_code
tests/test_setup_endpoints.py:250: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:36:48.265093Z[0m [[32m[1minfo [0m] [1mHTTP Request: GET http://testserver/setup/callback?code=test "HTTP/1.1 402 Payment Required"[0m
------------------------------ Captured log call -------------------------------
INFO httpx:_client.py:1025 HTTP Request: GET http://testserver/setup/callback?code=test "HTTP/1.1 402 Payment Required"
=========================== short test summary info ============================
FAILED tests/test_setup_endpoints.py::TestSetupDisabled::test_setup_callback_returns_403_when_disabled - assert 402 == 403
+ where 402 = <Response [402 Payment Required]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 142 passed, 3 deselected in 1.03s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/NumberReplacer, occurrence: 30
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -471,7 +471,7 @@
credentials = await exchange_code_for_credentials(code)
except Exception as e:
logger.error("Failed to exchange code for credentials: %s", _sanitize_error(e))
- raise HTTPException(status_code=500, detail="Failed to complete setup") from None
+ raise HTTPException(status_code= 501, detail="Failed to complete setup") from None
# Security note: Credentials are displayed in the HTML response for user convenience.
# This mirrors GitHub's own manifest flow behavior. The tradeoffs are acceptable because:........................................................................ [ 34%]
....................................................................F
=================================== FAILURES ===================================
____________ TestSetupCallback.test_callback_handles_exchange_error ____________
self = <tests.test_setup_endpoints.TestSetupCallback object at 0x7feaa28c7950>
test_client = <starlette.testclient.TestClient object at 0x7fea9e227ac0>
def test_callback_handles_exchange_error(self, test_client):
"""Test callback handles exchange error gracefully."""
with patch(
"stampbot.main.exchange_code_for_credentials",
new_callable=AsyncMock,
side_effect=Exception("API error"),
):
response = test_client.get("/setup/callback?code=bad-code")
> assert response.status_code == 500
E assert 501 == 500
E + where 501 = <Response [501 Not Implemented]>.status_code
tests/test_setup_endpoints.py:215: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:47:30.143557Z[0m [[31m[1merror [0m] [1mFailed to exchange code for credentials: API error[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.main (INFO)>[0m [36m_name[0m=[35merror[0m [36mclient_ip[0m=[35mtestclient[0m
[2m2026-05-16T19:47:30.144408Z[0m [[32m[1minfo [0m] [1mHTTP Request: GET http://testserver/setup/callback?code=bad-code "HTTP/1.1 501 Not Implemented"[0m
------------------------------ Captured log call -------------------------------
ERROR stampbot.main:main.py:473 {'event': 'Failed to exchange code for credentials: API error', 'client_ip': 'testclient', 'level': 'error', 'timestamp': '2026-05-16T19:47:30.143557Z'}
INFO httpx:_client.py:1025 HTTP Request: GET http://testserver/setup/callback?code=bad-code "HTTP/1.1 501 Not Implemented"
=========================== short test summary info ============================
FAILED tests/test_setup_endpoints.py::TestSetupCallback::test_callback_handles_exchange_error - assert 501 == 500
+ where 501 = <Response [501 Not Implemented]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 140 passed, 3 deselected in 1.07s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/NumberReplacer, occurrence: 31
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -471,7 +471,7 @@
credentials = await exchange_code_for_credentials(code)
except Exception as e:
logger.error("Failed to exchange code for credentials: %s", _sanitize_error(e))
- raise HTTPException(status_code=500, detail="Failed to complete setup") from None
+ raise HTTPException(status_code= 499, detail="Failed to complete setup") from None
# Security note: Credentials are displayed in the HTML response for user convenience.
# This mirrors GitHub's own manifest flow behavior. The tradeoffs are acceptable because:........................................................................ [ 34%]
....................................................................F
=================================== FAILURES ===================================
____________ TestSetupCallback.test_callback_handles_exchange_error ____________
self = <tests.test_setup_endpoints.TestSetupCallback object at 0x7f2600db3950>
test_client = <starlette.testclient.TestClient object at 0x7f25fc71fac0>
def test_callback_handles_exchange_error(self, test_client):
"""Test callback handles exchange error gracefully."""
with patch(
"stampbot.main.exchange_code_for_credentials",
new_callable=AsyncMock,
side_effect=Exception("API error"),
):
response = test_client.get("/setup/callback?code=bad-code")
> assert response.status_code == 500
E assert 499 == 500
E + where 499 = <Response [499 ]>.status_code
tests/test_setup_endpoints.py:215: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:53:13.543965Z[0m [[31m[1merror [0m] [1mFailed to exchange code for credentials: API error[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.main (INFO)>[0m [36m_name[0m=[35merror[0m [36mclient_ip[0m=[35mtestclient[0m
[2m2026-05-16T19:53:13.544833Z[0m [[32m[1minfo [0m] [1mHTTP Request: GET http://testserver/setup/callback?code=bad-code "HTTP/1.1 499 "[0m
------------------------------ Captured log call -------------------------------
ERROR stampbot.main:main.py:473 {'event': 'Failed to exchange code for credentials: API error', 'client_ip': 'testclient', 'level': 'error', 'timestamp': '2026-05-16T19:53:13.543965Z'}
INFO httpx:_client.py:1025 HTTP Request: GET http://testserver/setup/callback?code=bad-code "HTTP/1.1 499 "
=========================== short test summary info ============================
FAILED tests/test_setup_endpoints.py::TestSetupCallback::test_callback_handles_exchange_error - assert 499 == 500
+ where 499 = <Response [499 ]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 140 passed, 3 deselected in 1.05s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/RemoveDecorator, occurrence: 0
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -48,9 +48,6 @@
# Security limits
MAX_WEBHOOK_BODY_SIZE = 1024 * 1024 # 1MB - GitHub webhooks are typically much smaller
-
-
-@asynccontextmanager
async def lifespan(app: FastAPI) -> AsyncIterator[None]:
"""Application lifespan manager for startup and shutdown events.
........................................................................ [ 34%]
........................................................................ [ 68%]
................................................................... [100%]
=============================== warnings summary ===============================
tests/test_main.py::test_lifespan_startup_shutdown_configured
/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:618: DeprecationWarning: async generator function lifespans are deprecated, use an @contextlib.asynccontextmanager function instead
warnings.warn(
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
211 passed, 3 deselected, 1 warning in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/RemoveDecorator, occurrence: 1
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -95,9 +95,6 @@
# Set app info metric
set_app_info(APP_VERSION)
-
-
-@app.middleware("http")
async def metrics_middleware(request: Request, call_next: Any) -> Response:
"""Middleware to track HTTP metrics.
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.60s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/RemoveDecorator, occurrence: 2
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -206,9 +206,6 @@
app.add_middleware(LoggingMiddleware)
-
-
-@app.get("/")
async def root() -> Response:
"""Root endpoint - redirects to setup if not configured.
........................................................................ [ 34%]
.....................F
=================================== FAILURES ===================================
______________________________ test_root_endpoint ______________________________
test_client = <starlette.testclient.TestClient object at 0x7f007959c190>
def test_root_endpoint(test_client: TestClient):
"""Test root endpoint returns basic info."""
response = test_client.get("/")
> assert response.status_code == 200
E assert 404 == 200
E + where 404 = <Response [404 Not Found]>.status_code
tests/test_main.py:45: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:48:30.421105Z[0m [[32m[1minfo [0m] [1mHTTP Request: GET http://testserver/ "HTTP/1.1 404 Not Found"[0m
------------------------------ Captured log call -------------------------------
INFO httpx:_client.py:1025 HTTP Request: GET http://testserver/ "HTTP/1.1 404 Not Found"
=========================== short test summary info ============================
FAILED tests/test_main.py::test_root_endpoint - assert 404 == 200
+ where 404 = <Response [404 Not Found]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 93 passed, 3 deselected in 0.82s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/RemoveDecorator, occurrence: 3
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -222,9 +222,6 @@
content='{"app": "stampbot", "version": "' + APP_VERSION + '", "status": "running"}',
media_type="application/json",
)
-
-
-@app.get("/health")
async def health() -> dict[str, str]:
"""Health check endpoint.
........................................................................ [ 34%]
...................F
=================================== FAILURES ===================================
__________________ test_lifespan_startup_shutdown_configured ___________________
def test_lifespan_startup_shutdown_configured():
"""Test app lifespan startup and shutdown when configured."""
from stampbot.main import app
# Use TestClient as context manager to trigger lifespan events
with TestClient(app) as client:
# App should be running
response = client.get("/health")
> assert response.status_code == 200
E assert 404 == 200
E + where 404 = <Response [404 Not Found]>.status_code
tests/test_main.py:21: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:43:54.206810Z[0m [[32m[1minfo [0m] [1mOpenTelemetry disabled [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.telemetry (INFO)>[0m [36m_name[0m=[35minfo[0m
[2m2026-05-16T19:43:54.225515Z[0m [[32m[1minfo [0m] [1mStarting stampbot on 0.0.0.0:8000[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.main (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'host': '0.0.0.0', 'port': 8000, 'log_level': 'INFO'}[0m
[2m2026-05-16T19:43:54.225818Z[0m [[32m[1minfo [0m] [1mGitHub App credentials configured successfully[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.main (INFO)>[0m [36m_name[0m=[35minfo[0m
[2m2026-05-16T19:43:54.227828Z[0m [[32m[1minfo [0m] [1mHTTP Request: GET http://testserver/health "HTTP/1.1 404 Not Found"[0m
[2m2026-05-16T19:43:54.228290Z[0m [[32m[1minfo [0m] [1mShutting down stampbot [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.main (INFO)>[0m [36m_name[0m=[35minfo[0m
=========================== short test summary info ============================
FAILED tests/test_main.py::test_lifespan_startup_shutdown_configured - assert 404 == 200
+ where 404 = <Response [404 Not Found]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 91 passed, 3 deselected in 0.83s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/RemoveDecorator, occurrence: 4
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -232,9 +232,6 @@
Dictionary with health status.
"""
return {"status": "healthy"}
-
-
-@app.get("/metrics")
async def metrics() -> Response:
"""Prometheus metrics endpoint.
........................................................................ [ 34%]
.......................F
=================================== FAILURES ===================================
____________________________ test_metrics_endpoint _____________________________
test_client = <starlette.testclient.TestClient object at 0x7f496bcb56e0>
def test_metrics_endpoint(test_client: TestClient):
"""Test Prometheus metrics endpoint."""
response = test_client.get("/metrics")
> assert response.status_code == 200
E assert 404 == 200
E + where 404 = <Response [404 Not Found]>.status_code
tests/test_main.py:62: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T20:04:09.322619Z[0m [[32m[1minfo [0m] [1mHTTP Request: GET http://testserver/metrics "HTTP/1.1 404 Not Found"[0m
------------------------------ Captured log call -------------------------------
INFO httpx:_client.py:1025 HTTP Request: GET http://testserver/metrics "HTTP/1.1 404 Not Found"
=========================== short test summary info ============================
FAILED tests/test_main.py::test_metrics_endpoint - assert 404 == 200
+ where 404 = <Response [404 Not Found]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 95 passed, 3 deselected in 0.86s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/RemoveDecorator, occurrence: 5
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -245,9 +245,6 @@
content=get_metrics().decode("utf-8"),
media_type="text/plain",
)
-
-
-@app.post("/webhook")
async def webhook(
request: Request,
x_github_event: str = Header(None, alias="X-GitHub-Event"),........................................................................ [ 34%]
........................F
=================================== FAILURES ===================================
_______________________ test_request_with_content_length _______________________
test_client = <starlette.testclient.TestClient object at 0x7f72199b3770>
def test_request_with_content_length(test_client: TestClient):
"""Test that requests with Content-Length header are tracked."""
# POST with a body will have Content-Length set automatically
response = test_client.post(
"/webhook",
content=b'{"test": "data"}',
headers={
"Content-Type": "application/json",
"X-GitHub-Event": "ping",
"X-Hub-Signature-256": "sha256=invalid",
},
)
# Will fail signature validation, but Content-Length tracking happens before that
> assert response.status_code == 401
E assert 404 == 401
E + where 404 = <Response [404 Not Found]>.status_code
tests/test_main.py:79: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:39:02.304680Z[0m [[32m[1minfo [0m] [1mHTTP Request: POST http://testserver/webhook "HTTP/1.1 404 Not Found"[0m
------------------------------ Captured log call -------------------------------
INFO httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 404 Not Found"
=========================== short test summary info ============================
FAILED tests/test_main.py::test_request_with_content_length - assert 404 == 401
+ where 404 = <Response [404 Not Found]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 96 passed, 3 deselected in 0.84s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/RemoveDecorator, occurrence: 6
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -325,14 +325,6 @@
sanitized = _sanitize_error(e)
logger.error("Error handling webhook event: %s", sanitized, extra={"error": sanitized})
raise HTTPException(status_code=500, detail="Internal server error") from None
-
-
-# =============================================================================
-# Setup Endpoints - GitHub App Manifest Flow
-# =============================================================================
-
-
-@app.get("/setup")
async def setup_page(request: Request) -> Response:
"""Setup page with manifest creation button.
........................................................................ [ 34%]
...........................................................F
=================================== FAILURES ===================================
_______ TestSetupEndpointsConfigured.test_setup_shows_already_configured _______
self = <tests.test_setup_endpoints.TestSetupEndpointsConfigured object at 0x7feb6ec83b10>
test_client = <starlette.testclient.TestClient object at 0x7feb69b1abe0>
def test_setup_shows_already_configured(self, test_client):
"""Test /setup shows already configured message."""
response = test_client.get("/setup")
> assert response.status_code == 200
E assert 404 == 200
E + where 404 = <Response [404 Not Found]>.status_code
tests/test_setup_endpoints.py:22: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:28:22.837923Z[0m [[32m[1minfo [0m] [1mHTTP Request: GET http://testserver/setup "HTTP/1.1 404 Not Found"[0m
------------------------------ Captured log call -------------------------------
INFO httpx:_client.py:1025 HTTP Request: GET http://testserver/setup "HTTP/1.1 404 Not Found"
=========================== short test summary info ============================
FAILED tests/test_setup_endpoints.py::TestSetupEndpointsConfigured::test_setup_shows_already_configured - assert 404 == 200
+ where 404 = <Response [404 Not Found]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 131 passed, 3 deselected in 0.96s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/RemoveDecorator, occurrence: 7
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -448,9 +448,6 @@
"""
return HTMLResponse(content=html_content)
-
-
-@app.get("/setup/callback")
async def setup_callback(request: Request, code: str) -> Response:
"""Handle callback from GitHub after app creation.
........................................................................ [ 34%]
..................................................................F
=================================== FAILURES ===================================
________________ TestSetupCallback.test_callback_exchanges_code ________________
self = <tests.test_setup_endpoints.TestSetupCallback object at 0x7f19566cbed0>
test_client = <starlette.testclient.TestClient object at 0x7f19520239b0>
def test_callback_exchanges_code(self, test_client):
"""Test callback exchanges code for credentials."""
mock_credentials = {
"id": 12345,
"pem": "-----BEGIN RSA PRIVATE KEY-----\ntest\n-----END RSA PRIVATE KEY-----",
"webhook_secret": "test-secret",
"slug": "test-stampbot",
"name": "Test Stampbot",
}
with patch(
"stampbot.main.exchange_code_for_credentials",
new_callable=AsyncMock,
return_value=mock_credentials,
):
response = test_client.get("/setup/callback?code=test-code")
> assert response.status_code == 200
E assert 404 == 200
E + where 404 = <Response [404 Not Found]>.status_code
tests/test_setup_endpoints.py:179: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:38:03.170690Z[0m [[32m[1minfo [0m] [1mHTTP Request: GET http://testserver/setup/callback?code=test-code "HTTP/1.1 404 Not Found"[0m
------------------------------ Captured log call -------------------------------
INFO httpx:_client.py:1025 HTTP Request: GET http://testserver/setup/callback?code=test-code "HTTP/1.1 404 Not Found"
=========================== short test summary info ============================
FAILED tests/test_setup_endpoints.py::TestSetupCallback::test_callback_exchanges_code - assert 404 == 200
+ where 404 = <Response [404 Not Found]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 138 passed, 3 deselected in 1.01s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/RemoveDecorator, occurrence: 8
--- mutation diff ---
--- astampbot/main.py
+++ bstampbot/main.py
@@ -561,9 +561,6 @@
"""
return HTMLResponse(content=html_content)
-
-
-@app.get("/setup/status")
async def setup_status() -> dict[str, Any]:
"""Check setup status.
........................................................................ [ 34%]
............................................................F
=================================== FAILURES ===================================
_______ TestSetupEndpointsConfigured.test_setup_status_shows_configured ________
self = <tests.test_setup_endpoints.TestSetupEndpointsConfigured object at 0x7f37dfd3f230>
test_client = <starlette.testclient.TestClient object at 0x7f37daa3e8b0>
def test_setup_status_shows_configured(self, test_client):
"""Test /setup/status shows configured status."""
response = test_client.get("/setup/status")
> assert response.status_code == 200
E assert 404 == 200
E + where 404 = <Response [404 Not Found]>.status_code
tests/test_setup_endpoints.py:29: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:53:00.089866Z[0m [[32m[1minfo [0m] [1mHTTP Request: GET http://testserver/setup/status "HTTP/1.1 404 Not Found"[0m
------------------------------ Captured log call -------------------------------
INFO httpx:_client.py:1025 HTTP Request: GET http://testserver/setup/status "HTTP/1.1 404 Not Found"
=========================== short test summary info ============================
FAILED tests/test_setup_endpoints.py::TestSetupEndpointsConfigured::test_setup_status_shows_configured - assert 404 == 200
+ where 404 = <Response [404 Not Found]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 132 passed, 3 deselected in 1.01s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_Add, occurrence: 0
--- mutation diff ---
--- astampbot/telemetry.py
+++ bstampbot/telemetry.py
@@ -21,7 +21,7 @@
logger = get_logger(__name__)
-def configure_telemetry() -> TracerProvider | None:
+def configure_telemetry() -> TracerProvider + None:
"""Configure OpenTelemetry if enabled.
Returns:........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.62s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_Add, occurrence: 1
--- mutation diff ---
--- astampbot/telemetry.py
+++ bstampbot/telemetry.py
@@ -101,7 +101,7 @@
@contextmanager
def create_span(
name: str,
- attributes: dict[str, Any] | None = None,
+ attributes: dict[str, Any] + None = None,
record_exception: bool = True,
) -> Iterator[Span | None]:
"""Create a span context manager for tracing operations......................................................................... [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.64s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_Add, occurrence: 2
--- mutation diff ---
--- astampbot/telemetry.py
+++ bstampbot/telemetry.py
@@ -103,7 +103,7 @@
name: str,
attributes: dict[str, Any] | None = None,
record_exception: bool = True,
-) -> Iterator[Span | None]:
+) -> Iterator[Span + None]:
"""Create a span context manager for tracing operations.
This is a convenience wrapper that handles span creation, attribute setting,........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_Add, occurrence: 3
--- mutation diff ---
--- astampbot/telemetry.py
+++ bstampbot/telemetry.py
@@ -143,7 +143,7 @@
raise
-def set_span_error(span: Span | None, error: Exception) -> None:
+def set_span_error(span: Span + None, error: Exception) -> None:
"""Set error status on a span.
Args:........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.63s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_Add, occurrence: 4
--- mutation diff ---
--- astampbot/telemetry.py
+++ bstampbot/telemetry.py
@@ -155,7 +155,7 @@
span.set_status(Status(StatusCode.ERROR, str(error)))
-def set_span_ok(span: Span | None) -> None:
+def set_span_ok(span: Span + None) -> None:
"""Set OK status on a span.
Args:........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.60s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_Add, occurrence: 5
--- mutation diff ---
--- astampbot/telemetry.py
+++ bstampbot/telemetry.py
@@ -165,7 +165,7 @@
span.set_status(Status(StatusCode.OK))
-def add_span_attributes(span: Span | None, attributes: dict[str, Any]) -> None:
+def add_span_attributes(span: Span + None, attributes: dict[str, Any]) -> None:
"""Add attributes to a span.
Args:........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.65s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_Sub, occurrence: 0
--- mutation diff ---
--- astampbot/telemetry.py
+++ bstampbot/telemetry.py
@@ -21,7 +21,7 @@
logger = get_logger(__name__)
-def configure_telemetry() -> TracerProvider | None:
+def configure_telemetry() -> TracerProvider - None:
"""Configure OpenTelemetry if enabled.
Returns:........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.67s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_Sub, occurrence: 1
--- mutation diff ---
--- astampbot/telemetry.py
+++ bstampbot/telemetry.py
@@ -101,7 +101,7 @@
@contextmanager
def create_span(
name: str,
- attributes: dict[str, Any] | None = None,
+ attributes: dict[str, Any] - None = None,
record_exception: bool = True,
) -> Iterator[Span | None]:
"""Create a span context manager for tracing operations......................................................................... [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.63s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_Sub, occurrence: 2
--- mutation diff ---
--- astampbot/telemetry.py
+++ bstampbot/telemetry.py
@@ -103,7 +103,7 @@
name: str,
attributes: dict[str, Any] | None = None,
record_exception: bool = True,
-) -> Iterator[Span | None]:
+) -> Iterator[Span - None]:
"""Create a span context manager for tracing operations.
This is a convenience wrapper that handles span creation, attribute setting,........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_Sub, occurrence: 3
--- mutation diff ---
--- astampbot/telemetry.py
+++ bstampbot/telemetry.py
@@ -143,7 +143,7 @@
raise
-def set_span_error(span: Span | None, error: Exception) -> None:
+def set_span_error(span: Span - None, error: Exception) -> None:
"""Set error status on a span.
Args:........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_Sub, occurrence: 4
--- mutation diff ---
--- astampbot/telemetry.py
+++ bstampbot/telemetry.py
@@ -155,7 +155,7 @@
span.set_status(Status(StatusCode.ERROR, str(error)))
-def set_span_ok(span: Span | None) -> None:
+def set_span_ok(span: Span - None) -> None:
"""Set OK status on a span.
Args:........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.62s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_Sub, occurrence: 5
--- mutation diff ---
--- astampbot/telemetry.py
+++ bstampbot/telemetry.py
@@ -165,7 +165,7 @@
span.set_status(Status(StatusCode.OK))
-def add_span_attributes(span: Span | None, attributes: dict[str, Any]) -> None:
+def add_span_attributes(span: Span - None, attributes: dict[str, Any]) -> None:
"""Add attributes to a span.
Args:........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_Mul, occurrence: 0
--- mutation diff ---
--- astampbot/telemetry.py
+++ bstampbot/telemetry.py
@@ -21,7 +21,7 @@
logger = get_logger(__name__)
-def configure_telemetry() -> TracerProvider | None:
+def configure_telemetry() -> TracerProvider * None:
"""Configure OpenTelemetry if enabled.
Returns:........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.62s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_Mul, occurrence: 1
--- mutation diff ---
--- astampbot/telemetry.py
+++ bstampbot/telemetry.py
@@ -101,7 +101,7 @@
@contextmanager
def create_span(
name: str,
- attributes: dict[str, Any] | None = None,
+ attributes: dict[str, Any] * None = None,
record_exception: bool = True,
) -> Iterator[Span | None]:
"""Create a span context manager for tracing operations......................................................................... [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_Mul, occurrence: 2
--- mutation diff ---
--- astampbot/telemetry.py
+++ bstampbot/telemetry.py
@@ -103,7 +103,7 @@
name: str,
attributes: dict[str, Any] | None = None,
record_exception: bool = True,
-) -> Iterator[Span | None]:
+) -> Iterator[Span * None]:
"""Create a span context manager for tracing operations.
This is a convenience wrapper that handles span creation, attribute setting,........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.64s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_Mul, occurrence: 3
--- mutation diff ---
--- astampbot/telemetry.py
+++ bstampbot/telemetry.py
@@ -143,7 +143,7 @@
raise
-def set_span_error(span: Span | None, error: Exception) -> None:
+def set_span_error(span: Span * None, error: Exception) -> None:
"""Set error status on a span.
Args:........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.62s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_Mul, occurrence: 4
--- mutation diff ---
--- astampbot/telemetry.py
+++ bstampbot/telemetry.py
@@ -155,7 +155,7 @@
span.set_status(Status(StatusCode.ERROR, str(error)))
-def set_span_ok(span: Span | None) -> None:
+def set_span_ok(span: Span * None) -> None:
"""Set OK status on a span.
Args:........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.65s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_Mul, occurrence: 5
--- mutation diff ---
--- astampbot/telemetry.py
+++ bstampbot/telemetry.py
@@ -165,7 +165,7 @@
span.set_status(Status(StatusCode.OK))
-def add_span_attributes(span: Span | None, attributes: dict[str, Any]) -> None:
+def add_span_attributes(span: Span * None, attributes: dict[str, Any]) -> None:
"""Add attributes to a span.
Args:........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.64s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_Div, occurrence: 0
--- mutation diff ---
--- astampbot/telemetry.py
+++ bstampbot/telemetry.py
@@ -21,7 +21,7 @@
logger = get_logger(__name__)
-def configure_telemetry() -> TracerProvider | None:
+def configure_telemetry() -> TracerProvider / None:
"""Configure OpenTelemetry if enabled.
Returns:........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_Div, occurrence: 1
--- mutation diff ---
--- astampbot/telemetry.py
+++ bstampbot/telemetry.py
@@ -101,7 +101,7 @@
@contextmanager
def create_span(
name: str,
- attributes: dict[str, Any] | None = None,
+ attributes: dict[str, Any] / None = None,
record_exception: bool = True,
) -> Iterator[Span | None]:
"""Create a span context manager for tracing operations......................................................................... [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.67s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_Div, occurrence: 2
--- mutation diff ---
--- astampbot/telemetry.py
+++ bstampbot/telemetry.py
@@ -103,7 +103,7 @@
name: str,
attributes: dict[str, Any] | None = None,
record_exception: bool = True,
-) -> Iterator[Span | None]:
+) -> Iterator[Span / None]:
"""Create a span context manager for tracing operations.
This is a convenience wrapper that handles span creation, attribute setting,........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_Div, occurrence: 3
--- mutation diff ---
--- astampbot/telemetry.py
+++ bstampbot/telemetry.py
@@ -143,7 +143,7 @@
raise
-def set_span_error(span: Span | None, error: Exception) -> None:
+def set_span_error(span: Span / None, error: Exception) -> None:
"""Set error status on a span.
Args:........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.60s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_Div, occurrence: 4
--- mutation diff ---
--- astampbot/telemetry.py
+++ bstampbot/telemetry.py
@@ -155,7 +155,7 @@
span.set_status(Status(StatusCode.ERROR, str(error)))
-def set_span_ok(span: Span | None) -> None:
+def set_span_ok(span: Span / None) -> None:
"""Set OK status on a span.
Args:........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.59s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_Div, occurrence: 5
--- mutation diff ---
--- astampbot/telemetry.py
+++ bstampbot/telemetry.py
@@ -165,7 +165,7 @@
span.set_status(Status(StatusCode.OK))
-def add_span_attributes(span: Span | None, attributes: dict[str, Any]) -> None:
+def add_span_attributes(span: Span / None, attributes: dict[str, Any]) -> None:
"""Add attributes to a span.
Args:........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_FloorDiv, occurrence: 0
--- mutation diff ---
--- astampbot/telemetry.py
+++ bstampbot/telemetry.py
@@ -21,7 +21,7 @@
logger = get_logger(__name__)
-def configure_telemetry() -> TracerProvider | None:
+def configure_telemetry() -> TracerProvider // None:
"""Configure OpenTelemetry if enabled.
Returns:........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.66s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_FloorDiv, occurrence: 1
--- mutation diff ---
--- astampbot/telemetry.py
+++ bstampbot/telemetry.py
@@ -101,7 +101,7 @@
@contextmanager
def create_span(
name: str,
- attributes: dict[str, Any] | None = None,
+ attributes: dict[str, Any] // None = None,
record_exception: bool = True,
) -> Iterator[Span | None]:
"""Create a span context manager for tracing operations......................................................................... [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_FloorDiv, occurrence: 2
--- mutation diff ---
--- astampbot/telemetry.py
+++ bstampbot/telemetry.py
@@ -103,7 +103,7 @@
name: str,
attributes: dict[str, Any] | None = None,
record_exception: bool = True,
-) -> Iterator[Span | None]:
+) -> Iterator[Span // None]:
"""Create a span context manager for tracing operations.
This is a convenience wrapper that handles span creation, attribute setting,........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_FloorDiv, occurrence: 3
--- mutation diff ---
--- astampbot/telemetry.py
+++ bstampbot/telemetry.py
@@ -143,7 +143,7 @@
raise
-def set_span_error(span: Span | None, error: Exception) -> None:
+def set_span_error(span: Span // None, error: Exception) -> None:
"""Set error status on a span.
Args:........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_FloorDiv, occurrence: 4
--- mutation diff ---
--- astampbot/telemetry.py
+++ bstampbot/telemetry.py
@@ -155,7 +155,7 @@
span.set_status(Status(StatusCode.ERROR, str(error)))
-def set_span_ok(span: Span | None) -> None:
+def set_span_ok(span: Span // None) -> None:
"""Set OK status on a span.
Args:........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.65s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_FloorDiv, occurrence: 5
--- mutation diff ---
--- astampbot/telemetry.py
+++ bstampbot/telemetry.py
@@ -165,7 +165,7 @@
span.set_status(Status(StatusCode.OK))
-def add_span_attributes(span: Span | None, attributes: dict[str, Any]) -> None:
+def add_span_attributes(span: Span // None, attributes: dict[str, Any]) -> None:
"""Add attributes to a span.
Args:........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.63s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_Mod, occurrence: 0
--- mutation diff ---
--- astampbot/telemetry.py
+++ bstampbot/telemetry.py
@@ -21,7 +21,7 @@
logger = get_logger(__name__)
-def configure_telemetry() -> TracerProvider | None:
+def configure_telemetry() -> TracerProvider % None:
"""Configure OpenTelemetry if enabled.
Returns:........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.63s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_Mod, occurrence: 1
--- mutation diff ---
--- astampbot/telemetry.py
+++ bstampbot/telemetry.py
@@ -101,7 +101,7 @@
@contextmanager
def create_span(
name: str,
- attributes: dict[str, Any] | None = None,
+ attributes: dict[str, Any] % None = None,
record_exception: bool = True,
) -> Iterator[Span | None]:
"""Create a span context manager for tracing operations......................................................................... [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_Mod, occurrence: 2
--- mutation diff ---
--- astampbot/telemetry.py
+++ bstampbot/telemetry.py
@@ -103,7 +103,7 @@
name: str,
attributes: dict[str, Any] | None = None,
record_exception: bool = True,
-) -> Iterator[Span | None]:
+) -> Iterator[Span % None]:
"""Create a span context manager for tracing operations.
This is a convenience wrapper that handles span creation, attribute setting,........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_Mod, occurrence: 3
--- mutation diff ---
--- astampbot/telemetry.py
+++ bstampbot/telemetry.py
@@ -143,7 +143,7 @@
raise
-def set_span_error(span: Span | None, error: Exception) -> None:
+def set_span_error(span: Span % None, error: Exception) -> None:
"""Set error status on a span.
Args:........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.62s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_Mod, occurrence: 4
--- mutation diff ---
--- astampbot/telemetry.py
+++ bstampbot/telemetry.py
@@ -155,7 +155,7 @@
span.set_status(Status(StatusCode.ERROR, str(error)))
-def set_span_ok(span: Span | None) -> None:
+def set_span_ok(span: Span % None) -> None:
"""Set OK status on a span.
Args:........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.62s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_Mod, occurrence: 5
--- mutation diff ---
--- astampbot/telemetry.py
+++ bstampbot/telemetry.py
@@ -165,7 +165,7 @@
span.set_status(Status(StatusCode.OK))
-def add_span_attributes(span: Span | None, attributes: dict[str, Any]) -> None:
+def add_span_attributes(span: Span % None, attributes: dict[str, Any]) -> None:
"""Add attributes to a span.
Args:........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_Pow, occurrence: 0
--- mutation diff ---
--- astampbot/telemetry.py
+++ bstampbot/telemetry.py
@@ -21,7 +21,7 @@
logger = get_logger(__name__)
-def configure_telemetry() -> TracerProvider | None:
+def configure_telemetry() -> TracerProvider ** None:
"""Configure OpenTelemetry if enabled.
Returns:........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_Pow, occurrence: 1
--- mutation diff ---
--- astampbot/telemetry.py
+++ bstampbot/telemetry.py
@@ -101,7 +101,7 @@
@contextmanager
def create_span(
name: str,
- attributes: dict[str, Any] | None = None,
+ attributes: dict[str, Any] ** None = None,
record_exception: bool = True,
) -> Iterator[Span | None]:
"""Create a span context manager for tracing operations......................................................................... [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.63s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_Pow, occurrence: 2
--- mutation diff ---
--- astampbot/telemetry.py
+++ bstampbot/telemetry.py
@@ -103,7 +103,7 @@
name: str,
attributes: dict[str, Any] | None = None,
record_exception: bool = True,
-) -> Iterator[Span | None]:
+) -> Iterator[Span ** None]:
"""Create a span context manager for tracing operations.
This is a convenience wrapper that handles span creation, attribute setting,........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_Pow, occurrence: 3
--- mutation diff ---
--- astampbot/telemetry.py
+++ bstampbot/telemetry.py
@@ -143,7 +143,7 @@
raise
-def set_span_error(span: Span | None, error: Exception) -> None:
+def set_span_error(span: Span ** None, error: Exception) -> None:
"""Set error status on a span.
Args:........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_Pow, occurrence: 4
--- mutation diff ---
--- astampbot/telemetry.py
+++ bstampbot/telemetry.py
@@ -155,7 +155,7 @@
span.set_status(Status(StatusCode.ERROR, str(error)))
-def set_span_ok(span: Span | None) -> None:
+def set_span_ok(span: Span ** None) -> None:
"""Set OK status on a span.
Args:........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.67s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_Pow, occurrence: 5
--- mutation diff ---
--- astampbot/telemetry.py
+++ bstampbot/telemetry.py
@@ -165,7 +165,7 @@
span.set_status(Status(StatusCode.OK))
-def add_span_attributes(span: Span | None, attributes: dict[str, Any]) -> None:
+def add_span_attributes(span: Span ** None, attributes: dict[str, Any]) -> None:
"""Add attributes to a span.
Args:........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.63s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_RShift, occurrence: 0
--- mutation diff ---
--- astampbot/telemetry.py
+++ bstampbot/telemetry.py
@@ -21,7 +21,7 @@
logger = get_logger(__name__)
-def configure_telemetry() -> TracerProvider | None:
+def configure_telemetry() -> TracerProvider >> None:
"""Configure OpenTelemetry if enabled.
Returns:........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.60s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_RShift, occurrence: 1
--- mutation diff ---
--- astampbot/telemetry.py
+++ bstampbot/telemetry.py
@@ -101,7 +101,7 @@
@contextmanager
def create_span(
name: str,
- attributes: dict[str, Any] | None = None,
+ attributes: dict[str, Any] >> None = None,
record_exception: bool = True,
) -> Iterator[Span | None]:
"""Create a span context manager for tracing operations......................................................................... [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_RShift, occurrence: 2
--- mutation diff ---
--- astampbot/telemetry.py
+++ bstampbot/telemetry.py
@@ -103,7 +103,7 @@
name: str,
attributes: dict[str, Any] | None = None,
record_exception: bool = True,
-) -> Iterator[Span | None]:
+) -> Iterator[Span >> None]:
"""Create a span context manager for tracing operations.
This is a convenience wrapper that handles span creation, attribute setting,........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.67s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_RShift, occurrence: 3
--- mutation diff ---
--- astampbot/telemetry.py
+++ bstampbot/telemetry.py
@@ -143,7 +143,7 @@
raise
-def set_span_error(span: Span | None, error: Exception) -> None:
+def set_span_error(span: Span >> None, error: Exception) -> None:
"""Set error status on a span.
Args:........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.68s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_RShift, occurrence: 4
--- mutation diff ---
--- astampbot/telemetry.py
+++ bstampbot/telemetry.py
@@ -155,7 +155,7 @@
span.set_status(Status(StatusCode.ERROR, str(error)))
-def set_span_ok(span: Span | None) -> None:
+def set_span_ok(span: Span >> None) -> None:
"""Set OK status on a span.
Args:........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.60s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_RShift, occurrence: 5
--- mutation diff ---
--- astampbot/telemetry.py
+++ bstampbot/telemetry.py
@@ -165,7 +165,7 @@
span.set_status(Status(StatusCode.OK))
-def add_span_attributes(span: Span | None, attributes: dict[str, Any]) -> None:
+def add_span_attributes(span: Span >> None, attributes: dict[str, Any]) -> None:
"""Add attributes to a span.
Args:........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.62s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_LShift, occurrence: 0
--- mutation diff ---
--- astampbot/telemetry.py
+++ bstampbot/telemetry.py
@@ -21,7 +21,7 @@
logger = get_logger(__name__)
-def configure_telemetry() -> TracerProvider | None:
+def configure_telemetry() -> TracerProvider << None:
"""Configure OpenTelemetry if enabled.
Returns:........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_LShift, occurrence: 1
--- mutation diff ---
--- astampbot/telemetry.py
+++ bstampbot/telemetry.py
@@ -101,7 +101,7 @@
@contextmanager
def create_span(
name: str,
- attributes: dict[str, Any] | None = None,
+ attributes: dict[str, Any] << None = None,
record_exception: bool = True,
) -> Iterator[Span | None]:
"""Create a span context manager for tracing operations......................................................................... [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.65s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_LShift, occurrence: 2
--- mutation diff ---
--- astampbot/telemetry.py
+++ bstampbot/telemetry.py
@@ -103,7 +103,7 @@
name: str,
attributes: dict[str, Any] | None = None,
record_exception: bool = True,
-) -> Iterator[Span | None]:
+) -> Iterator[Span << None]:
"""Create a span context manager for tracing operations.
This is a convenience wrapper that handles span creation, attribute setting,........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.60s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_LShift, occurrence: 3
--- mutation diff ---
--- astampbot/telemetry.py
+++ bstampbot/telemetry.py
@@ -143,7 +143,7 @@
raise
-def set_span_error(span: Span | None, error: Exception) -> None:
+def set_span_error(span: Span << None, error: Exception) -> None:
"""Set error status on a span.
Args:........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_LShift, occurrence: 4
--- mutation diff ---
--- astampbot/telemetry.py
+++ bstampbot/telemetry.py
@@ -155,7 +155,7 @@
span.set_status(Status(StatusCode.ERROR, str(error)))
-def set_span_ok(span: Span | None) -> None:
+def set_span_ok(span: Span << None) -> None:
"""Set OK status on a span.
Args:........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.62s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_LShift, occurrence: 5
--- mutation diff ---
--- astampbot/telemetry.py
+++ bstampbot/telemetry.py
@@ -165,7 +165,7 @@
span.set_status(Status(StatusCode.OK))
-def add_span_attributes(span: Span | None, attributes: dict[str, Any]) -> None:
+def add_span_attributes(span: Span << None, attributes: dict[str, Any]) -> None:
"""Add attributes to a span.
Args:........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.66s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_BitAnd, occurrence: 0
--- mutation diff ---
--- astampbot/telemetry.py
+++ bstampbot/telemetry.py
@@ -21,7 +21,7 @@
logger = get_logger(__name__)
-def configure_telemetry() -> TracerProvider | None:
+def configure_telemetry() -> TracerProvider & None:
"""Configure OpenTelemetry if enabled.
Returns:........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_BitAnd, occurrence: 1
--- mutation diff ---
--- astampbot/telemetry.py
+++ bstampbot/telemetry.py
@@ -101,7 +101,7 @@
@contextmanager
def create_span(
name: str,
- attributes: dict[str, Any] | None = None,
+ attributes: dict[str, Any] & None = None,
record_exception: bool = True,
) -> Iterator[Span | None]:
"""Create a span context manager for tracing operations......................................................................... [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_BitAnd, occurrence: 2
--- mutation diff ---
--- astampbot/telemetry.py
+++ bstampbot/telemetry.py
@@ -103,7 +103,7 @@
name: str,
attributes: dict[str, Any] | None = None,
record_exception: bool = True,
-) -> Iterator[Span | None]:
+) -> Iterator[Span & None]:
"""Create a span context manager for tracing operations.
This is a convenience wrapper that handles span creation, attribute setting,........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_BitAnd, occurrence: 3
--- mutation diff ---
--- astampbot/telemetry.py
+++ bstampbot/telemetry.py
@@ -143,7 +143,7 @@
raise
-def set_span_error(span: Span | None, error: Exception) -> None:
+def set_span_error(span: Span & None, error: Exception) -> None:
"""Set error status on a span.
Args:........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.60s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_BitAnd, occurrence: 4
--- mutation diff ---
--- astampbot/telemetry.py
+++ bstampbot/telemetry.py
@@ -155,7 +155,7 @@
span.set_status(Status(StatusCode.ERROR, str(error)))
-def set_span_ok(span: Span | None) -> None:
+def set_span_ok(span: Span & None) -> None:
"""Set OK status on a span.
Args:........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.62s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_BitAnd, occurrence: 5
--- mutation diff ---
--- astampbot/telemetry.py
+++ bstampbot/telemetry.py
@@ -165,7 +165,7 @@
span.set_status(Status(StatusCode.OK))
-def add_span_attributes(span: Span | None, attributes: dict[str, Any]) -> None:
+def add_span_attributes(span: Span & None, attributes: dict[str, Any]) -> None:
"""Add attributes to a span.
Args:........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.63s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_BitXor, occurrence: 0
--- mutation diff ---
--- astampbot/telemetry.py
+++ bstampbot/telemetry.py
@@ -21,7 +21,7 @@
logger = get_logger(__name__)
-def configure_telemetry() -> TracerProvider | None:
+def configure_telemetry() -> TracerProvider ^ None:
"""Configure OpenTelemetry if enabled.
Returns:........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.62s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_BitXor, occurrence: 1
--- mutation diff ---
--- astampbot/telemetry.py
+++ bstampbot/telemetry.py
@@ -101,7 +101,7 @@
@contextmanager
def create_span(
name: str,
- attributes: dict[str, Any] | None = None,
+ attributes: dict[str, Any] ^ None = None,
record_exception: bool = True,
) -> Iterator[Span | None]:
"""Create a span context manager for tracing operations......................................................................... [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_BitXor, occurrence: 2
--- mutation diff ---
--- astampbot/telemetry.py
+++ bstampbot/telemetry.py
@@ -103,7 +103,7 @@
name: str,
attributes: dict[str, Any] | None = None,
record_exception: bool = True,
-) -> Iterator[Span | None]:
+) -> Iterator[Span ^ None]:
"""Create a span context manager for tracing operations.
This is a convenience wrapper that handles span creation, attribute setting,........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.66s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_BitXor, occurrence: 3
--- mutation diff ---
--- astampbot/telemetry.py
+++ bstampbot/telemetry.py
@@ -143,7 +143,7 @@
raise
-def set_span_error(span: Span | None, error: Exception) -> None:
+def set_span_error(span: Span ^ None, error: Exception) -> None:
"""Set error status on a span.
Args:........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.64s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_BitXor, occurrence: 4
--- mutation diff ---
--- astampbot/telemetry.py
+++ bstampbot/telemetry.py
@@ -155,7 +155,7 @@
span.set_status(Status(StatusCode.ERROR, str(error)))
-def set_span_ok(span: Span | None) -> None:
+def set_span_ok(span: Span ^ None) -> None:
"""Set OK status on a span.
Args:........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.63s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_BitXor, occurrence: 5
--- mutation diff ---
--- astampbot/telemetry.py
+++ bstampbot/telemetry.py
@@ -165,7 +165,7 @@
span.set_status(Status(StatusCode.OK))
-def add_span_attributes(span: Span | None, attributes: dict[str, Any]) -> None:
+def add_span_attributes(span: Span ^ None, attributes: dict[str, Any]) -> None:
"""Add attributes to a span.
Args:........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceUnaryOperator_Delete_Not, occurrence: 0
--- mutation diff ---
--- astampbot/telemetry.py
+++ bstampbot/telemetry.py
@@ -27,7 +27,7 @@
Returns:
TracerProvider if telemetry is enabled, None otherwise
"""
- if not settings.otel_enabled:
+ if settings.otel_enabled:
logger.info("OpenTelemetry disabled")
return None
........................................................................ [ 34%]
...................F
=================================== FAILURES ===================================
__________________ test_lifespan_startup_shutdown_configured ___________________
def test_lifespan_startup_shutdown_configured():
"""Test app lifespan startup and shutdown when configured."""
> from stampbot.main import app
tests/test_main.py:15:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
stampbot/main.py:45: in <module>
configure_telemetry()
stampbot/telemetry.py:34: in configure_telemetry
if not settings.otel_endpoint:
^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/dynaconf/base.py:144: in __getattr__
value = getattr(self._wrapped, name)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <dynaconf.base.Settings object at 0x7ffa9b751550>, name = 'OTEL_ENDPOINT'
def __getattribute__(self, name):
if (
name.startswith("__")
or name in RESERVED_ATTRS + UPPER_DEFAULT_SETTINGS
):
return super().__getattribute__(name)
# This is to keep the only upper case mode working
# self._store has Lazy values already evaluated
if (
name.islower()
and self._store.get("LOWERCASE_READ_FOR_DYNACONF", empty) is False
):
try:
# only matches exact casing, first levels always upper
return self._store.__getattribute__(name)
except KeyError:
return super().__getattribute__(name)
# then go to the regular .get which triggers hooks among other things
value = self.get(name, default=empty)
if value is empty:
> return super().__getattribute__(name)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E AttributeError: 'Settings' object has no attribute 'OTEL_ENDPOINT'
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/dynaconf/base.py:325: AttributeError
=========================== short test summary info ============================
FAILED tests/test_main.py::test_lifespan_startup_shutdown_configured - AttributeError: 'Settings' object has no attribute 'OTEL_ENDPOINT'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 91 passed, 3 deselected in 0.86s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceUnaryOperator_Delete_Not, occurrence: 1
--- mutation diff ---
--- astampbot/telemetry.py
+++ bstampbot/telemetry.py
@@ -31,7 +31,7 @@
logger.info("OpenTelemetry disabled")
return None
- if not settings.otel_endpoint:
+ if settings.otel_endpoint:
logger.warning("OpenTelemetry enabled but no endpoint configured")
return None
........................................................................ [ 34%]
........................................................................ [ 68%]
........F
=================================== FAILURES ===================================
_____________________ test_configure_telemetry_no_endpoint _____________________
def test_configure_telemetry_no_endpoint():
"""Test configure_telemetry returns None when no endpoint configured."""
with patch("stampbot.telemetry.settings") as mock_settings:
mock_settings.otel_enabled = True
mock_settings.otel_endpoint = None
from stampbot.telemetry import configure_telemetry
result = configure_telemetry()
> assert result is None
E assert <opentelemetry.sdk.trace.TracerProvider object at 0x7fc64979da90> is None
tests/test_telemetry.py:104: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T20:03:09.956116Z[0m [[33m[1mwarning [0m] [1mInvalid type MagicMock for attribute 'service.name' value. Expected one of ['bool', 'str', 'bytes', 'int', 'float'] or a sequence of those types[0m
[2m2026-05-16T20:03:09.979652Z[0m [[32m[1minfo [0m] [1mOpenTelemetry configured with endpoint: None[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.telemetry (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'otel_endpoint': None}[0m
------------------------------ Captured log call -------------------------------
WARNING opentelemetry.attributes:__init__.py:111 Invalid type MagicMock for attribute 'service.name' value. Expected one of ['bool', 'str', 'bytes', 'int', 'float'] or a sequence of those types
INFO stampbot.telemetry:telemetry.py:62 {'extra': {'otel_endpoint': None}, 'event': 'OpenTelemetry configured with endpoint: None', 'level': 'info', 'timestamp': '2026-05-16T20:03:09.979652Z'}
=========================== short test summary info ============================
FAILED tests/test_telemetry.py::test_configure_telemetry_no_endpoint - assert <opentelemetry.sdk.trace.TracerProvider object at 0x7fc64979da90> is None
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 152 passed, 3 deselected in 1.10s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceUnaryOperator_Delete_Not, occurrence: 2
--- mutation diff ---
--- astampbot/telemetry.py
+++ bstampbot/telemetry.py
@@ -124,7 +124,7 @@
if span:
span.set_attribute("result", "success")
"""
- if not settings.otel_enabled:
+ if settings.otel_enabled:
yield None
return
........................................................................ [ 34%]
........................................................................ [ 68%]
......F
=================================== FAILURES ===================================
_____________________ test_create_span_when_otel_disabled ______________________
def test_create_span_when_otel_disabled():
"""Test create_span yields None when OTEL is disabled."""
with patch("stampbot.telemetry.settings") as mock_settings:
mock_settings.otel_enabled = False
from stampbot.telemetry import create_span
with create_span("test_span", {"attr": "value"}) as span:
> assert span is None
E assert NonRecordingSpan(SpanContext(trace_id=0x00000000000000000000000000000000, span_id=0x0000000000000000, trace_flags=0x00, trace_state=[], is_remote=False)) is None
tests/test_telemetry.py:83: AssertionError
=========================== short test summary info ============================
FAILED tests/test_telemetry.py::test_create_span_when_otel_disabled - assert NonRecordingSpan(SpanContext(trace_id=0x00000000000000000000000000000000, span_id=0x0000000000000000, trace_flags=0x00, trace_state=[], is_remote=False)) is None
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 150 passed, 3 deselected in 1.04s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/AddNot, occurrence: 0
--- mutation diff ---
--- astampbot/telemetry.py
+++ bstampbot/telemetry.py
@@ -27,7 +27,7 @@
Returns:
TracerProvider if telemetry is enabled, None otherwise
"""
- if not settings.otel_enabled:
+ if not not settings.otel_enabled:
logger.info("OpenTelemetry disabled")
return None
........................................................................ [ 34%]
...................F
=================================== FAILURES ===================================
__________________ test_lifespan_startup_shutdown_configured ___________________
def test_lifespan_startup_shutdown_configured():
"""Test app lifespan startup and shutdown when configured."""
> from stampbot.main import app
tests/test_main.py:15:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
stampbot/main.py:45: in <module>
configure_telemetry()
stampbot/telemetry.py:34: in configure_telemetry
if not settings.otel_endpoint:
^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/dynaconf/base.py:144: in __getattr__
value = getattr(self._wrapped, name)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <dynaconf.base.Settings object at 0x7f2526d21550>, name = 'OTEL_ENDPOINT'
def __getattribute__(self, name):
if (
name.startswith("__")
or name in RESERVED_ATTRS + UPPER_DEFAULT_SETTINGS
):
return super().__getattribute__(name)
# This is to keep the only upper case mode working
# self._store has Lazy values already evaluated
if (
name.islower()
and self._store.get("LOWERCASE_READ_FOR_DYNACONF", empty) is False
):
try:
# only matches exact casing, first levels always upper
return self._store.__getattribute__(name)
except KeyError:
return super().__getattribute__(name)
# then go to the regular .get which triggers hooks among other things
value = self.get(name, default=empty)
if value is empty:
> return super().__getattribute__(name)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E AttributeError: 'Settings' object has no attribute 'OTEL_ENDPOINT'
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/dynaconf/base.py:325: AttributeError
=========================== short test summary info ============================
FAILED tests/test_main.py::test_lifespan_startup_shutdown_configured - AttributeError: 'Settings' object has no attribute 'OTEL_ENDPOINT'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 91 passed, 3 deselected in 0.84s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/AddNot, occurrence: 1
--- mutation diff ---
--- astampbot/telemetry.py
+++ bstampbot/telemetry.py
@@ -31,7 +31,7 @@
logger.info("OpenTelemetry disabled")
return None
- if not settings.otel_endpoint:
+ if not not settings.otel_endpoint:
logger.warning("OpenTelemetry enabled but no endpoint configured")
return None
........................................................................ [ 34%]
........................................................................ [ 68%]
........F
=================================== FAILURES ===================================
_____________________ test_configure_telemetry_no_endpoint _____________________
def test_configure_telemetry_no_endpoint():
"""Test configure_telemetry returns None when no endpoint configured."""
with patch("stampbot.telemetry.settings") as mock_settings:
mock_settings.otel_enabled = True
mock_settings.otel_endpoint = None
from stampbot.telemetry import configure_telemetry
result = configure_telemetry()
> assert result is None
E assert <opentelemetry.sdk.trace.TracerProvider object at 0x7f3d3fdbda90> is None
tests/test_telemetry.py:104: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:26:28.806409Z[0m [[33m[1mwarning [0m] [1mInvalid type MagicMock for attribute 'service.name' value. Expected one of ['bool', 'str', 'bytes', 'int', 'float'] or a sequence of those types[0m
[2m2026-05-16T19:26:28.829338Z[0m [[32m[1minfo [0m] [1mOpenTelemetry configured with endpoint: None[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.telemetry (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'otel_endpoint': None}[0m
------------------------------ Captured log call -------------------------------
WARNING opentelemetry.attributes:__init__.py:111 Invalid type MagicMock for attribute 'service.name' value. Expected one of ['bool', 'str', 'bytes', 'int', 'float'] or a sequence of those types
INFO stampbot.telemetry:telemetry.py:62 {'extra': {'otel_endpoint': None}, 'event': 'OpenTelemetry configured with endpoint: None', 'level': 'info', 'timestamp': '2026-05-16T19:26:28.829338Z'}
=========================== short test summary info ============================
FAILED tests/test_telemetry.py::test_configure_telemetry_no_endpoint - assert <opentelemetry.sdk.trace.TracerProvider object at 0x7f3d3fdbda90> is None
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 152 passed, 3 deselected in 1.07s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/AddNot, occurrence: 2
--- mutation diff ---
--- astampbot/telemetry.py
+++ bstampbot/telemetry.py
@@ -78,7 +78,7 @@
Args:
app: FastAPI application instance
"""
- if settings.otel_enabled:
+ if not settings.otel_enabled:
try:
FastAPIInstrumentor.instrument_app(app)
logger.info("FastAPI instrumented with OpenTelemetry")........................................................................ [ 34%]
........................................................................ [ 68%]
............F
=================================== FAILURES ===================================
_______________________ test_instrument_fastapi_success ________________________
def test_instrument_fastapi_success():
"""Test instrument_fastapi instruments app when OTEL is enabled."""
with (
patch("stampbot.telemetry.settings") as mock_settings,
patch("stampbot.telemetry.FastAPIInstrumentor") as mock_instrumentor,
):
mock_settings.otel_enabled = True
from stampbot.telemetry import instrument_fastapi
mock_app = Mock()
instrument_fastapi(mock_app)
> mock_instrumentor.instrument_app.assert_called_once_with(mock_app)
tests/test_telemetry.py:176:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <MagicMock name='FastAPIInstrumentor.instrument_app' id='140059649473376'>
args = (<Mock id='140059649473040'>,), kwargs = {}
msg = "Expected 'instrument_app' to be called once. Called 0 times."
def assert_called_once_with(self, /, *args, **kwargs):
"""assert that the mock was called exactly once and that that call was
with the specified arguments."""
if not self.call_count == 1:
msg = ("Expected '%s' to be called once. Called %s times.%s"
% (self._mock_name or 'mock',
self.call_count,
self._calls_repr()))
> raise AssertionError(msg)
E AssertionError: Expected 'instrument_app' to be called once. Called 0 times.
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:996: AssertionError
=========================== short test summary info ============================
FAILED tests/test_telemetry.py::test_instrument_fastapi_success - AssertionError: Expected 'instrument_app' to be called once. Called 0 times.
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 156 passed, 3 deselected in 1.09s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/AddNot, occurrence: 3
--- mutation diff ---
--- astampbot/telemetry.py
+++ bstampbot/telemetry.py
@@ -124,7 +124,7 @@
if span:
span.set_attribute("result", "success")
"""
- if not settings.otel_enabled:
+ if not not settings.otel_enabled:
yield None
return
........................................................................ [ 34%]
........................................................................ [ 68%]
......F
=================================== FAILURES ===================================
_____________________ test_create_span_when_otel_disabled ______________________
def test_create_span_when_otel_disabled():
"""Test create_span yields None when OTEL is disabled."""
with patch("stampbot.telemetry.settings") as mock_settings:
mock_settings.otel_enabled = False
from stampbot.telemetry import create_span
with create_span("test_span", {"attr": "value"}) as span:
> assert span is None
E assert NonRecordingSpan(SpanContext(trace_id=0x00000000000000000000000000000000, span_id=0x0000000000000000, trace_flags=0x00, trace_state=[], is_remote=False)) is None
tests/test_telemetry.py:83: AssertionError
=========================== short test summary info ============================
FAILED tests/test_telemetry.py::test_create_span_when_otel_disabled - assert NonRecordingSpan(SpanContext(trace_id=0x00000000000000000000000000000000, span_id=0x0000000000000000, trace_flags=0x00, trace_state=[], is_remote=False)) is None
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 150 passed, 3 deselected in 1.03s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/AddNot, occurrence: 4
--- mutation diff ---
--- astampbot/telemetry.py
+++ bstampbot/telemetry.py
@@ -132,7 +132,7 @@
with tracer.start_as_current_span(name) as span:
try:
- if attributes:
+ if not attributes:
for key, value in attributes.items():
span.set_attribute(key, value)
yield span........................................................................ [ 34%]
........................................................................ [ 68%]
..............F
=================================== FAILURES ===================================
_______________________ test_create_span_with_exception ________________________
def test_create_span_with_exception():
"""Test create_span records exception when one is raised."""
with (
patch("stampbot.telemetry.settings") as mock_settings,
patch("stampbot.telemetry.get_tracer") as mock_get_tracer,
):
mock_settings.otel_enabled = True
mock_span = Mock()
mock_tracer = Mock()
mock_tracer.start_as_current_span.return_value.__enter__ = Mock(return_value=mock_span)
mock_tracer.start_as_current_span.return_value.__exit__ = Mock(return_value=False)
mock_get_tracer.return_value = mock_tracer
from stampbot.telemetry import create_span
test_error = ValueError("test error")
try:
> with create_span("test_span"):
^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_telemetry.py:213:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/contextlib.py:141: in __enter__
return next(self.gen)
^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
name = 'test_span', attributes = None, record_exception = True
@contextmanager
def create_span(
name: str,
attributes: dict[str, Any] | None = None,
record_exception: bool = True,
) -> Iterator[Span | None]:
"""Create a span context manager for tracing operations.
This is a convenience wrapper that handles span creation, attribute setting,
and exception recording. If OpenTelemetry is disabled, yields None and
the code block executes without tracing.
Args:
name: Name of the span
attributes: Optional attributes to set on the span
record_exception: Whether to record exceptions on the span
Yields:
The span if telemetry is enabled, None otherwise
Example:
with create_span("github.approve_pr", {"repo": "owner/repo", "pr": 123}) as span:
# do work
if span:
span.set_attribute("result", "success")
"""
if not settings.otel_enabled:
yield None
return
tracer = get_tracer("stampbot")
with tracer.start_as_current_span(name) as span:
try:
if not attributes:
> for key, value in attributes.items():
^^^^^^^^^^^^^^^^
E AttributeError: 'NoneType' object has no attribute 'items'
stampbot/telemetry.py:136: AttributeError
=========================== short test summary info ============================
FAILED tests/test_telemetry.py::test_create_span_with_exception - AttributeError: 'NoneType' object has no attribute 'items'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 158 passed, 3 deselected in 1.12s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/AddNot, occurrence: 5
--- mutation diff ---
--- astampbot/telemetry.py
+++ bstampbot/telemetry.py
@@ -137,7 +137,7 @@
span.set_attribute(key, value)
yield span
except Exception as e:
- if record_exception:
+ if not record_exception:
span.record_exception(e)
span.set_status(Status(StatusCode.ERROR, str(e)))
raise........................................................................ [ 34%]
........................................................................ [ 68%]
..............F
=================================== FAILURES ===================================
_______________________ test_create_span_with_exception ________________________
def test_create_span_with_exception():
"""Test create_span records exception when one is raised."""
with (
patch("stampbot.telemetry.settings") as mock_settings,
patch("stampbot.telemetry.get_tracer") as mock_get_tracer,
):
mock_settings.otel_enabled = True
mock_span = Mock()
mock_tracer = Mock()
mock_tracer.start_as_current_span.return_value.__enter__ = Mock(return_value=mock_span)
mock_tracer.start_as_current_span.return_value.__exit__ = Mock(return_value=False)
mock_get_tracer.return_value = mock_tracer
from stampbot.telemetry import create_span
test_error = ValueError("test error")
try:
with create_span("test_span"):
raise test_error
except ValueError:
pass
> mock_span.record_exception.assert_called_once_with(test_error)
tests/test_telemetry.py:218:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <Mock name='mock.record_exception' id='140241814366464'>
args = (ValueError('test error'),), kwargs = {}
msg = "Expected 'record_exception' to be called once. Called 0 times."
def assert_called_once_with(self, /, *args, **kwargs):
"""assert that the mock was called exactly once and that that call was
with the specified arguments."""
if not self.call_count == 1:
msg = ("Expected '%s' to be called once. Called %s times.%s"
% (self._mock_name or 'mock',
self.call_count,
self._calls_repr()))
> raise AssertionError(msg)
E AssertionError: Expected 'record_exception' to be called once. Called 0 times.
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:996: AssertionError
=========================== short test summary info ============================
FAILED tests/test_telemetry.py::test_create_span_with_exception - AssertionError: Expected 'record_exception' to be called once. Called 0 times.
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 158 passed, 3 deselected in 1.09s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/AddNot, occurrence: 6
--- mutation diff ---
--- astampbot/telemetry.py
+++ bstampbot/telemetry.py
@@ -150,7 +150,7 @@
span: The span to update (can be None if telemetry disabled)
error: The exception that occurred
"""
- if span:
+ if not span:
span.record_exception(error)
span.set_status(Status(StatusCode.ERROR, str(error)))
................................................F
=================================== FAILURES ===================================
________ TestGetInstallationClient.test_get_installation_client_failure ________
self = <stampbot.github_client.GitHubAppClient object at 0x7facc27d5160>
installation_id = 123456
def _get_installation_client(self, installation_id: int) -> Github:
"""Get authenticated GitHub client for an installation.
Args:
installation_id: GitHub App installation ID.
Returns:
Authenticated Github client instance with timeout and retry configured.
Raises:
github.GithubException: If token exchange fails.
"""
start_time = time.time()
with create_span(
"github.get_installation_token",
{"github.installation_id": installation_id},
) as span:
try:
> auth = self.integration.get_access_token(installation_id)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/github_client.py:155:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1175: in __call__
return self._mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1179: in _mock_call
return self._execute_mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <Mock name='GithubIntegration().get_access_token' id='140379955150928'>
args = (123456,), kwargs = {}, effect = Exception('Token exchange failed')
def _execute_mock_call(self, /, *args, **kwargs):
# separate from _increment_mock_call so that awaited functions are
# executed separately from their call, also AsyncMock overrides this method
effect = self.side_effect
if effect is not None:
if _is_exception(effect):
> raise effect
E Exception: Token exchange failed
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1234: Exception
During handling of the above exception, another exception occurred:
self = <tests.test_github_client.TestGetInstallationClient object at 0x7facc5584190>
def test_get_installation_client_failure(self):
"""Test installation client creation failure."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_integration.get_access_token.side_effect = Exception("Token exchange failed")
mock_integration_cls.return_value = mock_integration
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
with pytest.raises(Exception, match="Token exchange failed"):
> client._get_installation_client(123456)
tests/test_github_client.py:240:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
stampbot/github_client.py:182: in _get_installation_client
set_span_error(span, e)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
span = None, error = Exception('Token exchange failed')
def set_span_error(span: Span | None, error: Exception) -> None:
"""Set error status on a span.
Args:
span: The span to update (can be None if telemetry disabled)
error: The exception that occurred
"""
if not span:
> span.record_exception(error)
^^^^^^^^^^^^^^^^^^^^^
E AttributeError: 'NoneType' object has no attribute 'record_exception'
stampbot/telemetry.py:154: AttributeError
During handling of the above exception, another exception occurred:
self = <tests.test_github_client.TestGetInstallationClient object at 0x7facc5584190>
def test_get_installation_client_failure(self):
"""Test installation client creation failure."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_integration.get_access_token.side_effect = Exception("Token exchange failed")
mock_integration_cls.return_value = mock_integration
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
> with pytest.raises(Exception, match="Token exchange failed"):
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E AssertionError: Regex pattern did not match.
E Expected regex: 'Token exchange failed'
E Actual message: "'NoneType' object has no attribute 'record_exception'"
tests/test_github_client.py:239: AssertionError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetInstallationClient::test_get_installation_client_failure - AssertionError: Regex pattern did not match.
Expected regex: 'Token exchange failed'
Actual message: "'NoneType' object has no attribute 'record_exception'"
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 48 passed, 3 deselected in 0.85s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/AddNot, occurrence: 7
--- mutation diff ---
--- astampbot/telemetry.py
+++ bstampbot/telemetry.py
@@ -161,7 +161,7 @@
Args:
span: The span to update (can be None if telemetry disabled)
"""
- if span:
+ if not span:
span.set_status(Status(StatusCode.OK))
...............................................F
=================================== FAILURES ===================================
________ TestGetInstallationClient.test_get_installation_client_success ________
self = <tests.test_github_client.TestGetInstallationClient object at 0x7fc2db13c050>
def test_get_installation_client_success(self):
"""Test successful installation client creation."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
> result = client._get_installation_client(123456)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_github_client.py:211:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
stampbot/github_client.py:175: in _get_installation_client
set_span_ok(span)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
span = None
def set_span_ok(span: Span | None) -> None:
"""Set OK status on a span.
Args:
span: The span to update (can be None if telemetry disabled)
"""
if not span:
> span.set_status(Status(StatusCode.OK))
^^^^^^^^^^^^^^^
E AttributeError: 'NoneType' object has no attribute 'set_status'
stampbot/telemetry.py:165: AttributeError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetInstallationClient::test_get_installation_client_success - AttributeError: 'NoneType' object has no attribute 'set_status'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 47 passed, 3 deselected in 0.77s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/AddNot, occurrence: 8
--- mutation diff ---
--- astampbot/telemetry.py
+++ bstampbot/telemetry.py
@@ -172,7 +172,7 @@
span: The span to update (can be None if telemetry disabled)
attributes: Attributes to add
"""
- if span:
+ if not span:
for key, value in attributes.items():
span.set_attribute(key, value)
...................................................F
=================================== FAILURES ===================================
____________________ TestApprovePR.test_approve_pr_success _____________________
self = <tests.test_github_client.TestApprovePR object at 0x7fbe22b5c550>
def test_approve_pr_success(self):
"""Test successful PR approval."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_pr = Mock()
mock_repo = Mock()
mock_repo.get_pull.return_value = mock_pr
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
result = client.approve_pr(123456, "owner/repo", 42)
> assert result is True
E assert False is True
tests/test_github_client.py:313: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-05-16 20:03:14 [error ] Failed to approve PR #42 in owner/repo: 'NoneType' object has no attribute 'set_attribute' extra={'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': "'NoneType' object has no attribute 'set_attribute'"}
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestApprovePR::test_approve_pr_success - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 51 passed, 3 deselected in 0.75s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceTrueWithFalse, occurrence: 0
--- mutation diff ---
--- astampbot/telemetry.py
+++ bstampbot/telemetry.py
@@ -50,7 +50,7 @@
# Create OTLP exporter
otlp_exporter = OTLPSpanExporter(
endpoint=settings.otel_endpoint,
- insecure=True, # Use TLS in production
+ insecure=False, # Use TLS in production
)
# Add span processor........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.64s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceTrueWithFalse, occurrence: 1
--- mutation diff ---
--- astampbot/telemetry.py
+++ bstampbot/telemetry.py
@@ -102,7 +102,7 @@
def create_span(
name: str,
attributes: dict[str, Any] | None = None,
- record_exception: bool = True,
+ record_exception: bool = False,
) -> Iterator[Span | None]:
"""Create a span context manager for tracing operations.
........................................................................ [ 34%]
........................................................................ [ 68%]
..............F
=================================== FAILURES ===================================
_______________________ test_create_span_with_exception ________________________
def test_create_span_with_exception():
"""Test create_span records exception when one is raised."""
with (
patch("stampbot.telemetry.settings") as mock_settings,
patch("stampbot.telemetry.get_tracer") as mock_get_tracer,
):
mock_settings.otel_enabled = True
mock_span = Mock()
mock_tracer = Mock()
mock_tracer.start_as_current_span.return_value.__enter__ = Mock(return_value=mock_span)
mock_tracer.start_as_current_span.return_value.__exit__ = Mock(return_value=False)
mock_get_tracer.return_value = mock_tracer
from stampbot.telemetry import create_span
test_error = ValueError("test error")
try:
with create_span("test_span"):
raise test_error
except ValueError:
pass
> mock_span.record_exception.assert_called_once_with(test_error)
tests/test_telemetry.py:218:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <Mock name='mock.record_exception' id='140256745138432'>
args = (ValueError('test error'),), kwargs = {}
msg = "Expected 'record_exception' to be called once. Called 0 times."
def assert_called_once_with(self, /, *args, **kwargs):
"""assert that the mock was called exactly once and that that call was
with the specified arguments."""
if not self.call_count == 1:
msg = ("Expected '%s' to be called once. Called %s times.%s"
% (self._mock_name or 'mock',
self.call_count,
self._calls_repr()))
> raise AssertionError(msg)
E AssertionError: Expected 'record_exception' to be called once. Called 0 times.
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:996: AssertionError
=========================== short test summary info ============================
FAILED tests/test_telemetry.py::test_create_span_with_exception - AssertionError: Expected 'record_exception' to be called once. Called 0 times.
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 158 passed, 3 deselected in 1.13s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ExceptionReplacer, occurrence: 0
--- mutation diff ---
--- astampbot/telemetry.py
+++ bstampbot/telemetry.py
@@ -67,7 +67,7 @@
return provider
- except Exception as e:
+ except CosmicRayTestingException as e:
logger.error("Failed to configure OpenTelemetry: %s", e, extra={"error": str(e)})
return None
........................................................................ [ 34%]
........................................................................ [ 68%]
...........F
=================================== FAILURES ===================================
______________________ test_configure_telemetry_exception ______________________
def configure_telemetry() -> TracerProvider | None:
"""Configure OpenTelemetry if enabled.
Returns:
TracerProvider if telemetry is enabled, None otherwise
"""
if not settings.otel_enabled:
logger.info("OpenTelemetry disabled")
return None
if not settings.otel_endpoint:
logger.warning("OpenTelemetry enabled but no endpoint configured")
return None
try:
# Create resource
> resource = Resource.create(
{
"service.name": settings.otel_service_name,
"service.version": "0.1.0",
}
)
stampbot/telemetry.py:40:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1175: in __call__
return self._mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1179: in _mock_call
return self._execute_mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <MagicMock name='Resource.create' id='140152859706096'>
args = ({'service.name': <MagicMock name='settings.otel_service_name' id='140152859707440'>, 'service.version': '0.1.0'},)
kwargs = {}, effect = Exception('Connection failed')
def _execute_mock_call(self, /, *args, **kwargs):
# separate from _increment_mock_call so that awaited functions are
# executed separately from their call, also AsyncMock overrides this method
effect = self.side_effect
if effect is not None:
if _is_exception(effect):
> raise effect
E Exception: Connection failed
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1234: Exception
During handling of the above exception, another exception occurred:
def test_configure_telemetry_exception():
"""Test configure_telemetry handles exceptions gracefully."""
with (
patch("stampbot.telemetry.settings") as mock_settings,
patch("stampbot.telemetry.Resource") as mock_resource,
):
mock_settings.otel_enabled = True
mock_settings.otel_endpoint = "http://localhost:4317"
mock_resource.create.side_effect = Exception("Connection failed")
from stampbot.telemetry import configure_telemetry
> result = configure_telemetry()
^^^^^^^^^^^^^^^^^^^^^
tests/test_telemetry.py:159:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
def configure_telemetry() -> TracerProvider | None:
"""Configure OpenTelemetry if enabled.
Returns:
TracerProvider if telemetry is enabled, None otherwise
"""
if not settings.otel_enabled:
logger.info("OpenTelemetry disabled")
return None
if not settings.otel_endpoint:
logger.warning("OpenTelemetry enabled but no endpoint configured")
return None
try:
# Create resource
resource = Resource.create(
{
"service.name": settings.otel_service_name,
"service.version": "0.1.0",
}
)
# Create tracer provider
provider = TracerProvider(resource=resource)
# Create OTLP exporter
otlp_exporter = OTLPSpanExporter(
endpoint=settings.otel_endpoint,
insecure=True, # Use TLS in production
)
# Add span processor
provider.add_span_processor(BatchSpanProcessor(otlp_exporter))
# Set as global tracer provider
trace.set_tracer_provider(provider)
logger.info(
"OpenTelemetry configured with endpoint: %s",
settings.otel_endpoint,
extra={"otel_endpoint": settings.otel_endpoint},
)
return provider
> except CosmicRayTestingException as e:
^^^^^^^^^^^^^^^^^^^^^^^^^
E NameError: name 'CosmicRayTestingException' is not defined
stampbot/telemetry.py:70: NameError
=========================== short test summary info ============================
FAILED tests/test_telemetry.py::test_configure_telemetry_exception - NameError: name 'CosmicRayTestingException' is not defined
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 155 passed, 3 deselected in 1.20s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ExceptionReplacer, occurrence: 1
--- mutation diff ---
--- astampbot/telemetry.py
+++ bstampbot/telemetry.py
@@ -82,7 +82,7 @@
try:
FastAPIInstrumentor.instrument_app(app)
logger.info("FastAPI instrumented with OpenTelemetry")
- except Exception as e:
+ except CosmicRayTestingException as e:
logger.error("Failed to instrument FastAPI: %s", e, extra={"error": str(e)})
........................................................................ [ 34%]
........................................................................ [ 68%]
.............F
=================================== FAILURES ===================================
______________________ test_instrument_fastapi_exception _______________________
app = <Mock id='140491269585088'>
def instrument_fastapi(app: Any) -> None:
"""Instrument FastAPI app with OpenTelemetry.
Args:
app: FastAPI application instance
"""
if settings.otel_enabled:
try:
> FastAPIInstrumentor.instrument_app(app)
stampbot/telemetry.py:83:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1175: in __call__
return self._mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1179: in _mock_call
return self._execute_mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <MagicMock name='FastAPIInstrumentor.instrument_app' id='140491269585760'>
args = (<Mock id='140491269585088'>,), kwargs = {}
effect = Exception('Instrumentation failed')
def _execute_mock_call(self, /, *args, **kwargs):
# separate from _increment_mock_call so that awaited functions are
# executed separately from their call, also AsyncMock overrides this method
effect = self.side_effect
if effect is not None:
if _is_exception(effect):
> raise effect
E Exception: Instrumentation failed
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1234: Exception
During handling of the above exception, another exception occurred:
def test_instrument_fastapi_exception():
"""Test instrument_fastapi handles exceptions gracefully."""
with (
patch("stampbot.telemetry.settings") as mock_settings,
patch("stampbot.telemetry.FastAPIInstrumentor") as mock_instrumentor,
):
mock_settings.otel_enabled = True
mock_instrumentor.instrument_app.side_effect = Exception("Instrumentation failed")
from stampbot.telemetry import instrument_fastapi
mock_app = Mock()
# Should not raise
> instrument_fastapi(mock_app)
tests/test_telemetry.py:192:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
app = <Mock id='140491269585088'>
def instrument_fastapi(app: Any) -> None:
"""Instrument FastAPI app with OpenTelemetry.
Args:
app: FastAPI application instance
"""
if settings.otel_enabled:
try:
FastAPIInstrumentor.instrument_app(app)
logger.info("FastAPI instrumented with OpenTelemetry")
> except CosmicRayTestingException as e:
^^^^^^^^^^^^^^^^^^^^^^^^^
E NameError: name 'CosmicRayTestingException' is not defined
stampbot/telemetry.py:85: NameError
=========================== short test summary info ============================
FAILED tests/test_telemetry.py::test_instrument_fastapi_exception - NameError: name 'CosmicRayTestingException' is not defined
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 157 passed, 3 deselected in 1.16s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ExceptionReplacer, occurrence: 2
--- mutation diff ---
--- astampbot/telemetry.py
+++ bstampbot/telemetry.py
@@ -136,7 +136,7 @@
for key, value in attributes.items():
span.set_attribute(key, value)
yield span
- except Exception as e:
+ except CosmicRayTestingException as e:
if record_exception:
span.record_exception(e)
span.set_status(Status(StatusCode.ERROR, str(e)))........................................................................ [ 34%]
........................................................................ [ 68%]
..............F
=================================== FAILURES ===================================
_______________________ test_create_span_with_exception ________________________
def test_create_span_with_exception():
"""Test create_span records exception when one is raised."""
with (
patch("stampbot.telemetry.settings") as mock_settings,
patch("stampbot.telemetry.get_tracer") as mock_get_tracer,
):
mock_settings.otel_enabled = True
mock_span = Mock()
mock_tracer = Mock()
mock_tracer.start_as_current_span.return_value.__enter__ = Mock(return_value=mock_span)
mock_tracer.start_as_current_span.return_value.__exit__ = Mock(return_value=False)
mock_get_tracer.return_value = mock_tracer
from stampbot.telemetry import create_span
test_error = ValueError("test error")
try:
with create_span("test_span"):
> raise test_error
E ValueError: test error
tests/test_telemetry.py:214: ValueError
During handling of the above exception, another exception occurred:
def test_create_span_with_exception():
"""Test create_span records exception when one is raised."""
with (
patch("stampbot.telemetry.settings") as mock_settings,
patch("stampbot.telemetry.get_tracer") as mock_get_tracer,
):
mock_settings.otel_enabled = True
mock_span = Mock()
mock_tracer = Mock()
mock_tracer.start_as_current_span.return_value.__enter__ = Mock(return_value=mock_span)
mock_tracer.start_as_current_span.return_value.__exit__ = Mock(return_value=False)
mock_get_tracer.return_value = mock_tracer
from stampbot.telemetry import create_span
test_error = ValueError("test error")
try:
> with create_span("test_span"):
^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_telemetry.py:213:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/contextlib.py:162: in __exit__
self.gen.throw(value)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
name = 'test_span', attributes = None, record_exception = True
@contextmanager
def create_span(
name: str,
attributes: dict[str, Any] | None = None,
record_exception: bool = True,
) -> Iterator[Span | None]:
"""Create a span context manager for tracing operations.
This is a convenience wrapper that handles span creation, attribute setting,
and exception recording. If OpenTelemetry is disabled, yields None and
the code block executes without tracing.
Args:
name: Name of the span
attributes: Optional attributes to set on the span
record_exception: Whether to record exceptions on the span
Yields:
The span if telemetry is enabled, None otherwise
Example:
with create_span("github.approve_pr", {"repo": "owner/repo", "pr": 123}) as span:
# do work
if span:
span.set_attribute("result", "success")
"""
if not settings.otel_enabled:
yield None
return
tracer = get_tracer("stampbot")
with tracer.start_as_current_span(name) as span:
try:
if attributes:
for key, value in attributes.items():
span.set_attribute(key, value)
yield span
> except CosmicRayTestingException as e:
^^^^^^^^^^^^^^^^^^^^^^^^^
E NameError: name 'CosmicRayTestingException' is not defined
stampbot/telemetry.py:139: NameError
=========================== short test summary info ============================
FAILED tests/test_telemetry.py::test_create_span_with_exception - NameError: name 'CosmicRayTestingException' is not defined
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 158 passed, 3 deselected in 1.11s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/RemoveDecorator, occurrence: 0
--- mutation diff ---
--- astampbot/telemetry.py
+++ bstampbot/telemetry.py
@@ -96,9 +96,6 @@
Tracer instance (no-op if telemetry disabled)
"""
return trace.get_tracer(name)
-
-
-@contextmanager
def create_span(
name: str,
attributes: dict[str, Any] | None = None,........................................................................ [ 34%]
.............................F
=================================== FAILURES ===================================
___________________________ test_webhook_valid_ping ____________________________
test_client = <starlette.testclient.TestClient object at 0x7fca97dbe9c0>
def test_webhook_valid_ping(test_client: TestClient):
"""Test webhook accepts valid ping event."""
import hashlib
import hmac
from stampbot.webhook_handler import webhook_handler
body = json.dumps({"zen": "Design for failure."}).encode()
signature = (
"sha256="
+ hmac.new(
webhook_handler.webhook_secret,
body,
hashlib.sha256,
).hexdigest()
)
response = test_client.post(
"/webhook",
content=body,
headers={
"Content-Type": "application/json",
"X-GitHub-Event": "ping",
"X-Hub-Signature-256": signature,
},
)
> assert response.status_code == 200
E assert 500 == 200
E + where 500 = <Response [500 Internal Server Error]>.status_code
tests/test_main.py:175: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:35:00.206506Z[0m [[31m[1merror [0m] [1mError handling webhook event: 'generator' object does not support the context manager protocol (missed __exit__ method)[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.main (INFO)>[0m [36m_name[0m=[35merror[0m [36mclient_ip[0m=[35mtestclient[0m [36mextra[0m=[35m{'error': "'generator' object does not support the context manager protocol (missed __exit__ method)"}[0m
[2m2026-05-16T19:35:00.207336Z[0m [[32m[1minfo [0m] [1mHTTP Request: POST http://testserver/webhook "HTTP/1.1 500 Internal Server Error"[0m
------------------------------ Captured log call -------------------------------
ERROR stampbot.main:main.py:326 {'extra': {'error': "'generator' object does not support the context manager protocol (missed __exit__ method)"}, 'event': "Error handling webhook event: 'generator' object does not support the context manager protocol (missed __exit__ method)", 'client_ip': 'testclient', 'level': 'error', 'timestamp': '2026-05-16T19:35:00.206506Z'}
INFO httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 500 Internal Server Error"
=========================== short test summary info ============================
FAILED tests/test_main.py::test_webhook_valid_ping - assert 500 == 200
+ where 500 = <Response [500 Internal Server Error]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 101 passed, 3 deselected in 0.84s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ZeroIterationForLoop, occurrence: 0
--- mutation diff ---
--- astampbot/telemetry.py
+++ bstampbot/telemetry.py
@@ -133,7 +133,7 @@
with tracer.start_as_current_span(name) as span:
try:
if attributes:
- for key, value in attributes.items():
+ for key, value in []:
span.set_attribute(key, value)
yield span
except Exception as e:........................................................................ [ 34%]
........................................................................ [ 68%]
...............F
=================================== FAILURES ===================================
________________ test_create_span_with_attributes_when_enabled _________________
def test_create_span_with_attributes_when_enabled():
"""Test create_span sets attributes on span when OTEL is enabled."""
with (
patch("stampbot.telemetry.settings") as mock_settings,
patch("stampbot.telemetry.get_tracer") as mock_get_tracer,
):
mock_settings.otel_enabled = True
mock_span = Mock()
mock_tracer = Mock()
mock_tracer.start_as_current_span.return_value.__enter__ = Mock(return_value=mock_span)
mock_tracer.start_as_current_span.return_value.__exit__ = Mock(return_value=False)
mock_get_tracer.return_value = mock_tracer
from stampbot.telemetry import create_span
with create_span("test_span", {"key1": "value1", "key2": "value2"}) as span:
assert span == mock_span
# Verify attributes were set on the span
> assert mock_span.set_attribute.call_count == 2
E AssertionError: assert 0 == 2
E + where 0 = <Mock name='mock.set_attribute' id='139977184320144'>.call_count
E + where <Mock name='mock.set_attribute' id='139977184320144'> = <Mock id='139977184322832'>.set_attribute
tests/test_telemetry.py:242: AssertionError
=========================== short test summary info ============================
FAILED tests/test_telemetry.py::test_create_span_with_attributes_when_enabled - AssertionError: assert 0 == 2
+ where 0 = <Mock name='mock.set_attribute' id='139977184320144'>.call_count
+ where <Mock name='mock.set_attribute' id='139977184320144'> = <Mock id='139977184322832'>.set_attribute
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 159 passed, 3 deselected in 1.06s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ZeroIterationForLoop, occurrence: 1
--- mutation diff ---
--- astampbot/telemetry.py
+++ bstampbot/telemetry.py
@@ -173,6 +173,6 @@
attributes: Attributes to add
"""
if span:
- for key, value in attributes.items():
+ for key, value in []:
span.set_attribute(key, value)
........................................................................ [ 34%]
........................................................................ [ 68%]
.....F
=================================== FAILURES ===================================
______________________ test_add_span_attributes_with_span ______________________
def test_add_span_attributes_with_span():
"""Test add_span_attributes adds attributes to span."""
from stampbot.telemetry import add_span_attributes
mock_span = Mock()
attributes = {"key1": "value1", "key2": "value2"}
add_span_attributes(mock_span, attributes)
> assert mock_span.set_attribute.call_count == 2
E AssertionError: assert 0 == 2
E + where 0 = <Mock name='mock.set_attribute' id='140021004412176'>.call_count
E + where <Mock name='mock.set_attribute' id='140021004412176'> = <Mock id='140021004412512'>.set_attribute
tests/test_telemetry.py:71: AssertionError
=========================== short test summary info ============================
FAILED tests/test_telemetry.py::test_add_span_attributes_with_span - AssertionError: assert 0 == 2
+ where 0 = <Mock name='mock.set_attribute' id='140021004412176'>.call_count
+ where <Mock name='mock.set_attribute' id='140021004412176'> = <Mock id='140021004412512'>.set_attribute
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 149 passed, 3 deselected in 1.04s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/NumberReplacer, occurrence: 0
--- mutation diff ---
--- astampbot/metrics.py
+++ bstampbot/metrics.py
@@ -31,7 +31,7 @@
"stampbot_http_request_duration_seconds",
"HTTP request duration in seconds",
["method", "endpoint"],
- buckets=(0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+ buckets=( 1.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
)
http_request_size_bytes = Histogram(...................................F
=================================== FAILURES ===================================
________ TestGitHubAppClientInit.test_init_creates_uninitialized_client ________
self = <tests.test_github_client.TestGitHubAppClientInit object at 0x7f20fc8439d0>
def test_init_creates_uninitialized_client(self):
"""Test that __init__ creates an uninitialized client."""
> from stampbot.github_client import GitHubAppClient
tests/test_github_client.py:19:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
stampbot/github_client.py:15: in <module>
from stampbot.metrics import (
stampbot/metrics.py:30: in <module>
http_request_duration_seconds = Histogram(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:625: in __init__
self._prepare_buckets(buckets)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <[AttributeError("'Histogram' object has no attribute '_name'") raised in repr()] Histogram object at 0x7f20faa68c20>
source_buckets = (1.005, 0.01, 0.025, 0.05, 0.1, 0.25, ...)
def _prepare_buckets(self, source_buckets: Sequence[Union[float, str]]) -> None:
buckets = [float(b) for b in source_buckets]
if buckets != sorted(buckets):
# This is probably an error on the part of the user,
# so raise rather than sorting for them.
> raise ValueError('Buckets not in sorted order')
E ValueError: Buckets not in sorted order
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:643: ValueError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGitHubAppClientInit::test_init_creates_uninitialized_client - ValueError: Buckets not in sorted order
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 35 passed, 3 deselected in 0.70s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/NumberReplacer, occurrence: 1
--- mutation diff ---
--- astampbot/metrics.py
+++ bstampbot/metrics.py
@@ -31,7 +31,7 @@
"stampbot_http_request_duration_seconds",
"HTTP request duration in seconds",
["method", "endpoint"],
- buckets=(0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+ buckets=( -0.995, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
)
http_request_size_bytes = Histogram(........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/NumberReplacer, occurrence: 2
--- mutation diff ---
--- astampbot/metrics.py
+++ bstampbot/metrics.py
@@ -31,7 +31,7 @@
"stampbot_http_request_duration_seconds",
"HTTP request duration in seconds",
["method", "endpoint"],
- buckets=(0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+ buckets=(0.005, 1.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
)
http_request_size_bytes = Histogram(...................................F
=================================== FAILURES ===================================
________ TestGitHubAppClientInit.test_init_creates_uninitialized_client ________
self = <tests.test_github_client.TestGitHubAppClientInit object at 0x7f5a9978b9d0>
def test_init_creates_uninitialized_client(self):
"""Test that __init__ creates an uninitialized client."""
> from stampbot.github_client import GitHubAppClient
tests/test_github_client.py:19:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
stampbot/github_client.py:15: in <module>
from stampbot.metrics import (
stampbot/metrics.py:30: in <module>
http_request_duration_seconds = Histogram(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:625: in __init__
self._prepare_buckets(buckets)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <[AttributeError("'Histogram' object has no attribute '_name'") raised in repr()] Histogram object at 0x7f5a97b54c20>
source_buckets = (0.005, 1.01, 0.025, 0.05, 0.1, 0.25, ...)
def _prepare_buckets(self, source_buckets: Sequence[Union[float, str]]) -> None:
buckets = [float(b) for b in source_buckets]
if buckets != sorted(buckets):
# This is probably an error on the part of the user,
# so raise rather than sorting for them.
> raise ValueError('Buckets not in sorted order')
E ValueError: Buckets not in sorted order
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:643: ValueError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGitHubAppClientInit::test_init_creates_uninitialized_client - ValueError: Buckets not in sorted order
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 35 passed, 3 deselected in 0.66s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/NumberReplacer, occurrence: 3
--- mutation diff ---
--- astampbot/metrics.py
+++ bstampbot/metrics.py
@@ -31,7 +31,7 @@
"stampbot_http_request_duration_seconds",
"HTTP request duration in seconds",
["method", "endpoint"],
- buckets=(0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+ buckets=(0.005, -0.99, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
)
http_request_size_bytes = Histogram(...................................F
=================================== FAILURES ===================================
________ TestGitHubAppClientInit.test_init_creates_uninitialized_client ________
self = <tests.test_github_client.TestGitHubAppClientInit object at 0x7fc3c86a39d0>
def test_init_creates_uninitialized_client(self):
"""Test that __init__ creates an uninitialized client."""
> from stampbot.github_client import GitHubAppClient
tests/test_github_client.py:19:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
stampbot/github_client.py:15: in <module>
from stampbot.metrics import (
stampbot/metrics.py:30: in <module>
http_request_duration_seconds = Histogram(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:625: in __init__
self._prepare_buckets(buckets)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <[AttributeError("'Histogram' object has no attribute '_name'") raised in repr()] Histogram object at 0x7fc3c6754c20>
source_buckets = (0.005, -0.99, 0.025, 0.05, 0.1, 0.25, ...)
def _prepare_buckets(self, source_buckets: Sequence[Union[float, str]]) -> None:
buckets = [float(b) for b in source_buckets]
if buckets != sorted(buckets):
# This is probably an error on the part of the user,
# so raise rather than sorting for them.
> raise ValueError('Buckets not in sorted order')
E ValueError: Buckets not in sorted order
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:643: ValueError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGitHubAppClientInit::test_init_creates_uninitialized_client - ValueError: Buckets not in sorted order
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 35 passed, 3 deselected in 0.66s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/NumberReplacer, occurrence: 4
--- mutation diff ---
--- astampbot/metrics.py
+++ bstampbot/metrics.py
@@ -31,7 +31,7 @@
"stampbot_http_request_duration_seconds",
"HTTP request duration in seconds",
["method", "endpoint"],
- buckets=(0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+ buckets=(0.005, 0.01, 1.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
)
http_request_size_bytes = Histogram(...................................F
=================================== FAILURES ===================================
________ TestGitHubAppClientInit.test_init_creates_uninitialized_client ________
self = <tests.test_github_client.TestGitHubAppClientInit object at 0x7efd22adb9d0>
def test_init_creates_uninitialized_client(self):
"""Test that __init__ creates an uninitialized client."""
> from stampbot.github_client import GitHubAppClient
tests/test_github_client.py:19:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
stampbot/github_client.py:15: in <module>
from stampbot.metrics import (
stampbot/metrics.py:30: in <module>
http_request_duration_seconds = Histogram(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:625: in __init__
self._prepare_buckets(buckets)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <[AttributeError("'Histogram' object has no attribute '_name'") raised in repr()] Histogram object at 0x7efd20d44c20>
source_buckets = (0.005, 0.01, 1.025, 0.05, 0.1, 0.25, ...)
def _prepare_buckets(self, source_buckets: Sequence[Union[float, str]]) -> None:
buckets = [float(b) for b in source_buckets]
if buckets != sorted(buckets):
# This is probably an error on the part of the user,
# so raise rather than sorting for them.
> raise ValueError('Buckets not in sorted order')
E ValueError: Buckets not in sorted order
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:643: ValueError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGitHubAppClientInit::test_init_creates_uninitialized_client - ValueError: Buckets not in sorted order
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 35 passed, 3 deselected in 0.67s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/NumberReplacer, occurrence: 5
--- mutation diff ---
--- astampbot/metrics.py
+++ bstampbot/metrics.py
@@ -31,7 +31,7 @@
"stampbot_http_request_duration_seconds",
"HTTP request duration in seconds",
["method", "endpoint"],
- buckets=(0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+ buckets=(0.005, 0.01, -0.975, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
)
http_request_size_bytes = Histogram(...................................F
=================================== FAILURES ===================================
________ TestGitHubAppClientInit.test_init_creates_uninitialized_client ________
self = <tests.test_github_client.TestGitHubAppClientInit object at 0x7fb2ff0139d0>
def test_init_creates_uninitialized_client(self):
"""Test that __init__ creates an uninitialized client."""
> from stampbot.github_client import GitHubAppClient
tests/test_github_client.py:19:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
stampbot/github_client.py:15: in <module>
from stampbot.metrics import (
stampbot/metrics.py:30: in <module>
http_request_duration_seconds = Histogram(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:625: in __init__
self._prepare_buckets(buckets)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <[AttributeError("'Histogram' object has no attribute '_name'") raised in repr()] Histogram object at 0x7fb2fd358c20>
source_buckets = (0.005, 0.01, -0.975, 0.05, 0.1, 0.25, ...)
def _prepare_buckets(self, source_buckets: Sequence[Union[float, str]]) -> None:
buckets = [float(b) for b in source_buckets]
if buckets != sorted(buckets):
# This is probably an error on the part of the user,
# so raise rather than sorting for them.
> raise ValueError('Buckets not in sorted order')
E ValueError: Buckets not in sorted order
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:643: ValueError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGitHubAppClientInit::test_init_creates_uninitialized_client - ValueError: Buckets not in sorted order
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 35 passed, 3 deselected in 0.65s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/NumberReplacer, occurrence: 6
--- mutation diff ---
--- astampbot/metrics.py
+++ bstampbot/metrics.py
@@ -31,7 +31,7 @@
"stampbot_http_request_duration_seconds",
"HTTP request duration in seconds",
["method", "endpoint"],
- buckets=(0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+ buckets=(0.005, 0.01, 0.025, 1.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
)
http_request_size_bytes = Histogram(...................................F
=================================== FAILURES ===================================
________ TestGitHubAppClientInit.test_init_creates_uninitialized_client ________
self = <tests.test_github_client.TestGitHubAppClientInit object at 0x7faab2f6f9d0>
def test_init_creates_uninitialized_client(self):
"""Test that __init__ creates an uninitialized client."""
> from stampbot.github_client import GitHubAppClient
tests/test_github_client.py:19:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
stampbot/github_client.py:15: in <module>
from stampbot.metrics import (
stampbot/metrics.py:30: in <module>
http_request_duration_seconds = Histogram(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:625: in __init__
self._prepare_buckets(buckets)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <[AttributeError("'Histogram' object has no attribute '_name'") raised in repr()] Histogram object at 0x7faab1254c20>
source_buckets = (0.005, 0.01, 0.025, 1.05, 0.1, 0.25, ...)
def _prepare_buckets(self, source_buckets: Sequence[Union[float, str]]) -> None:
buckets = [float(b) for b in source_buckets]
if buckets != sorted(buckets):
# This is probably an error on the part of the user,
# so raise rather than sorting for them.
> raise ValueError('Buckets not in sorted order')
E ValueError: Buckets not in sorted order
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:643: ValueError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGitHubAppClientInit::test_init_creates_uninitialized_client - ValueError: Buckets not in sorted order
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 35 passed, 3 deselected in 0.66s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/NumberReplacer, occurrence: 7
--- mutation diff ---
--- astampbot/metrics.py
+++ bstampbot/metrics.py
@@ -31,7 +31,7 @@
"stampbot_http_request_duration_seconds",
"HTTP request duration in seconds",
["method", "endpoint"],
- buckets=(0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+ buckets=(0.005, 0.01, 0.025, -0.95, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
)
http_request_size_bytes = Histogram(...................................F
=================================== FAILURES ===================================
________ TestGitHubAppClientInit.test_init_creates_uninitialized_client ________
self = <tests.test_github_client.TestGitHubAppClientInit object at 0x7f518f5539d0>
def test_init_creates_uninitialized_client(self):
"""Test that __init__ creates an uninitialized client."""
> from stampbot.github_client import GitHubAppClient
tests/test_github_client.py:19:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
stampbot/github_client.py:15: in <module>
from stampbot.metrics import (
stampbot/metrics.py:30: in <module>
http_request_duration_seconds = Histogram(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:625: in __init__
self._prepare_buckets(buckets)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <[AttributeError("'Histogram' object has no attribute '_name'") raised in repr()] Histogram object at 0x7f518d758c20>
source_buckets = (0.005, 0.01, 0.025, -0.95, 0.1, 0.25, ...)
def _prepare_buckets(self, source_buckets: Sequence[Union[float, str]]) -> None:
buckets = [float(b) for b in source_buckets]
if buckets != sorted(buckets):
# This is probably an error on the part of the user,
# so raise rather than sorting for them.
> raise ValueError('Buckets not in sorted order')
E ValueError: Buckets not in sorted order
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:643: ValueError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGitHubAppClientInit::test_init_creates_uninitialized_client - ValueError: Buckets not in sorted order
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 35 passed, 3 deselected in 0.69s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/NumberReplacer, occurrence: 8
--- mutation diff ---
--- astampbot/metrics.py
+++ bstampbot/metrics.py
@@ -31,7 +31,7 @@
"stampbot_http_request_duration_seconds",
"HTTP request duration in seconds",
["method", "endpoint"],
- buckets=(0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+ buckets=(0.005, 0.01, 0.025, 0.05, 1.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
)
http_request_size_bytes = Histogram(...................................F
=================================== FAILURES ===================================
________ TestGitHubAppClientInit.test_init_creates_uninitialized_client ________
self = <tests.test_github_client.TestGitHubAppClientInit object at 0x7f439b97b9d0>
def test_init_creates_uninitialized_client(self):
"""Test that __init__ creates an uninitialized client."""
> from stampbot.github_client import GitHubAppClient
tests/test_github_client.py:19:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
stampbot/github_client.py:15: in <module>
from stampbot.metrics import (
stampbot/metrics.py:30: in <module>
http_request_duration_seconds = Histogram(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:625: in __init__
self._prepare_buckets(buckets)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <[AttributeError("'Histogram' object has no attribute '_name'") raised in repr()] Histogram object at 0x7f4399c44c20>
source_buckets = (0.005, 0.01, 0.025, 0.05, 1.1, 0.25, ...)
def _prepare_buckets(self, source_buckets: Sequence[Union[float, str]]) -> None:
buckets = [float(b) for b in source_buckets]
if buckets != sorted(buckets):
# This is probably an error on the part of the user,
# so raise rather than sorting for them.
> raise ValueError('Buckets not in sorted order')
E ValueError: Buckets not in sorted order
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:643: ValueError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGitHubAppClientInit::test_init_creates_uninitialized_client - ValueError: Buckets not in sorted order
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 35 passed, 3 deselected in 0.67s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/NumberReplacer, occurrence: 9
--- mutation diff ---
--- astampbot/metrics.py
+++ bstampbot/metrics.py
@@ -31,7 +31,7 @@
"stampbot_http_request_duration_seconds",
"HTTP request duration in seconds",
["method", "endpoint"],
- buckets=(0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+ buckets=(0.005, 0.01, 0.025, 0.05, -0.9, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
)
http_request_size_bytes = Histogram(...................................F
=================================== FAILURES ===================================
________ TestGitHubAppClientInit.test_init_creates_uninitialized_client ________
self = <tests.test_github_client.TestGitHubAppClientInit object at 0x7fd4ca8479d0>
def test_init_creates_uninitialized_client(self):
"""Test that __init__ creates an uninitialized client."""
> from stampbot.github_client import GitHubAppClient
tests/test_github_client.py:19:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
stampbot/github_client.py:15: in <module>
from stampbot.metrics import (
stampbot/metrics.py:30: in <module>
http_request_duration_seconds = Histogram(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:625: in __init__
self._prepare_buckets(buckets)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <[AttributeError("'Histogram' object has no attribute '_name'") raised in repr()] Histogram object at 0x7fd4c8b54c20>
source_buckets = (0.005, 0.01, 0.025, 0.05, -0.9, 0.25, ...)
def _prepare_buckets(self, source_buckets: Sequence[Union[float, str]]) -> None:
buckets = [float(b) for b in source_buckets]
if buckets != sorted(buckets):
# This is probably an error on the part of the user,
# so raise rather than sorting for them.
> raise ValueError('Buckets not in sorted order')
E ValueError: Buckets not in sorted order
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:643: ValueError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGitHubAppClientInit::test_init_creates_uninitialized_client - ValueError: Buckets not in sorted order
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 35 passed, 3 deselected in 0.68s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/NumberReplacer, occurrence: 10
--- mutation diff ---
--- astampbot/metrics.py
+++ bstampbot/metrics.py
@@ -31,7 +31,7 @@
"stampbot_http_request_duration_seconds",
"HTTP request duration in seconds",
["method", "endpoint"],
- buckets=(0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+ buckets=(0.005, 0.01, 0.025, 0.05, 0.1, 1.25, 0.5, 1.0, 2.5, 5.0, 10.0),
)
http_request_size_bytes = Histogram(...................................F
=================================== FAILURES ===================================
________ TestGitHubAppClientInit.test_init_creates_uninitialized_client ________
self = <tests.test_github_client.TestGitHubAppClientInit object at 0x7fd0ab99f9d0>
def test_init_creates_uninitialized_client(self):
"""Test that __init__ creates an uninitialized client."""
> from stampbot.github_client import GitHubAppClient
tests/test_github_client.py:19:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
stampbot/github_client.py:15: in <module>
from stampbot.metrics import (
stampbot/metrics.py:30: in <module>
http_request_duration_seconds = Histogram(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:625: in __init__
self._prepare_buckets(buckets)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <[AttributeError("'Histogram' object has no attribute '_name'") raised in repr()] Histogram object at 0x7fd0a9d50c20>
source_buckets = (0.005, 0.01, 0.025, 0.05, 0.1, 1.25, ...)
def _prepare_buckets(self, source_buckets: Sequence[Union[float, str]]) -> None:
buckets = [float(b) for b in source_buckets]
if buckets != sorted(buckets):
# This is probably an error on the part of the user,
# so raise rather than sorting for them.
> raise ValueError('Buckets not in sorted order')
E ValueError: Buckets not in sorted order
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:643: ValueError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGitHubAppClientInit::test_init_creates_uninitialized_client - ValueError: Buckets not in sorted order
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 35 passed, 3 deselected in 0.67s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/NumberReplacer, occurrence: 11
--- mutation diff ---
--- astampbot/metrics.py
+++ bstampbot/metrics.py
@@ -31,7 +31,7 @@
"stampbot_http_request_duration_seconds",
"HTTP request duration in seconds",
["method", "endpoint"],
- buckets=(0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+ buckets=(0.005, 0.01, 0.025, 0.05, 0.1, -0.75, 0.5, 1.0, 2.5, 5.0, 10.0),
)
http_request_size_bytes = Histogram(...................................F
=================================== FAILURES ===================================
________ TestGitHubAppClientInit.test_init_creates_uninitialized_client ________
self = <tests.test_github_client.TestGitHubAppClientInit object at 0x7ff1e21479d0>
def test_init_creates_uninitialized_client(self):
"""Test that __init__ creates an uninitialized client."""
> from stampbot.github_client import GitHubAppClient
tests/test_github_client.py:19:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
stampbot/github_client.py:15: in <module>
from stampbot.metrics import (
stampbot/metrics.py:30: in <module>
http_request_duration_seconds = Histogram(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:625: in __init__
self._prepare_buckets(buckets)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <[AttributeError("'Histogram' object has no attribute '_name'") raised in repr()] Histogram object at 0x7ff1e0440c20>
source_buckets = (0.005, 0.01, 0.025, 0.05, 0.1, -0.75, ...)
def _prepare_buckets(self, source_buckets: Sequence[Union[float, str]]) -> None:
buckets = [float(b) for b in source_buckets]
if buckets != sorted(buckets):
# This is probably an error on the part of the user,
# so raise rather than sorting for them.
> raise ValueError('Buckets not in sorted order')
E ValueError: Buckets not in sorted order
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:643: ValueError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGitHubAppClientInit::test_init_creates_uninitialized_client - ValueError: Buckets not in sorted order
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 35 passed, 3 deselected in 0.66s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/NumberReplacer, occurrence: 12
--- mutation diff ---
--- astampbot/metrics.py
+++ bstampbot/metrics.py
@@ -31,7 +31,7 @@
"stampbot_http_request_duration_seconds",
"HTTP request duration in seconds",
["method", "endpoint"],
- buckets=(0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+ buckets=(0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 1.5, 1.0, 2.5, 5.0, 10.0),
)
http_request_size_bytes = Histogram(...................................F
=================================== FAILURES ===================================
________ TestGitHubAppClientInit.test_init_creates_uninitialized_client ________
self = <tests.test_github_client.TestGitHubAppClientInit object at 0x7fbbdf5cb9d0>
def test_init_creates_uninitialized_client(self):
"""Test that __init__ creates an uninitialized client."""
> from stampbot.github_client import GitHubAppClient
tests/test_github_client.py:19:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
stampbot/github_client.py:15: in <module>
from stampbot.metrics import (
stampbot/metrics.py:30: in <module>
http_request_duration_seconds = Histogram(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:625: in __init__
self._prepare_buckets(buckets)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <[AttributeError("'Histogram' object has no attribute '_name'") raised in repr()] Histogram object at 0x7fbbdd858c20>
source_buckets = (0.005, 0.01, 0.025, 0.05, 0.1, 0.25, ...)
def _prepare_buckets(self, source_buckets: Sequence[Union[float, str]]) -> None:
buckets = [float(b) for b in source_buckets]
if buckets != sorted(buckets):
# This is probably an error on the part of the user,
# so raise rather than sorting for them.
> raise ValueError('Buckets not in sorted order')
E ValueError: Buckets not in sorted order
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:643: ValueError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGitHubAppClientInit::test_init_creates_uninitialized_client - ValueError: Buckets not in sorted order
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 35 passed, 3 deselected in 0.69s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/NumberReplacer, occurrence: 13
--- mutation diff ---
--- astampbot/metrics.py
+++ bstampbot/metrics.py
@@ -31,7 +31,7 @@
"stampbot_http_request_duration_seconds",
"HTTP request duration in seconds",
["method", "endpoint"],
- buckets=(0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+ buckets=(0.005, 0.01, 0.025, 0.05, 0.1, 0.25, -0.5, 1.0, 2.5, 5.0, 10.0),
)
http_request_size_bytes = Histogram(...................................F
=================================== FAILURES ===================================
________ TestGitHubAppClientInit.test_init_creates_uninitialized_client ________
self = <tests.test_github_client.TestGitHubAppClientInit object at 0x7fd3540e79d0>
def test_init_creates_uninitialized_client(self):
"""Test that __init__ creates an uninitialized client."""
> from stampbot.github_client import GitHubAppClient
tests/test_github_client.py:19:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
stampbot/github_client.py:15: in <module>
from stampbot.metrics import (
stampbot/metrics.py:30: in <module>
http_request_duration_seconds = Histogram(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:625: in __init__
self._prepare_buckets(buckets)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <[AttributeError("'Histogram' object has no attribute '_name'") raised in repr()] Histogram object at 0x7fd352354c20>
source_buckets = (0.005, 0.01, 0.025, 0.05, 0.1, 0.25, ...)
def _prepare_buckets(self, source_buckets: Sequence[Union[float, str]]) -> None:
buckets = [float(b) for b in source_buckets]
if buckets != sorted(buckets):
# This is probably an error on the part of the user,
# so raise rather than sorting for them.
> raise ValueError('Buckets not in sorted order')
E ValueError: Buckets not in sorted order
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:643: ValueError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGitHubAppClientInit::test_init_creates_uninitialized_client - ValueError: Buckets not in sorted order
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 35 passed, 3 deselected in 0.70s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/NumberReplacer, occurrence: 14
--- mutation diff ---
--- astampbot/metrics.py
+++ bstampbot/metrics.py
@@ -31,7 +31,7 @@
"stampbot_http_request_duration_seconds",
"HTTP request duration in seconds",
["method", "endpoint"],
- buckets=(0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+ buckets=(0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 2.0, 2.5, 5.0, 10.0),
)
http_request_size_bytes = Histogram(........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.69s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/NumberReplacer, occurrence: 15
--- mutation diff ---
--- astampbot/metrics.py
+++ bstampbot/metrics.py
@@ -31,7 +31,7 @@
"stampbot_http_request_duration_seconds",
"HTTP request duration in seconds",
["method", "endpoint"],
- buckets=(0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+ buckets=(0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 0.0, 2.5, 5.0, 10.0),
)
http_request_size_bytes = Histogram(...................................F
=================================== FAILURES ===================================
________ TestGitHubAppClientInit.test_init_creates_uninitialized_client ________
self = <tests.test_github_client.TestGitHubAppClientInit object at 0x7f58b4b5f9d0>
def test_init_creates_uninitialized_client(self):
"""Test that __init__ creates an uninitialized client."""
> from stampbot.github_client import GitHubAppClient
tests/test_github_client.py:19:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
stampbot/github_client.py:15: in <module>
from stampbot.metrics import (
stampbot/metrics.py:30: in <module>
http_request_duration_seconds = Histogram(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:625: in __init__
self._prepare_buckets(buckets)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <[AttributeError("'Histogram' object has no attribute '_name'") raised in repr()] Histogram object at 0x7f58b2e60c20>
source_buckets = (0.005, 0.01, 0.025, 0.05, 0.1, 0.25, ...)
def _prepare_buckets(self, source_buckets: Sequence[Union[float, str]]) -> None:
buckets = [float(b) for b in source_buckets]
if buckets != sorted(buckets):
# This is probably an error on the part of the user,
# so raise rather than sorting for them.
> raise ValueError('Buckets not in sorted order')
E ValueError: Buckets not in sorted order
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:643: ValueError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGitHubAppClientInit::test_init_creates_uninitialized_client - ValueError: Buckets not in sorted order
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 35 passed, 3 deselected in 0.68s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/NumberReplacer, occurrence: 16
--- mutation diff ---
--- astampbot/metrics.py
+++ bstampbot/metrics.py
@@ -31,7 +31,7 @@
"stampbot_http_request_duration_seconds",
"HTTP request duration in seconds",
["method", "endpoint"],
- buckets=(0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+ buckets=(0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 3.5, 5.0, 10.0),
)
http_request_size_bytes = Histogram(........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.62s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/NumberReplacer, occurrence: 17
--- mutation diff ---
--- astampbot/metrics.py
+++ bstampbot/metrics.py
@@ -31,7 +31,7 @@
"stampbot_http_request_duration_seconds",
"HTTP request duration in seconds",
["method", "endpoint"],
- buckets=(0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+ buckets=(0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 1.5, 5.0, 10.0),
)
http_request_size_bytes = Histogram(........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.63s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/NumberReplacer, occurrence: 18
--- mutation diff ---
--- astampbot/metrics.py
+++ bstampbot/metrics.py
@@ -31,7 +31,7 @@
"stampbot_http_request_duration_seconds",
"HTTP request duration in seconds",
["method", "endpoint"],
- buckets=(0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+ buckets=(0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 6.0, 10.0),
)
http_request_size_bytes = Histogram(........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.62s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/NumberReplacer, occurrence: 19
--- mutation diff ---
--- astampbot/metrics.py
+++ bstampbot/metrics.py
@@ -31,7 +31,7 @@
"stampbot_http_request_duration_seconds",
"HTTP request duration in seconds",
["method", "endpoint"],
- buckets=(0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+ buckets=(0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 4.0, 10.0),
)
http_request_size_bytes = Histogram(........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.60s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/NumberReplacer, occurrence: 20
--- mutation diff ---
--- astampbot/metrics.py
+++ bstampbot/metrics.py
@@ -31,7 +31,7 @@
"stampbot_http_request_duration_seconds",
"HTTP request duration in seconds",
["method", "endpoint"],
- buckets=(0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+ buckets=(0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 11.0),
)
http_request_size_bytes = Histogram(........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/NumberReplacer, occurrence: 21
--- mutation diff ---
--- astampbot/metrics.py
+++ bstampbot/metrics.py
@@ -31,7 +31,7 @@
"stampbot_http_request_duration_seconds",
"HTTP request duration in seconds",
["method", "endpoint"],
- buckets=(0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+ buckets=(0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 9.0),
)
http_request_size_bytes = Histogram(........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.60s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/NumberReplacer, occurrence: 22
--- mutation diff ---
--- astampbot/metrics.py
+++ bstampbot/metrics.py
@@ -38,7 +38,7 @@
"stampbot_http_request_size_bytes",
"HTTP request body size in bytes",
["method", "endpoint"],
- buckets=(100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
+ buckets=( 101, 500, 1000, 5000, 10000, 50000, 100000, 500000),
)
http_response_size_bytes = Histogram(........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.60s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/NumberReplacer, occurrence: 23
--- mutation diff ---
--- astampbot/metrics.py
+++ bstampbot/metrics.py
@@ -38,7 +38,7 @@
"stampbot_http_request_size_bytes",
"HTTP request body size in bytes",
["method", "endpoint"],
- buckets=(100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
+ buckets=( 99, 500, 1000, 5000, 10000, 50000, 100000, 500000),
)
http_response_size_bytes = Histogram(........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/NumberReplacer, occurrence: 24
--- mutation diff ---
--- astampbot/metrics.py
+++ bstampbot/metrics.py
@@ -38,7 +38,7 @@
"stampbot_http_request_size_bytes",
"HTTP request body size in bytes",
["method", "endpoint"],
- buckets=(100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
+ buckets=(100, 501, 1000, 5000, 10000, 50000, 100000, 500000),
)
http_response_size_bytes = Histogram(........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/NumberReplacer, occurrence: 25
--- mutation diff ---
--- astampbot/metrics.py
+++ bstampbot/metrics.py
@@ -38,7 +38,7 @@
"stampbot_http_request_size_bytes",
"HTTP request body size in bytes",
["method", "endpoint"],
- buckets=(100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
+ buckets=(100, 499, 1000, 5000, 10000, 50000, 100000, 500000),
)
http_response_size_bytes = Histogram(........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.66s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/NumberReplacer, occurrence: 26
--- mutation diff ---
--- astampbot/metrics.py
+++ bstampbot/metrics.py
@@ -38,7 +38,7 @@
"stampbot_http_request_size_bytes",
"HTTP request body size in bytes",
["method", "endpoint"],
- buckets=(100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
+ buckets=(100, 500, 1001, 5000, 10000, 50000, 100000, 500000),
)
http_response_size_bytes = Histogram(........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.64s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/NumberReplacer, occurrence: 27
--- mutation diff ---
--- astampbot/metrics.py
+++ bstampbot/metrics.py
@@ -38,7 +38,7 @@
"stampbot_http_request_size_bytes",
"HTTP request body size in bytes",
["method", "endpoint"],
- buckets=(100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
+ buckets=(100, 500, 999, 5000, 10000, 50000, 100000, 500000),
)
http_response_size_bytes = Histogram(........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.64s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/NumberReplacer, occurrence: 28
--- mutation diff ---
--- astampbot/metrics.py
+++ bstampbot/metrics.py
@@ -38,7 +38,7 @@
"stampbot_http_request_size_bytes",
"HTTP request body size in bytes",
["method", "endpoint"],
- buckets=(100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
+ buckets=(100, 500, 1000, 5001, 10000, 50000, 100000, 500000),
)
http_response_size_bytes = Histogram(........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/NumberReplacer, occurrence: 29
--- mutation diff ---
--- astampbot/metrics.py
+++ bstampbot/metrics.py
@@ -38,7 +38,7 @@
"stampbot_http_request_size_bytes",
"HTTP request body size in bytes",
["method", "endpoint"],
- buckets=(100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
+ buckets=(100, 500, 1000, 4999, 10000, 50000, 100000, 500000),
)
http_response_size_bytes = Histogram(........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.63s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/NumberReplacer, occurrence: 30
--- mutation diff ---
--- astampbot/metrics.py
+++ bstampbot/metrics.py
@@ -38,7 +38,7 @@
"stampbot_http_request_size_bytes",
"HTTP request body size in bytes",
["method", "endpoint"],
- buckets=(100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
+ buckets=(100, 500, 1000, 5000, 10001, 50000, 100000, 500000),
)
http_response_size_bytes = Histogram(........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/NumberReplacer, occurrence: 31
--- mutation diff ---
--- astampbot/metrics.py
+++ bstampbot/metrics.py
@@ -38,7 +38,7 @@
"stampbot_http_request_size_bytes",
"HTTP request body size in bytes",
["method", "endpoint"],
- buckets=(100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
+ buckets=(100, 500, 1000, 5000, 9999, 50000, 100000, 500000),
)
http_response_size_bytes = Histogram(........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.60s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/NumberReplacer, occurrence: 32
--- mutation diff ---
--- astampbot/metrics.py
+++ bstampbot/metrics.py
@@ -38,7 +38,7 @@
"stampbot_http_request_size_bytes",
"HTTP request body size in bytes",
["method", "endpoint"],
- buckets=(100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
+ buckets=(100, 500, 1000, 5000, 10000, 50001, 100000, 500000),
)
http_response_size_bytes = Histogram(........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/NumberReplacer, occurrence: 33
--- mutation diff ---
--- astampbot/metrics.py
+++ bstampbot/metrics.py
@@ -38,7 +38,7 @@
"stampbot_http_request_size_bytes",
"HTTP request body size in bytes",
["method", "endpoint"],
- buckets=(100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
+ buckets=(100, 500, 1000, 5000, 10000, 49999, 100000, 500000),
)
http_response_size_bytes = Histogram(........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.63s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/NumberReplacer, occurrence: 34
--- mutation diff ---
--- astampbot/metrics.py
+++ bstampbot/metrics.py
@@ -38,7 +38,7 @@
"stampbot_http_request_size_bytes",
"HTTP request body size in bytes",
["method", "endpoint"],
- buckets=(100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
+ buckets=(100, 500, 1000, 5000, 10000, 50000, 100001, 500000),
)
http_response_size_bytes = Histogram(........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/NumberReplacer, occurrence: 35
--- mutation diff ---
--- astampbot/metrics.py
+++ bstampbot/metrics.py
@@ -38,7 +38,7 @@
"stampbot_http_request_size_bytes",
"HTTP request body size in bytes",
["method", "endpoint"],
- buckets=(100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
+ buckets=(100, 500, 1000, 5000, 10000, 50000, 99999, 500000),
)
http_response_size_bytes = Histogram(........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.60s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/NumberReplacer, occurrence: 36
--- mutation diff ---
--- astampbot/metrics.py
+++ bstampbot/metrics.py
@@ -38,7 +38,7 @@
"stampbot_http_request_size_bytes",
"HTTP request body size in bytes",
["method", "endpoint"],
- buckets=(100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
+ buckets=(100, 500, 1000, 5000, 10000, 50000, 100000, 500001),
)
http_response_size_bytes = Histogram(........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/NumberReplacer, occurrence: 37
--- mutation diff ---
--- astampbot/metrics.py
+++ bstampbot/metrics.py
@@ -38,7 +38,7 @@
"stampbot_http_request_size_bytes",
"HTTP request body size in bytes",
["method", "endpoint"],
- buckets=(100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
+ buckets=(100, 500, 1000, 5000, 10000, 50000, 100000, 499999),
)
http_response_size_bytes = Histogram(........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.63s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/NumberReplacer, occurrence: 38
--- mutation diff ---
--- astampbot/metrics.py
+++ bstampbot/metrics.py
@@ -45,7 +45,7 @@
"stampbot_http_response_size_bytes",
"HTTP response body size in bytes",
["method", "endpoint"],
- buckets=(100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
+ buckets=( 101, 500, 1000, 5000, 10000, 50000, 100000, 500000),
)
http_requests_in_progress = Gauge(........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/NumberReplacer, occurrence: 39
--- mutation diff ---
--- astampbot/metrics.py
+++ bstampbot/metrics.py
@@ -45,7 +45,7 @@
"stampbot_http_response_size_bytes",
"HTTP response body size in bytes",
["method", "endpoint"],
- buckets=(100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
+ buckets=( 99, 500, 1000, 5000, 10000, 50000, 100000, 500000),
)
http_requests_in_progress = Gauge(........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.63s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/NumberReplacer, occurrence: 40
--- mutation diff ---
--- astampbot/metrics.py
+++ bstampbot/metrics.py
@@ -45,7 +45,7 @@
"stampbot_http_response_size_bytes",
"HTTP response body size in bytes",
["method", "endpoint"],
- buckets=(100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
+ buckets=(100, 501, 1000, 5000, 10000, 50000, 100000, 500000),
)
http_requests_in_progress = Gauge(........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/NumberReplacer, occurrence: 41
--- mutation diff ---
--- astampbot/metrics.py
+++ bstampbot/metrics.py
@@ -45,7 +45,7 @@
"stampbot_http_response_size_bytes",
"HTTP response body size in bytes",
["method", "endpoint"],
- buckets=(100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
+ buckets=(100, 499, 1000, 5000, 10000, 50000, 100000, 500000),
)
http_requests_in_progress = Gauge(........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.60s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/NumberReplacer, occurrence: 42
--- mutation diff ---
--- astampbot/metrics.py
+++ bstampbot/metrics.py
@@ -45,7 +45,7 @@
"stampbot_http_response_size_bytes",
"HTTP response body size in bytes",
["method", "endpoint"],
- buckets=(100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
+ buckets=(100, 500, 1001, 5000, 10000, 50000, 100000, 500000),
)
http_requests_in_progress = Gauge(........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.62s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/NumberReplacer, occurrence: 43
--- mutation diff ---
--- astampbot/metrics.py
+++ bstampbot/metrics.py
@@ -45,7 +45,7 @@
"stampbot_http_response_size_bytes",
"HTTP response body size in bytes",
["method", "endpoint"],
- buckets=(100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
+ buckets=(100, 500, 999, 5000, 10000, 50000, 100000, 500000),
)
http_requests_in_progress = Gauge(........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/NumberReplacer, occurrence: 44
--- mutation diff ---
--- astampbot/metrics.py
+++ bstampbot/metrics.py
@@ -45,7 +45,7 @@
"stampbot_http_response_size_bytes",
"HTTP response body size in bytes",
["method", "endpoint"],
- buckets=(100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
+ buckets=(100, 500, 1000, 5001, 10000, 50000, 100000, 500000),
)
http_requests_in_progress = Gauge(........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/NumberReplacer, occurrence: 45
--- mutation diff ---
--- astampbot/metrics.py
+++ bstampbot/metrics.py
@@ -45,7 +45,7 @@
"stampbot_http_response_size_bytes",
"HTTP response body size in bytes",
["method", "endpoint"],
- buckets=(100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
+ buckets=(100, 500, 1000, 4999, 10000, 50000, 100000, 500000),
)
http_requests_in_progress = Gauge(........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.63s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/NumberReplacer, occurrence: 46
--- mutation diff ---
--- astampbot/metrics.py
+++ bstampbot/metrics.py
@@ -45,7 +45,7 @@
"stampbot_http_response_size_bytes",
"HTTP response body size in bytes",
["method", "endpoint"],
- buckets=(100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
+ buckets=(100, 500, 1000, 5000, 10001, 50000, 100000, 500000),
)
http_requests_in_progress = Gauge(........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.62s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/NumberReplacer, occurrence: 47
--- mutation diff ---
--- astampbot/metrics.py
+++ bstampbot/metrics.py
@@ -45,7 +45,7 @@
"stampbot_http_response_size_bytes",
"HTTP response body size in bytes",
["method", "endpoint"],
- buckets=(100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
+ buckets=(100, 500, 1000, 5000, 9999, 50000, 100000, 500000),
)
http_requests_in_progress = Gauge(........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.68s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/NumberReplacer, occurrence: 48
--- mutation diff ---
--- astampbot/metrics.py
+++ bstampbot/metrics.py
@@ -45,7 +45,7 @@
"stampbot_http_response_size_bytes",
"HTTP response body size in bytes",
["method", "endpoint"],
- buckets=(100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
+ buckets=(100, 500, 1000, 5000, 10000, 50001, 100000, 500000),
)
http_requests_in_progress = Gauge(........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/NumberReplacer, occurrence: 49
--- mutation diff ---
--- astampbot/metrics.py
+++ bstampbot/metrics.py
@@ -45,7 +45,7 @@
"stampbot_http_response_size_bytes",
"HTTP response body size in bytes",
["method", "endpoint"],
- buckets=(100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
+ buckets=(100, 500, 1000, 5000, 10000, 49999, 100000, 500000),
)
http_requests_in_progress = Gauge(........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/NumberReplacer, occurrence: 50
--- mutation diff ---
--- astampbot/metrics.py
+++ bstampbot/metrics.py
@@ -45,7 +45,7 @@
"stampbot_http_response_size_bytes",
"HTTP response body size in bytes",
["method", "endpoint"],
- buckets=(100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
+ buckets=(100, 500, 1000, 5000, 10000, 50000, 100001, 500000),
)
http_requests_in_progress = Gauge(........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.60s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/NumberReplacer, occurrence: 51
--- mutation diff ---
--- astampbot/metrics.py
+++ bstampbot/metrics.py
@@ -45,7 +45,7 @@
"stampbot_http_response_size_bytes",
"HTTP response body size in bytes",
["method", "endpoint"],
- buckets=(100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
+ buckets=(100, 500, 1000, 5000, 10000, 50000, 99999, 500000),
)
http_requests_in_progress = Gauge(........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.63s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/NumberReplacer, occurrence: 52
--- mutation diff ---
--- astampbot/metrics.py
+++ bstampbot/metrics.py
@@ -45,7 +45,7 @@
"stampbot_http_response_size_bytes",
"HTTP response body size in bytes",
["method", "endpoint"],
- buckets=(100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
+ buckets=(100, 500, 1000, 5000, 10000, 50000, 100000, 500001),
)
http_requests_in_progress = Gauge(........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.63s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/NumberReplacer, occurrence: 53
--- mutation diff ---
--- astampbot/metrics.py
+++ bstampbot/metrics.py
@@ -45,7 +45,7 @@
"stampbot_http_response_size_bytes",
"HTTP response body size in bytes",
["method", "endpoint"],
- buckets=(100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
+ buckets=(100, 500, 1000, 5000, 10000, 50000, 100000, 499999),
)
http_requests_in_progress = Gauge(........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/NumberReplacer, occurrence: 54
--- mutation diff ---
--- astampbot/metrics.py
+++ bstampbot/metrics.py
@@ -74,7 +74,7 @@
"stampbot_webhook_processing_duration_seconds",
"Webhook event processing duration in seconds",
["event_type"],
- buckets=(0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+ buckets=( 1.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
)
# =============================================================================...................................F
=================================== FAILURES ===================================
________ TestGitHubAppClientInit.test_init_creates_uninitialized_client ________
self = <tests.test_github_client.TestGitHubAppClientInit object at 0x7efe39f039d0>
def test_init_creates_uninitialized_client(self):
"""Test that __init__ creates an uninitialized client."""
> from stampbot.github_client import GitHubAppClient
tests/test_github_client.py:19:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
stampbot/github_client.py:15: in <module>
from stampbot.metrics import (
stampbot/metrics.py:73: in <module>
webhook_processing_duration_seconds = Histogram(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:625: in __init__
self._prepare_buckets(buckets)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <[AttributeError("'Histogram' object has no attribute '_name'") raised in repr()] Histogram object at 0x7efe383dd5b0>
source_buckets = (1.01, 0.025, 0.05, 0.1, 0.25, 0.5, ...)
def _prepare_buckets(self, source_buckets: Sequence[Union[float, str]]) -> None:
buckets = [float(b) for b in source_buckets]
if buckets != sorted(buckets):
# This is probably an error on the part of the user,
# so raise rather than sorting for them.
> raise ValueError('Buckets not in sorted order')
E ValueError: Buckets not in sorted order
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:643: ValueError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGitHubAppClientInit::test_init_creates_uninitialized_client - ValueError: Buckets not in sorted order
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 35 passed, 3 deselected in 0.66s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/NumberReplacer, occurrence: 55
--- mutation diff ---
--- astampbot/metrics.py
+++ bstampbot/metrics.py
@@ -74,7 +74,7 @@
"stampbot_webhook_processing_duration_seconds",
"Webhook event processing duration in seconds",
["event_type"],
- buckets=(0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+ buckets=( -0.99, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
)
# =============================================================================........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/NumberReplacer, occurrence: 56
--- mutation diff ---
--- astampbot/metrics.py
+++ bstampbot/metrics.py
@@ -74,7 +74,7 @@
"stampbot_webhook_processing_duration_seconds",
"Webhook event processing duration in seconds",
["event_type"],
- buckets=(0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+ buckets=(0.01, 1.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
)
# =============================================================================...................................F
=================================== FAILURES ===================================
________ TestGitHubAppClientInit.test_init_creates_uninitialized_client ________
self = <tests.test_github_client.TestGitHubAppClientInit object at 0x7f07cbee79d0>
def test_init_creates_uninitialized_client(self):
"""Test that __init__ creates an uninitialized client."""
> from stampbot.github_client import GitHubAppClient
tests/test_github_client.py:19:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
stampbot/github_client.py:15: in <module>
from stampbot.metrics import (
stampbot/metrics.py:73: in <module>
webhook_processing_duration_seconds = Histogram(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:625: in __init__
self._prepare_buckets(buckets)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <[AttributeError("'Histogram' object has no attribute '_name'") raised in repr()] Histogram object at 0x7f07ca2dd5b0>
source_buckets = (0.01, 1.025, 0.05, 0.1, 0.25, 0.5, ...)
def _prepare_buckets(self, source_buckets: Sequence[Union[float, str]]) -> None:
buckets = [float(b) for b in source_buckets]
if buckets != sorted(buckets):
# This is probably an error on the part of the user,
# so raise rather than sorting for them.
> raise ValueError('Buckets not in sorted order')
E ValueError: Buckets not in sorted order
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:643: ValueError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGitHubAppClientInit::test_init_creates_uninitialized_client - ValueError: Buckets not in sorted order
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 35 passed, 3 deselected in 0.68s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/NumberReplacer, occurrence: 57
--- mutation diff ---
--- astampbot/metrics.py
+++ bstampbot/metrics.py
@@ -74,7 +74,7 @@
"stampbot_webhook_processing_duration_seconds",
"Webhook event processing duration in seconds",
["event_type"],
- buckets=(0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+ buckets=(0.01, -0.975, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
)
# =============================================================================...................................F
=================================== FAILURES ===================================
________ TestGitHubAppClientInit.test_init_creates_uninitialized_client ________
self = <tests.test_github_client.TestGitHubAppClientInit object at 0x7fcc07ebb9d0>
def test_init_creates_uninitialized_client(self):
"""Test that __init__ creates an uninitialized client."""
> from stampbot.github_client import GitHubAppClient
tests/test_github_client.py:19:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
stampbot/github_client.py:15: in <module>
from stampbot.metrics import (
stampbot/metrics.py:73: in <module>
webhook_processing_duration_seconds = Histogram(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:625: in __init__
self._prepare_buckets(buckets)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <[AttributeError("'Histogram' object has no attribute '_name'") raised in repr()] Histogram object at 0x7fcc062dd5b0>
source_buckets = (0.01, -0.975, 0.05, 0.1, 0.25, 0.5, ...)
def _prepare_buckets(self, source_buckets: Sequence[Union[float, str]]) -> None:
buckets = [float(b) for b in source_buckets]
if buckets != sorted(buckets):
# This is probably an error on the part of the user,
# so raise rather than sorting for them.
> raise ValueError('Buckets not in sorted order')
E ValueError: Buckets not in sorted order
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:643: ValueError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGitHubAppClientInit::test_init_creates_uninitialized_client - ValueError: Buckets not in sorted order
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 35 passed, 3 deselected in 0.66s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/NumberReplacer, occurrence: 58
--- mutation diff ---
--- astampbot/metrics.py
+++ bstampbot/metrics.py
@@ -74,7 +74,7 @@
"stampbot_webhook_processing_duration_seconds",
"Webhook event processing duration in seconds",
["event_type"],
- buckets=(0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+ buckets=(0.01, 0.025, 1.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
)
# =============================================================================...................................F
=================================== FAILURES ===================================
________ TestGitHubAppClientInit.test_init_creates_uninitialized_client ________
self = <tests.test_github_client.TestGitHubAppClientInit object at 0x7fe22048f9d0>
def test_init_creates_uninitialized_client(self):
"""Test that __init__ creates an uninitialized client."""
> from stampbot.github_client import GitHubAppClient
tests/test_github_client.py:19:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
stampbot/github_client.py:15: in <module>
from stampbot.metrics import (
stampbot/metrics.py:73: in <module>
webhook_processing_duration_seconds = Histogram(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:625: in __init__
self._prepare_buckets(buckets)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <[AttributeError("'Histogram' object has no attribute '_name'") raised in repr()] Histogram object at 0x7fe21e7dd5b0>
source_buckets = (0.01, 0.025, 1.05, 0.1, 0.25, 0.5, ...)
def _prepare_buckets(self, source_buckets: Sequence[Union[float, str]]) -> None:
buckets = [float(b) for b in source_buckets]
if buckets != sorted(buckets):
# This is probably an error on the part of the user,
# so raise rather than sorting for them.
> raise ValueError('Buckets not in sorted order')
E ValueError: Buckets not in sorted order
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:643: ValueError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGitHubAppClientInit::test_init_creates_uninitialized_client - ValueError: Buckets not in sorted order
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 35 passed, 3 deselected in 0.68s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/NumberReplacer, occurrence: 59
--- mutation diff ---
--- astampbot/metrics.py
+++ bstampbot/metrics.py
@@ -74,7 +74,7 @@
"stampbot_webhook_processing_duration_seconds",
"Webhook event processing duration in seconds",
["event_type"],
- buckets=(0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+ buckets=(0.01, 0.025, -0.95, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
)
# =============================================================================...................................F
=================================== FAILURES ===================================
________ TestGitHubAppClientInit.test_init_creates_uninitialized_client ________
self = <tests.test_github_client.TestGitHubAppClientInit object at 0x7f2e8848f9d0>
def test_init_creates_uninitialized_client(self):
"""Test that __init__ creates an uninitialized client."""
> from stampbot.github_client import GitHubAppClient
tests/test_github_client.py:19:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
stampbot/github_client.py:15: in <module>
from stampbot.metrics import (
stampbot/metrics.py:73: in <module>
webhook_processing_duration_seconds = Histogram(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:625: in __init__
self._prepare_buckets(buckets)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <[AttributeError("'Histogram' object has no attribute '_name'") raised in repr()] Histogram object at 0x7f2e868ed5b0>
source_buckets = (0.01, 0.025, -0.95, 0.1, 0.25, 0.5, ...)
def _prepare_buckets(self, source_buckets: Sequence[Union[float, str]]) -> None:
buckets = [float(b) for b in source_buckets]
if buckets != sorted(buckets):
# This is probably an error on the part of the user,
# so raise rather than sorting for them.
> raise ValueError('Buckets not in sorted order')
E ValueError: Buckets not in sorted order
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:643: ValueError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGitHubAppClientInit::test_init_creates_uninitialized_client - ValueError: Buckets not in sorted order
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 35 passed, 3 deselected in 0.67s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/NumberReplacer, occurrence: 60
--- mutation diff ---
--- astampbot/metrics.py
+++ bstampbot/metrics.py
@@ -74,7 +74,7 @@
"stampbot_webhook_processing_duration_seconds",
"Webhook event processing duration in seconds",
["event_type"],
- buckets=(0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+ buckets=(0.01, 0.025, 0.05, 1.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
)
# =============================================================================...................................F
=================================== FAILURES ===================================
________ TestGitHubAppClientInit.test_init_creates_uninitialized_client ________
self = <tests.test_github_client.TestGitHubAppClientInit object at 0x7f4163de39d0>
def test_init_creates_uninitialized_client(self):
"""Test that __init__ creates an uninitialized client."""
> from stampbot.github_client import GitHubAppClient
tests/test_github_client.py:19:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
stampbot/github_client.py:15: in <module>
from stampbot.metrics import (
stampbot/metrics.py:73: in <module>
webhook_processing_duration_seconds = Histogram(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:625: in __init__
self._prepare_buckets(buckets)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <[AttributeError("'Histogram' object has no attribute '_name'") raised in repr()] Histogram object at 0x7f41622f15b0>
source_buckets = (0.01, 0.025, 0.05, 1.1, 0.25, 0.5, ...)
def _prepare_buckets(self, source_buckets: Sequence[Union[float, str]]) -> None:
buckets = [float(b) for b in source_buckets]
if buckets != sorted(buckets):
# This is probably an error on the part of the user,
# so raise rather than sorting for them.
> raise ValueError('Buckets not in sorted order')
E ValueError: Buckets not in sorted order
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:643: ValueError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGitHubAppClientInit::test_init_creates_uninitialized_client - ValueError: Buckets not in sorted order
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 35 passed, 3 deselected in 0.67s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/NumberReplacer, occurrence: 61
--- mutation diff ---
--- astampbot/metrics.py
+++ bstampbot/metrics.py
@@ -74,7 +74,7 @@
"stampbot_webhook_processing_duration_seconds",
"Webhook event processing duration in seconds",
["event_type"],
- buckets=(0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+ buckets=(0.01, 0.025, 0.05, -0.9, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
)
# =============================================================================...................................F
=================================== FAILURES ===================================
________ TestGitHubAppClientInit.test_init_creates_uninitialized_client ________
self = <tests.test_github_client.TestGitHubAppClientInit object at 0x7fb49f16f9d0>
def test_init_creates_uninitialized_client(self):
"""Test that __init__ creates an uninitialized client."""
> from stampbot.github_client import GitHubAppClient
tests/test_github_client.py:19:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
stampbot/github_client.py:15: in <module>
from stampbot.metrics import (
stampbot/metrics.py:73: in <module>
webhook_processing_duration_seconds = Histogram(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:625: in __init__
self._prepare_buckets(buckets)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <[AttributeError("'Histogram' object has no attribute '_name'") raised in repr()] Histogram object at 0x7fb49d5f15b0>
source_buckets = (0.01, 0.025, 0.05, -0.9, 0.25, 0.5, ...)
def _prepare_buckets(self, source_buckets: Sequence[Union[float, str]]) -> None:
buckets = [float(b) for b in source_buckets]
if buckets != sorted(buckets):
# This is probably an error on the part of the user,
# so raise rather than sorting for them.
> raise ValueError('Buckets not in sorted order')
E ValueError: Buckets not in sorted order
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:643: ValueError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGitHubAppClientInit::test_init_creates_uninitialized_client - ValueError: Buckets not in sorted order
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 35 passed, 3 deselected in 0.65s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/NumberReplacer, occurrence: 62
--- mutation diff ---
--- astampbot/metrics.py
+++ bstampbot/metrics.py
@@ -74,7 +74,7 @@
"stampbot_webhook_processing_duration_seconds",
"Webhook event processing duration in seconds",
["event_type"],
- buckets=(0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+ buckets=(0.01, 0.025, 0.05, 0.1, 1.25, 0.5, 1.0, 2.5, 5.0, 10.0),
)
# =============================================================================...................................F
=================================== FAILURES ===================================
________ TestGitHubAppClientInit.test_init_creates_uninitialized_client ________
self = <tests.test_github_client.TestGitHubAppClientInit object at 0x7f206f3c79d0>
def test_init_creates_uninitialized_client(self):
"""Test that __init__ creates an uninitialized client."""
> from stampbot.github_client import GitHubAppClient
tests/test_github_client.py:19:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
stampbot/github_client.py:15: in <module>
from stampbot.metrics import (
stampbot/metrics.py:73: in <module>
webhook_processing_duration_seconds = Histogram(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:625: in __init__
self._prepare_buckets(buckets)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <[AttributeError("'Histogram' object has no attribute '_name'") raised in repr()] Histogram object at 0x7f206d7dd5b0>
source_buckets = (0.01, 0.025, 0.05, 0.1, 1.25, 0.5, ...)
def _prepare_buckets(self, source_buckets: Sequence[Union[float, str]]) -> None:
buckets = [float(b) for b in source_buckets]
if buckets != sorted(buckets):
# This is probably an error on the part of the user,
# so raise rather than sorting for them.
> raise ValueError('Buckets not in sorted order')
E ValueError: Buckets not in sorted order
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:643: ValueError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGitHubAppClientInit::test_init_creates_uninitialized_client - ValueError: Buckets not in sorted order
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 35 passed, 3 deselected in 0.67s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/NumberReplacer, occurrence: 63
--- mutation diff ---
--- astampbot/metrics.py
+++ bstampbot/metrics.py
@@ -74,7 +74,7 @@
"stampbot_webhook_processing_duration_seconds",
"Webhook event processing duration in seconds",
["event_type"],
- buckets=(0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+ buckets=(0.01, 0.025, 0.05, 0.1, -0.75, 0.5, 1.0, 2.5, 5.0, 10.0),
)
# =============================================================================...................................F
=================================== FAILURES ===================================
________ TestGitHubAppClientInit.test_init_creates_uninitialized_client ________
self = <tests.test_github_client.TestGitHubAppClientInit object at 0x7fa8f3f439d0>
def test_init_creates_uninitialized_client(self):
"""Test that __init__ creates an uninitialized client."""
> from stampbot.github_client import GitHubAppClient
tests/test_github_client.py:19:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
stampbot/github_client.py:15: in <module>
from stampbot.metrics import (
stampbot/metrics.py:73: in <module>
webhook_processing_duration_seconds = Histogram(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:625: in __init__
self._prepare_buckets(buckets)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <[AttributeError("'Histogram' object has no attribute '_name'") raised in repr()] Histogram object at 0x7fa8f23f15b0>
source_buckets = (0.01, 0.025, 0.05, 0.1, -0.75, 0.5, ...)
def _prepare_buckets(self, source_buckets: Sequence[Union[float, str]]) -> None:
buckets = [float(b) for b in source_buckets]
if buckets != sorted(buckets):
# This is probably an error on the part of the user,
# so raise rather than sorting for them.
> raise ValueError('Buckets not in sorted order')
E ValueError: Buckets not in sorted order
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:643: ValueError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGitHubAppClientInit::test_init_creates_uninitialized_client - ValueError: Buckets not in sorted order
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 35 passed, 3 deselected in 0.66s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/NumberReplacer, occurrence: 64
--- mutation diff ---
--- astampbot/metrics.py
+++ bstampbot/metrics.py
@@ -74,7 +74,7 @@
"stampbot_webhook_processing_duration_seconds",
"Webhook event processing duration in seconds",
["event_type"],
- buckets=(0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+ buckets=(0.01, 0.025, 0.05, 0.1, 0.25, 1.5, 1.0, 2.5, 5.0, 10.0),
)
# =============================================================================...................................F
=================================== FAILURES ===================================
________ TestGitHubAppClientInit.test_init_creates_uninitialized_client ________
self = <tests.test_github_client.TestGitHubAppClientInit object at 0x7fe485bdf9d0>
def test_init_creates_uninitialized_client(self):
"""Test that __init__ creates an uninitialized client."""
> from stampbot.github_client import GitHubAppClient
tests/test_github_client.py:19:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
stampbot/github_client.py:15: in <module>
from stampbot.metrics import (
stampbot/metrics.py:73: in <module>
webhook_processing_duration_seconds = Histogram(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:625: in __init__
self._prepare_buckets(buckets)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <[AttributeError("'Histogram' object has no attribute '_name'") raised in repr()] Histogram object at 0x7fe4840f15b0>
source_buckets = (0.01, 0.025, 0.05, 0.1, 0.25, 1.5, ...)
def _prepare_buckets(self, source_buckets: Sequence[Union[float, str]]) -> None:
buckets = [float(b) for b in source_buckets]
if buckets != sorted(buckets):
# This is probably an error on the part of the user,
# so raise rather than sorting for them.
> raise ValueError('Buckets not in sorted order')
E ValueError: Buckets not in sorted order
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:643: ValueError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGitHubAppClientInit::test_init_creates_uninitialized_client - ValueError: Buckets not in sorted order
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 35 passed, 3 deselected in 0.66s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/NumberReplacer, occurrence: 65
--- mutation diff ---
--- astampbot/metrics.py
+++ bstampbot/metrics.py
@@ -74,7 +74,7 @@
"stampbot_webhook_processing_duration_seconds",
"Webhook event processing duration in seconds",
["event_type"],
- buckets=(0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+ buckets=(0.01, 0.025, 0.05, 0.1, 0.25, -0.5, 1.0, 2.5, 5.0, 10.0),
)
# =============================================================================...................................F
=================================== FAILURES ===================================
________ TestGitHubAppClientInit.test_init_creates_uninitialized_client ________
self = <tests.test_github_client.TestGitHubAppClientInit object at 0x7fe9db45b9d0>
def test_init_creates_uninitialized_client(self):
"""Test that __init__ creates an uninitialized client."""
> from stampbot.github_client import GitHubAppClient
tests/test_github_client.py:19:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
stampbot/github_client.py:15: in <module>
from stampbot.metrics import (
stampbot/metrics.py:73: in <module>
webhook_processing_duration_seconds = Histogram(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:625: in __init__
self._prepare_buckets(buckets)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <[AttributeError("'Histogram' object has no attribute '_name'") raised in repr()] Histogram object at 0x7fe9d98f15b0>
source_buckets = (0.01, 0.025, 0.05, 0.1, 0.25, -0.5, ...)
def _prepare_buckets(self, source_buckets: Sequence[Union[float, str]]) -> None:
buckets = [float(b) for b in source_buckets]
if buckets != sorted(buckets):
# This is probably an error on the part of the user,
# so raise rather than sorting for them.
> raise ValueError('Buckets not in sorted order')
E ValueError: Buckets not in sorted order
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:643: ValueError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGitHubAppClientInit::test_init_creates_uninitialized_client - ValueError: Buckets not in sorted order
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 35 passed, 3 deselected in 0.70s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/NumberReplacer, occurrence: 66
--- mutation diff ---
--- astampbot/metrics.py
+++ bstampbot/metrics.py
@@ -74,7 +74,7 @@
"stampbot_webhook_processing_duration_seconds",
"Webhook event processing duration in seconds",
["event_type"],
- buckets=(0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+ buckets=(0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 2.0, 2.5, 5.0, 10.0),
)
# =============================================================================........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/NumberReplacer, occurrence: 67
--- mutation diff ---
--- astampbot/metrics.py
+++ bstampbot/metrics.py
@@ -74,7 +74,7 @@
"stampbot_webhook_processing_duration_seconds",
"Webhook event processing duration in seconds",
["event_type"],
- buckets=(0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+ buckets=(0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 0.0, 2.5, 5.0, 10.0),
)
# =============================================================================...................................F
=================================== FAILURES ===================================
________ TestGitHubAppClientInit.test_init_creates_uninitialized_client ________
self = <tests.test_github_client.TestGitHubAppClientInit object at 0x7feb1ec2b9d0>
def test_init_creates_uninitialized_client(self):
"""Test that __init__ creates an uninitialized client."""
> from stampbot.github_client import GitHubAppClient
tests/test_github_client.py:19:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
stampbot/github_client.py:15: in <module>
from stampbot.metrics import (
stampbot/metrics.py:73: in <module>
webhook_processing_duration_seconds = Histogram(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:625: in __init__
self._prepare_buckets(buckets)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <[AttributeError("'Histogram' object has no attribute '_name'") raised in repr()] Histogram object at 0x7feb1d0f15b0>
source_buckets = (0.01, 0.025, 0.05, 0.1, 0.25, 0.5, ...)
def _prepare_buckets(self, source_buckets: Sequence[Union[float, str]]) -> None:
buckets = [float(b) for b in source_buckets]
if buckets != sorted(buckets):
# This is probably an error on the part of the user,
# so raise rather than sorting for them.
> raise ValueError('Buckets not in sorted order')
E ValueError: Buckets not in sorted order
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:643: ValueError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGitHubAppClientInit::test_init_creates_uninitialized_client - ValueError: Buckets not in sorted order
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 35 passed, 3 deselected in 0.66s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/NumberReplacer, occurrence: 68
--- mutation diff ---
--- astampbot/metrics.py
+++ bstampbot/metrics.py
@@ -74,7 +74,7 @@
"stampbot_webhook_processing_duration_seconds",
"Webhook event processing duration in seconds",
["event_type"],
- buckets=(0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+ buckets=(0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 3.5, 5.0, 10.0),
)
# =============================================================================........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/NumberReplacer, occurrence: 69
--- mutation diff ---
--- astampbot/metrics.py
+++ bstampbot/metrics.py
@@ -74,7 +74,7 @@
"stampbot_webhook_processing_duration_seconds",
"Webhook event processing duration in seconds",
["event_type"],
- buckets=(0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+ buckets=(0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 1.5, 5.0, 10.0),
)
# =============================================================================........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.65s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/NumberReplacer, occurrence: 70
--- mutation diff ---
--- astampbot/metrics.py
+++ bstampbot/metrics.py
@@ -74,7 +74,7 @@
"stampbot_webhook_processing_duration_seconds",
"Webhook event processing duration in seconds",
["event_type"],
- buckets=(0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+ buckets=(0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 6.0, 10.0),
)
# =============================================================================........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.62s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/NumberReplacer, occurrence: 71
--- mutation diff ---
--- astampbot/metrics.py
+++ bstampbot/metrics.py
@@ -74,7 +74,7 @@
"stampbot_webhook_processing_duration_seconds",
"Webhook event processing duration in seconds",
["event_type"],
- buckets=(0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+ buckets=(0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 4.0, 10.0),
)
# =============================================================================........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.63s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/NumberReplacer, occurrence: 72
--- mutation diff ---
--- astampbot/metrics.py
+++ bstampbot/metrics.py
@@ -74,7 +74,7 @@
"stampbot_webhook_processing_duration_seconds",
"Webhook event processing duration in seconds",
["event_type"],
- buckets=(0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+ buckets=(0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 11.0),
)
# =============================================================================........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/NumberReplacer, occurrence: 73
--- mutation diff ---
--- astampbot/metrics.py
+++ bstampbot/metrics.py
@@ -74,7 +74,7 @@
"stampbot_webhook_processing_duration_seconds",
"Webhook event processing duration in seconds",
["event_type"],
- buckets=(0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+ buckets=(0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 9.0),
)
# =============================================================================........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.64s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/NumberReplacer, occurrence: 74
--- mutation diff ---
--- astampbot/metrics.py
+++ bstampbot/metrics.py
@@ -90,7 +90,7 @@
pr_approval_duration_seconds = Histogram(
"stampbot_pr_approval_duration_seconds",
"PR approval operation duration in seconds",
- buckets=(0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+ buckets=( 1.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
)
pr_dismissals_total = Counter(...................................F
=================================== FAILURES ===================================
________ TestGitHubAppClientInit.test_init_creates_uninitialized_client ________
self = <tests.test_github_client.TestGitHubAppClientInit object at 0x7f85d7f039d0>
def test_init_creates_uninitialized_client(self):
"""Test that __init__ creates an uninitialized client."""
> from stampbot.github_client import GitHubAppClient
tests/test_github_client.py:19:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
stampbot/github_client.py:15: in <module>
from stampbot.metrics import (
stampbot/metrics.py:90: in <module>
pr_approval_duration_seconds = Histogram(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:625: in __init__
self._prepare_buckets(buckets)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <[AttributeError("'Histogram' object has no attribute '_name'") raised in repr()] Histogram object at 0x7f85d62f1810>
source_buckets = (1.1, 0.25, 0.5, 1.0, 2.5, 5.0, ...)
def _prepare_buckets(self, source_buckets: Sequence[Union[float, str]]) -> None:
buckets = [float(b) for b in source_buckets]
if buckets != sorted(buckets):
# This is probably an error on the part of the user,
# so raise rather than sorting for them.
> raise ValueError('Buckets not in sorted order')
E ValueError: Buckets not in sorted order
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:643: ValueError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGitHubAppClientInit::test_init_creates_uninitialized_client - ValueError: Buckets not in sorted order
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 35 passed, 3 deselected in 0.67s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/NumberReplacer, occurrence: 75
--- mutation diff ---
--- astampbot/metrics.py
+++ bstampbot/metrics.py
@@ -90,7 +90,7 @@
pr_approval_duration_seconds = Histogram(
"stampbot_pr_approval_duration_seconds",
"PR approval operation duration in seconds",
- buckets=(0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+ buckets=( -0.9, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
)
pr_dismissals_total = Counter(........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.60s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/NumberReplacer, occurrence: 76
--- mutation diff ---
--- astampbot/metrics.py
+++ bstampbot/metrics.py
@@ -90,7 +90,7 @@
pr_approval_duration_seconds = Histogram(
"stampbot_pr_approval_duration_seconds",
"PR approval operation duration in seconds",
- buckets=(0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+ buckets=(0.1, 1.25, 0.5, 1.0, 2.5, 5.0, 10.0),
)
pr_dismissals_total = Counter(...................................F
=================================== FAILURES ===================================
________ TestGitHubAppClientInit.test_init_creates_uninitialized_client ________
self = <tests.test_github_client.TestGitHubAppClientInit object at 0x7f530be679d0>
def test_init_creates_uninitialized_client(self):
"""Test that __init__ creates an uninitialized client."""
> from stampbot.github_client import GitHubAppClient
tests/test_github_client.py:19:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
stampbot/github_client.py:15: in <module>
from stampbot.metrics import (
stampbot/metrics.py:90: in <module>
pr_approval_duration_seconds = Histogram(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:625: in __init__
self._prepare_buckets(buckets)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <[AttributeError("'Histogram' object has no attribute '_name'") raised in repr()] Histogram object at 0x7f530a2ed810>
source_buckets = (0.1, 1.25, 0.5, 1.0, 2.5, 5.0, ...)
def _prepare_buckets(self, source_buckets: Sequence[Union[float, str]]) -> None:
buckets = [float(b) for b in source_buckets]
if buckets != sorted(buckets):
# This is probably an error on the part of the user,
# so raise rather than sorting for them.
> raise ValueError('Buckets not in sorted order')
E ValueError: Buckets not in sorted order
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:643: ValueError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGitHubAppClientInit::test_init_creates_uninitialized_client - ValueError: Buckets not in sorted order
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 35 passed, 3 deselected in 0.68s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/NumberReplacer, occurrence: 77
--- mutation diff ---
--- astampbot/metrics.py
+++ bstampbot/metrics.py
@@ -90,7 +90,7 @@
pr_approval_duration_seconds = Histogram(
"stampbot_pr_approval_duration_seconds",
"PR approval operation duration in seconds",
- buckets=(0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+ buckets=(0.1, -0.75, 0.5, 1.0, 2.5, 5.0, 10.0),
)
pr_dismissals_total = Counter(...................................F
=================================== FAILURES ===================================
________ TestGitHubAppClientInit.test_init_creates_uninitialized_client ________
self = <tests.test_github_client.TestGitHubAppClientInit object at 0x7f1ecf0c79d0>
def test_init_creates_uninitialized_client(self):
"""Test that __init__ creates an uninitialized client."""
> from stampbot.github_client import GitHubAppClient
tests/test_github_client.py:19:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
stampbot/github_client.py:15: in <module>
from stampbot.metrics import (
stampbot/metrics.py:90: in <module>
pr_approval_duration_seconds = Histogram(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:625: in __init__
self._prepare_buckets(buckets)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <[AttributeError("'Histogram' object has no attribute '_name'") raised in repr()] Histogram object at 0x7f1ecd4dd810>
source_buckets = (0.1, -0.75, 0.5, 1.0, 2.5, 5.0, ...)
def _prepare_buckets(self, source_buckets: Sequence[Union[float, str]]) -> None:
buckets = [float(b) for b in source_buckets]
if buckets != sorted(buckets):
# This is probably an error on the part of the user,
# so raise rather than sorting for them.
> raise ValueError('Buckets not in sorted order')
E ValueError: Buckets not in sorted order
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:643: ValueError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGitHubAppClientInit::test_init_creates_uninitialized_client - ValueError: Buckets not in sorted order
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 35 passed, 3 deselected in 0.67s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/NumberReplacer, occurrence: 78
--- mutation diff ---
--- astampbot/metrics.py
+++ bstampbot/metrics.py
@@ -90,7 +90,7 @@
pr_approval_duration_seconds = Histogram(
"stampbot_pr_approval_duration_seconds",
"PR approval operation duration in seconds",
- buckets=(0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+ buckets=(0.1, 0.25, 1.5, 1.0, 2.5, 5.0, 10.0),
)
pr_dismissals_total = Counter(...................................F
=================================== FAILURES ===================================
________ TestGitHubAppClientInit.test_init_creates_uninitialized_client ________
self = <tests.test_github_client.TestGitHubAppClientInit object at 0x7f49c48579d0>
def test_init_creates_uninitialized_client(self):
"""Test that __init__ creates an uninitialized client."""
> from stampbot.github_client import GitHubAppClient
tests/test_github_client.py:19:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
stampbot/github_client.py:15: in <module>
from stampbot.metrics import (
stampbot/metrics.py:90: in <module>
pr_approval_duration_seconds = Histogram(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:625: in __init__
self._prepare_buckets(buckets)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <[AttributeError("'Histogram' object has no attribute '_name'") raised in repr()] Histogram object at 0x7f49c2cf1810>
source_buckets = (0.1, 0.25, 1.5, 1.0, 2.5, 5.0, ...)
def _prepare_buckets(self, source_buckets: Sequence[Union[float, str]]) -> None:
buckets = [float(b) for b in source_buckets]
if buckets != sorted(buckets):
# This is probably an error on the part of the user,
# so raise rather than sorting for them.
> raise ValueError('Buckets not in sorted order')
E ValueError: Buckets not in sorted order
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:643: ValueError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGitHubAppClientInit::test_init_creates_uninitialized_client - ValueError: Buckets not in sorted order
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 35 passed, 3 deselected in 0.67s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/NumberReplacer, occurrence: 79
--- mutation diff ---
--- astampbot/metrics.py
+++ bstampbot/metrics.py
@@ -90,7 +90,7 @@
pr_approval_duration_seconds = Histogram(
"stampbot_pr_approval_duration_seconds",
"PR approval operation duration in seconds",
- buckets=(0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+ buckets=(0.1, 0.25, -0.5, 1.0, 2.5, 5.0, 10.0),
)
pr_dismissals_total = Counter(...................................F
=================================== FAILURES ===================================
________ TestGitHubAppClientInit.test_init_creates_uninitialized_client ________
self = <tests.test_github_client.TestGitHubAppClientInit object at 0x7ff5f39279d0>
def test_init_creates_uninitialized_client(self):
"""Test that __init__ creates an uninitialized client."""
> from stampbot.github_client import GitHubAppClient
tests/test_github_client.py:19:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
stampbot/github_client.py:15: in <module>
from stampbot.metrics import (
stampbot/metrics.py:90: in <module>
pr_approval_duration_seconds = Histogram(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:625: in __init__
self._prepare_buckets(buckets)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <[AttributeError("'Histogram' object has no attribute '_name'") raised in repr()] Histogram object at 0x7ff5f1bdd810>
source_buckets = (0.1, 0.25, -0.5, 1.0, 2.5, 5.0, ...)
def _prepare_buckets(self, source_buckets: Sequence[Union[float, str]]) -> None:
buckets = [float(b) for b in source_buckets]
if buckets != sorted(buckets):
# This is probably an error on the part of the user,
# so raise rather than sorting for them.
> raise ValueError('Buckets not in sorted order')
E ValueError: Buckets not in sorted order
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:643: ValueError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGitHubAppClientInit::test_init_creates_uninitialized_client - ValueError: Buckets not in sorted order
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 35 passed, 3 deselected in 0.67s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/NumberReplacer, occurrence: 80
--- mutation diff ---
--- astampbot/metrics.py
+++ bstampbot/metrics.py
@@ -90,7 +90,7 @@
pr_approval_duration_seconds = Histogram(
"stampbot_pr_approval_duration_seconds",
"PR approval operation duration in seconds",
- buckets=(0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+ buckets=(0.1, 0.25, 0.5, 2.0, 2.5, 5.0, 10.0),
)
pr_dismissals_total = Counter(........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.62s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/NumberReplacer, occurrence: 81
--- mutation diff ---
--- astampbot/metrics.py
+++ bstampbot/metrics.py
@@ -90,7 +90,7 @@
pr_approval_duration_seconds = Histogram(
"stampbot_pr_approval_duration_seconds",
"PR approval operation duration in seconds",
- buckets=(0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+ buckets=(0.1, 0.25, 0.5, 0.0, 2.5, 5.0, 10.0),
)
pr_dismissals_total = Counter(...................................F
=================================== FAILURES ===================================
________ TestGitHubAppClientInit.test_init_creates_uninitialized_client ________
self = <tests.test_github_client.TestGitHubAppClientInit object at 0x7f3f817239d0>
def test_init_creates_uninitialized_client(self):
"""Test that __init__ creates an uninitialized client."""
> from stampbot.github_client import GitHubAppClient
tests/test_github_client.py:19:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
stampbot/github_client.py:15: in <module>
from stampbot.metrics import (
stampbot/metrics.py:90: in <module>
pr_approval_duration_seconds = Histogram(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:625: in __init__
self._prepare_buckets(buckets)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <[AttributeError("'Histogram' object has no attribute '_name'") raised in repr()] Histogram object at 0x7f3f7faed810>
source_buckets = (0.1, 0.25, 0.5, 0.0, 2.5, 5.0, ...)
def _prepare_buckets(self, source_buckets: Sequence[Union[float, str]]) -> None:
buckets = [float(b) for b in source_buckets]
if buckets != sorted(buckets):
# This is probably an error on the part of the user,
# so raise rather than sorting for them.
> raise ValueError('Buckets not in sorted order')
E ValueError: Buckets not in sorted order
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:643: ValueError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGitHubAppClientInit::test_init_creates_uninitialized_client - ValueError: Buckets not in sorted order
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 35 passed, 3 deselected in 0.67s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/NumberReplacer, occurrence: 82
--- mutation diff ---
--- astampbot/metrics.py
+++ bstampbot/metrics.py
@@ -90,7 +90,7 @@
pr_approval_duration_seconds = Histogram(
"stampbot_pr_approval_duration_seconds",
"PR approval operation duration in seconds",
- buckets=(0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+ buckets=(0.1, 0.25, 0.5, 1.0, 3.5, 5.0, 10.0),
)
pr_dismissals_total = Counter(........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/NumberReplacer, occurrence: 83
--- mutation diff ---
--- astampbot/metrics.py
+++ bstampbot/metrics.py
@@ -90,7 +90,7 @@
pr_approval_duration_seconds = Histogram(
"stampbot_pr_approval_duration_seconds",
"PR approval operation duration in seconds",
- buckets=(0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+ buckets=(0.1, 0.25, 0.5, 1.0, 1.5, 5.0, 10.0),
)
pr_dismissals_total = Counter(........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/NumberReplacer, occurrence: 84
--- mutation diff ---
--- astampbot/metrics.py
+++ bstampbot/metrics.py
@@ -90,7 +90,7 @@
pr_approval_duration_seconds = Histogram(
"stampbot_pr_approval_duration_seconds",
"PR approval operation duration in seconds",
- buckets=(0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+ buckets=(0.1, 0.25, 0.5, 1.0, 2.5, 6.0, 10.0),
)
pr_dismissals_total = Counter(........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.60s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/NumberReplacer, occurrence: 85
--- mutation diff ---
--- astampbot/metrics.py
+++ bstampbot/metrics.py
@@ -90,7 +90,7 @@
pr_approval_duration_seconds = Histogram(
"stampbot_pr_approval_duration_seconds",
"PR approval operation duration in seconds",
- buckets=(0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+ buckets=(0.1, 0.25, 0.5, 1.0, 2.5, 4.0, 10.0),
)
pr_dismissals_total = Counter(........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/NumberReplacer, occurrence: 86
--- mutation diff ---
--- astampbot/metrics.py
+++ bstampbot/metrics.py
@@ -90,7 +90,7 @@
pr_approval_duration_seconds = Histogram(
"stampbot_pr_approval_duration_seconds",
"PR approval operation duration in seconds",
- buckets=(0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+ buckets=(0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 11.0),
)
pr_dismissals_total = Counter(........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/NumberReplacer, occurrence: 87
--- mutation diff ---
--- astampbot/metrics.py
+++ bstampbot/metrics.py
@@ -90,7 +90,7 @@
pr_approval_duration_seconds = Histogram(
"stampbot_pr_approval_duration_seconds",
"PR approval operation duration in seconds",
- buckets=(0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+ buckets=(0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 9.0),
)
pr_dismissals_total = Counter(........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/NumberReplacer, occurrence: 88
--- mutation diff ---
--- astampbot/metrics.py
+++ bstampbot/metrics.py
@@ -102,7 +102,7 @@
pr_dismissal_duration_seconds = Histogram(
"stampbot_pr_dismissal_duration_seconds",
"PR dismissal operation duration in seconds",
- buckets=(0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+ buckets=( 1.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
)
# =============================================================================...................................F
=================================== FAILURES ===================================
________ TestGitHubAppClientInit.test_init_creates_uninitialized_client ________
self = <tests.test_github_client.TestGitHubAppClientInit object at 0x7fd4c6aef9d0>
def test_init_creates_uninitialized_client(self):
"""Test that __init__ creates an uninitialized client."""
> from stampbot.github_client import GitHubAppClient
tests/test_github_client.py:19:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
stampbot/github_client.py:15: in <module>
from stampbot.metrics import (
stampbot/metrics.py:102: in <module>
pr_dismissal_duration_seconds = Histogram(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:625: in __init__
self._prepare_buckets(buckets)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <[AttributeError("'Histogram' object has no attribute '_name'") raised in repr()] Histogram object at 0x7fd4c4d2d370>
source_buckets = (1.1, 0.25, 0.5, 1.0, 2.5, 5.0, ...)
def _prepare_buckets(self, source_buckets: Sequence[Union[float, str]]) -> None:
buckets = [float(b) for b in source_buckets]
if buckets != sorted(buckets):
# This is probably an error on the part of the user,
# so raise rather than sorting for them.
> raise ValueError('Buckets not in sorted order')
E ValueError: Buckets not in sorted order
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:643: ValueError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGitHubAppClientInit::test_init_creates_uninitialized_client - ValueError: Buckets not in sorted order
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 35 passed, 3 deselected in 0.66s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/NumberReplacer, occurrence: 89
--- mutation diff ---
--- astampbot/metrics.py
+++ bstampbot/metrics.py
@@ -102,7 +102,7 @@
pr_dismissal_duration_seconds = Histogram(
"stampbot_pr_dismissal_duration_seconds",
"PR dismissal operation duration in seconds",
- buckets=(0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+ buckets=( -0.9, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
)
# =============================================================================........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.59s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/NumberReplacer, occurrence: 90
--- mutation diff ---
--- astampbot/metrics.py
+++ bstampbot/metrics.py
@@ -102,7 +102,7 @@
pr_dismissal_duration_seconds = Histogram(
"stampbot_pr_dismissal_duration_seconds",
"PR dismissal operation duration in seconds",
- buckets=(0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+ buckets=(0.1, 1.25, 0.5, 1.0, 2.5, 5.0, 10.0),
)
# =============================================================================...................................F
=================================== FAILURES ===================================
________ TestGitHubAppClientInit.test_init_creates_uninitialized_client ________
self = <tests.test_github_client.TestGitHubAppClientInit object at 0x7ff3fa6e79d0>
def test_init_creates_uninitialized_client(self):
"""Test that __init__ creates an uninitialized client."""
> from stampbot.github_client import GitHubAppClient
tests/test_github_client.py:19:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
stampbot/github_client.py:15: in <module>
from stampbot.metrics import (
stampbot/metrics.py:102: in <module>
pr_dismissal_duration_seconds = Histogram(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:625: in __init__
self._prepare_buckets(buckets)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <[AttributeError("'Histogram' object has no attribute '_name'") raised in repr()] Histogram object at 0x7ff3f8925370>
source_buckets = (0.1, 1.25, 0.5, 1.0, 2.5, 5.0, ...)
def _prepare_buckets(self, source_buckets: Sequence[Union[float, str]]) -> None:
buckets = [float(b) for b in source_buckets]
if buckets != sorted(buckets):
# This is probably an error on the part of the user,
# so raise rather than sorting for them.
> raise ValueError('Buckets not in sorted order')
E ValueError: Buckets not in sorted order
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:643: ValueError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGitHubAppClientInit::test_init_creates_uninitialized_client - ValueError: Buckets not in sorted order
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 35 passed, 3 deselected in 0.67s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/NumberReplacer, occurrence: 91
--- mutation diff ---
--- astampbot/metrics.py
+++ bstampbot/metrics.py
@@ -102,7 +102,7 @@
pr_dismissal_duration_seconds = Histogram(
"stampbot_pr_dismissal_duration_seconds",
"PR dismissal operation duration in seconds",
- buckets=(0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+ buckets=(0.1, -0.75, 0.5, 1.0, 2.5, 5.0, 10.0),
)
# =============================================================================...................................F
=================================== FAILURES ===================================
________ TestGitHubAppClientInit.test_init_creates_uninitialized_client ________
self = <tests.test_github_client.TestGitHubAppClientInit object at 0x7f98f94379d0>
def test_init_creates_uninitialized_client(self):
"""Test that __init__ creates an uninitialized client."""
> from stampbot.github_client import GitHubAppClient
tests/test_github_client.py:19:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
stampbot/github_client.py:15: in <module>
from stampbot.metrics import (
stampbot/metrics.py:102: in <module>
pr_dismissal_duration_seconds = Histogram(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:625: in __init__
self._prepare_buckets(buckets)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <[AttributeError("'Histogram' object has no attribute '_name'") raised in repr()] Histogram object at 0x7f98f772d370>
source_buckets = (0.1, -0.75, 0.5, 1.0, 2.5, 5.0, ...)
def _prepare_buckets(self, source_buckets: Sequence[Union[float, str]]) -> None:
buckets = [float(b) for b in source_buckets]
if buckets != sorted(buckets):
# This is probably an error on the part of the user,
# so raise rather than sorting for them.
> raise ValueError('Buckets not in sorted order')
E ValueError: Buckets not in sorted order
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:643: ValueError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGitHubAppClientInit::test_init_creates_uninitialized_client - ValueError: Buckets not in sorted order
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 35 passed, 3 deselected in 0.66s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/NumberReplacer, occurrence: 92
--- mutation diff ---
--- astampbot/metrics.py
+++ bstampbot/metrics.py
@@ -102,7 +102,7 @@
pr_dismissal_duration_seconds = Histogram(
"stampbot_pr_dismissal_duration_seconds",
"PR dismissal operation duration in seconds",
- buckets=(0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+ buckets=(0.1, 0.25, 1.5, 1.0, 2.5, 5.0, 10.0),
)
# =============================================================================...................................F
=================================== FAILURES ===================================
________ TestGitHubAppClientInit.test_init_creates_uninitialized_client ________
self = <tests.test_github_client.TestGitHubAppClientInit object at 0x7f1c4711f9d0>
def test_init_creates_uninitialized_client(self):
"""Test that __init__ creates an uninitialized client."""
> from stampbot.github_client import GitHubAppClient
tests/test_github_client.py:19:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
stampbot/github_client.py:15: in <module>
from stampbot.metrics import (
stampbot/metrics.py:102: in <module>
pr_dismissal_duration_seconds = Histogram(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:625: in __init__
self._prepare_buckets(buckets)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <[AttributeError("'Histogram' object has no attribute '_name'") raised in repr()] Histogram object at 0x7f1c4533d370>
source_buckets = (0.1, 0.25, 1.5, 1.0, 2.5, 5.0, ...)
def _prepare_buckets(self, source_buckets: Sequence[Union[float, str]]) -> None:
buckets = [float(b) for b in source_buckets]
if buckets != sorted(buckets):
# This is probably an error on the part of the user,
# so raise rather than sorting for them.
> raise ValueError('Buckets not in sorted order')
E ValueError: Buckets not in sorted order
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:643: ValueError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGitHubAppClientInit::test_init_creates_uninitialized_client - ValueError: Buckets not in sorted order
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 35 passed, 3 deselected in 0.66s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/NumberReplacer, occurrence: 93
--- mutation diff ---
--- astampbot/metrics.py
+++ bstampbot/metrics.py
@@ -102,7 +102,7 @@
pr_dismissal_duration_seconds = Histogram(
"stampbot_pr_dismissal_duration_seconds",
"PR dismissal operation duration in seconds",
- buckets=(0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+ buckets=(0.1, 0.25, -0.5, 1.0, 2.5, 5.0, 10.0),
)
# =============================================================================...................................F
=================================== FAILURES ===================================
________ TestGitHubAppClientInit.test_init_creates_uninitialized_client ________
self = <tests.test_github_client.TestGitHubAppClientInit object at 0x7f3ec78d79d0>
def test_init_creates_uninitialized_client(self):
"""Test that __init__ creates an uninitialized client."""
> from stampbot.github_client import GitHubAppClient
tests/test_github_client.py:19:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
stampbot/github_client.py:15: in <module>
from stampbot.metrics import (
stampbot/metrics.py:102: in <module>
pr_dismissal_duration_seconds = Histogram(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:625: in __init__
self._prepare_buckets(buckets)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <[AttributeError("'Histogram' object has no attribute '_name'") raised in repr()] Histogram object at 0x7f3ec5c11370>
source_buckets = (0.1, 0.25, -0.5, 1.0, 2.5, 5.0, ...)
def _prepare_buckets(self, source_buckets: Sequence[Union[float, str]]) -> None:
buckets = [float(b) for b in source_buckets]
if buckets != sorted(buckets):
# This is probably an error on the part of the user,
# so raise rather than sorting for them.
> raise ValueError('Buckets not in sorted order')
E ValueError: Buckets not in sorted order
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:643: ValueError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGitHubAppClientInit::test_init_creates_uninitialized_client - ValueError: Buckets not in sorted order
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 35 passed, 3 deselected in 0.67s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/NumberReplacer, occurrence: 94
--- mutation diff ---
--- astampbot/metrics.py
+++ bstampbot/metrics.py
@@ -102,7 +102,7 @@
pr_dismissal_duration_seconds = Histogram(
"stampbot_pr_dismissal_duration_seconds",
"PR dismissal operation duration in seconds",
- buckets=(0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+ buckets=(0.1, 0.25, 0.5, 2.0, 2.5, 5.0, 10.0),
)
# =============================================================================........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/NumberReplacer, occurrence: 95
--- mutation diff ---
--- astampbot/metrics.py
+++ bstampbot/metrics.py
@@ -102,7 +102,7 @@
pr_dismissal_duration_seconds = Histogram(
"stampbot_pr_dismissal_duration_seconds",
"PR dismissal operation duration in seconds",
- buckets=(0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+ buckets=(0.1, 0.25, 0.5, 0.0, 2.5, 5.0, 10.0),
)
# =============================================================================...................................F
=================================== FAILURES ===================================
________ TestGitHubAppClientInit.test_init_creates_uninitialized_client ________
self = <tests.test_github_client.TestGitHubAppClientInit object at 0x7fe121d4b9d0>
def test_init_creates_uninitialized_client(self):
"""Test that __init__ creates an uninitialized client."""
> from stampbot.github_client import GitHubAppClient
tests/test_github_client.py:19:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
stampbot/github_client.py:15: in <module>
from stampbot.metrics import (
stampbot/metrics.py:102: in <module>
pr_dismissal_duration_seconds = Histogram(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:625: in __init__
self._prepare_buckets(buckets)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <[AttributeError("'Histogram' object has no attribute '_name'") raised in repr()] Histogram object at 0x7fe12001d370>
source_buckets = (0.1, 0.25, 0.5, 0.0, 2.5, 5.0, ...)
def _prepare_buckets(self, source_buckets: Sequence[Union[float, str]]) -> None:
buckets = [float(b) for b in source_buckets]
if buckets != sorted(buckets):
# This is probably an error on the part of the user,
# so raise rather than sorting for them.
> raise ValueError('Buckets not in sorted order')
E ValueError: Buckets not in sorted order
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:643: ValueError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGitHubAppClientInit::test_init_creates_uninitialized_client - ValueError: Buckets not in sorted order
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 35 passed, 3 deselected in 0.66s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/NumberReplacer, occurrence: 96
--- mutation diff ---
--- astampbot/metrics.py
+++ bstampbot/metrics.py
@@ -102,7 +102,7 @@
pr_dismissal_duration_seconds = Histogram(
"stampbot_pr_dismissal_duration_seconds",
"PR dismissal operation duration in seconds",
- buckets=(0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+ buckets=(0.1, 0.25, 0.5, 1.0, 3.5, 5.0, 10.0),
)
# =============================================================================........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.63s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/NumberReplacer, occurrence: 97
--- mutation diff ---
--- astampbot/metrics.py
+++ bstampbot/metrics.py
@@ -102,7 +102,7 @@
pr_dismissal_duration_seconds = Histogram(
"stampbot_pr_dismissal_duration_seconds",
"PR dismissal operation duration in seconds",
- buckets=(0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+ buckets=(0.1, 0.25, 0.5, 1.0, 1.5, 5.0, 10.0),
)
# =============================================================================........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.62s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/NumberReplacer, occurrence: 98
--- mutation diff ---
--- astampbot/metrics.py
+++ bstampbot/metrics.py
@@ -102,7 +102,7 @@
pr_dismissal_duration_seconds = Histogram(
"stampbot_pr_dismissal_duration_seconds",
"PR dismissal operation duration in seconds",
- buckets=(0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+ buckets=(0.1, 0.25, 0.5, 1.0, 2.5, 6.0, 10.0),
)
# =============================================================================........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/NumberReplacer, occurrence: 99
--- mutation diff ---
--- astampbot/metrics.py
+++ bstampbot/metrics.py
@@ -102,7 +102,7 @@
pr_dismissal_duration_seconds = Histogram(
"stampbot_pr_dismissal_duration_seconds",
"PR dismissal operation duration in seconds",
- buckets=(0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+ buckets=(0.1, 0.25, 0.5, 1.0, 2.5, 4.0, 10.0),
)
# =============================================================================........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.60s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/NumberReplacer, occurrence: 100
--- mutation diff ---
--- astampbot/metrics.py
+++ bstampbot/metrics.py
@@ -102,7 +102,7 @@
pr_dismissal_duration_seconds = Histogram(
"stampbot_pr_dismissal_duration_seconds",
"PR dismissal operation duration in seconds",
- buckets=(0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+ buckets=(0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 11.0),
)
# =============================================================================........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.62s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/NumberReplacer, occurrence: 101
--- mutation diff ---
--- astampbot/metrics.py
+++ bstampbot/metrics.py
@@ -102,7 +102,7 @@
pr_dismissal_duration_seconds = Histogram(
"stampbot_pr_dismissal_duration_seconds",
"PR dismissal operation duration in seconds",
- buckets=(0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+ buckets=(0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 9.0),
)
# =============================================================================........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.65s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/NumberReplacer, occurrence: 102
--- mutation diff ---
--- astampbot/metrics.py
+++ bstampbot/metrics.py
@@ -132,7 +132,7 @@
"stampbot_github_api_request_duration_seconds",
"GitHub API request duration in seconds",
["operation"],
- buckets=(0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
+ buckets=( 1.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
)
github_api_rate_limit_remaining = Gauge(...................................F
=================================== FAILURES ===================================
________ TestGitHubAppClientInit.test_init_creates_uninitialized_client ________
self = <tests.test_github_client.TestGitHubAppClientInit object at 0x7f450662f9d0>
def test_init_creates_uninitialized_client(self):
"""Test that __init__ creates an uninitialized client."""
> from stampbot.github_client import GitHubAppClient
tests/test_github_client.py:19:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
stampbot/github_client.py:15: in <module>
from stampbot.metrics import (
stampbot/metrics.py:131: in <module>
github_api_request_duration_seconds = Histogram(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:625: in __init__
self._prepare_buckets(buckets)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <[AttributeError("'Histogram' object has no attribute '_name'") raised in repr()] Histogram object at 0x7f4504949150>
source_buckets = (1.05, 0.1, 0.25, 0.5, 1.0, 2.5, ...)
def _prepare_buckets(self, source_buckets: Sequence[Union[float, str]]) -> None:
buckets = [float(b) for b in source_buckets]
if buckets != sorted(buckets):
# This is probably an error on the part of the user,
# so raise rather than sorting for them.
> raise ValueError('Buckets not in sorted order')
E ValueError: Buckets not in sorted order
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:643: ValueError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGitHubAppClientInit::test_init_creates_uninitialized_client - ValueError: Buckets not in sorted order
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 35 passed, 3 deselected in 0.67s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/NumberReplacer, occurrence: 103
--- mutation diff ---
--- astampbot/metrics.py
+++ bstampbot/metrics.py
@@ -132,7 +132,7 @@
"stampbot_github_api_request_duration_seconds",
"GitHub API request duration in seconds",
["operation"],
- buckets=(0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
+ buckets=( -0.95, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
)
github_api_rate_limit_remaining = Gauge(........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/NumberReplacer, occurrence: 104
--- mutation diff ---
--- astampbot/metrics.py
+++ bstampbot/metrics.py
@@ -132,7 +132,7 @@
"stampbot_github_api_request_duration_seconds",
"GitHub API request duration in seconds",
["operation"],
- buckets=(0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
+ buckets=(0.05, 1.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
)
github_api_rate_limit_remaining = Gauge(...................................F
=================================== FAILURES ===================================
________ TestGitHubAppClientInit.test_init_creates_uninitialized_client ________
self = <tests.test_github_client.TestGitHubAppClientInit object at 0x7f10142439d0>
def test_init_creates_uninitialized_client(self):
"""Test that __init__ creates an uninitialized client."""
> from stampbot.github_client import GitHubAppClient
tests/test_github_client.py:19:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
stampbot/github_client.py:15: in <module>
from stampbot.metrics import (
stampbot/metrics.py:131: in <module>
github_api_request_duration_seconds = Histogram(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:625: in __init__
self._prepare_buckets(buckets)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <[AttributeError("'Histogram' object has no attribute '_name'") raised in repr()] Histogram object at 0x7f1012535150>
source_buckets = (0.05, 1.1, 0.25, 0.5, 1.0, 2.5, ...)
def _prepare_buckets(self, source_buckets: Sequence[Union[float, str]]) -> None:
buckets = [float(b) for b in source_buckets]
if buckets != sorted(buckets):
# This is probably an error on the part of the user,
# so raise rather than sorting for them.
> raise ValueError('Buckets not in sorted order')
E ValueError: Buckets not in sorted order
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:643: ValueError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGitHubAppClientInit::test_init_creates_uninitialized_client - ValueError: Buckets not in sorted order
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 35 passed, 3 deselected in 0.70s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/NumberReplacer, occurrence: 105
--- mutation diff ---
--- astampbot/metrics.py
+++ bstampbot/metrics.py
@@ -132,7 +132,7 @@
"stampbot_github_api_request_duration_seconds",
"GitHub API request duration in seconds",
["operation"],
- buckets=(0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
+ buckets=(0.05, -0.9, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
)
github_api_rate_limit_remaining = Gauge(...................................F
=================================== FAILURES ===================================
________ TestGitHubAppClientInit.test_init_creates_uninitialized_client ________
self = <tests.test_github_client.TestGitHubAppClientInit object at 0x7fb67d9af9d0>
def test_init_creates_uninitialized_client(self):
"""Test that __init__ creates an uninitialized client."""
> from stampbot.github_client import GitHubAppClient
tests/test_github_client.py:19:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
stampbot/github_client.py:15: in <module>
from stampbot.metrics import (
stampbot/metrics.py:131: in <module>
github_api_request_duration_seconds = Histogram(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:625: in __init__
self._prepare_buckets(buckets)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <[AttributeError("'Histogram' object has no attribute '_name'") raised in repr()] Histogram object at 0x7fb67bc39150>
source_buckets = (0.05, -0.9, 0.25, 0.5, 1.0, 2.5, ...)
def _prepare_buckets(self, source_buckets: Sequence[Union[float, str]]) -> None:
buckets = [float(b) for b in source_buckets]
if buckets != sorted(buckets):
# This is probably an error on the part of the user,
# so raise rather than sorting for them.
> raise ValueError('Buckets not in sorted order')
E ValueError: Buckets not in sorted order
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:643: ValueError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGitHubAppClientInit::test_init_creates_uninitialized_client - ValueError: Buckets not in sorted order
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 35 passed, 3 deselected in 0.66s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/NumberReplacer, occurrence: 106
--- mutation diff ---
--- astampbot/metrics.py
+++ bstampbot/metrics.py
@@ -132,7 +132,7 @@
"stampbot_github_api_request_duration_seconds",
"GitHub API request duration in seconds",
["operation"],
- buckets=(0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
+ buckets=(0.05, 0.1, 1.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
)
github_api_rate_limit_remaining = Gauge(...................................F
=================================== FAILURES ===================================
________ TestGitHubAppClientInit.test_init_creates_uninitialized_client ________
self = <tests.test_github_client.TestGitHubAppClientInit object at 0x7fb547c479d0>
def test_init_creates_uninitialized_client(self):
"""Test that __init__ creates an uninitialized client."""
> from stampbot.github_client import GitHubAppClient
tests/test_github_client.py:19:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
stampbot/github_client.py:15: in <module>
from stampbot.metrics import (
stampbot/metrics.py:131: in <module>
github_api_request_duration_seconds = Histogram(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:625: in __init__
self._prepare_buckets(buckets)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <[AttributeError("'Histogram' object has no attribute '_name'") raised in repr()] Histogram object at 0x7fb545f49150>
source_buckets = (0.05, 0.1, 1.25, 0.5, 1.0, 2.5, ...)
def _prepare_buckets(self, source_buckets: Sequence[Union[float, str]]) -> None:
buckets = [float(b) for b in source_buckets]
if buckets != sorted(buckets):
# This is probably an error on the part of the user,
# so raise rather than sorting for them.
> raise ValueError('Buckets not in sorted order')
E ValueError: Buckets not in sorted order
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:643: ValueError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGitHubAppClientInit::test_init_creates_uninitialized_client - ValueError: Buckets not in sorted order
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 35 passed, 3 deselected in 0.67s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/NumberReplacer, occurrence: 107
--- mutation diff ---
--- astampbot/metrics.py
+++ bstampbot/metrics.py
@@ -132,7 +132,7 @@
"stampbot_github_api_request_duration_seconds",
"GitHub API request duration in seconds",
["operation"],
- buckets=(0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
+ buckets=(0.05, 0.1, -0.75, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
)
github_api_rate_limit_remaining = Gauge(...................................F
=================================== FAILURES ===================================
________ TestGitHubAppClientInit.test_init_creates_uninitialized_client ________
self = <tests.test_github_client.TestGitHubAppClientInit object at 0x7fe64d8eb9d0>
def test_init_creates_uninitialized_client(self):
"""Test that __init__ creates an uninitialized client."""
> from stampbot.github_client import GitHubAppClient
tests/test_github_client.py:19:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
stampbot/github_client.py:15: in <module>
from stampbot.metrics import (
stampbot/metrics.py:131: in <module>
github_api_request_duration_seconds = Histogram(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:625: in __init__
self._prepare_buckets(buckets)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <[AttributeError("'Histogram' object has no attribute '_name'") raised in repr()] Histogram object at 0x7fe64bb4d150>
source_buckets = (0.05, 0.1, -0.75, 0.5, 1.0, 2.5, ...)
def _prepare_buckets(self, source_buckets: Sequence[Union[float, str]]) -> None:
buckets = [float(b) for b in source_buckets]
if buckets != sorted(buckets):
# This is probably an error on the part of the user,
# so raise rather than sorting for them.
> raise ValueError('Buckets not in sorted order')
E ValueError: Buckets not in sorted order
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:643: ValueError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGitHubAppClientInit::test_init_creates_uninitialized_client - ValueError: Buckets not in sorted order
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 35 passed, 3 deselected in 0.66s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/NumberReplacer, occurrence: 108
--- mutation diff ---
--- astampbot/metrics.py
+++ bstampbot/metrics.py
@@ -132,7 +132,7 @@
"stampbot_github_api_request_duration_seconds",
"GitHub API request duration in seconds",
["operation"],
- buckets=(0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
+ buckets=(0.05, 0.1, 0.25, 1.5, 1.0, 2.5, 5.0, 10.0, 30.0),
)
github_api_rate_limit_remaining = Gauge(...................................F
=================================== FAILURES ===================================
________ TestGitHubAppClientInit.test_init_creates_uninitialized_client ________
self = <tests.test_github_client.TestGitHubAppClientInit object at 0x7f38fb9b39d0>
def test_init_creates_uninitialized_client(self):
"""Test that __init__ creates an uninitialized client."""
> from stampbot.github_client import GitHubAppClient
tests/test_github_client.py:19:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
stampbot/github_client.py:15: in <module>
from stampbot.metrics import (
stampbot/metrics.py:131: in <module>
github_api_request_duration_seconds = Histogram(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:625: in __init__
self._prepare_buckets(buckets)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <[AttributeError("'Histogram' object has no attribute '_name'") raised in repr()] Histogram object at 0x7f38f9c45150>
source_buckets = (0.05, 0.1, 0.25, 1.5, 1.0, 2.5, ...)
def _prepare_buckets(self, source_buckets: Sequence[Union[float, str]]) -> None:
buckets = [float(b) for b in source_buckets]
if buckets != sorted(buckets):
# This is probably an error on the part of the user,
# so raise rather than sorting for them.
> raise ValueError('Buckets not in sorted order')
E ValueError: Buckets not in sorted order
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:643: ValueError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGitHubAppClientInit::test_init_creates_uninitialized_client - ValueError: Buckets not in sorted order
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 35 passed, 3 deselected in 0.66s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/NumberReplacer, occurrence: 109
--- mutation diff ---
--- astampbot/metrics.py
+++ bstampbot/metrics.py
@@ -132,7 +132,7 @@
"stampbot_github_api_request_duration_seconds",
"GitHub API request duration in seconds",
["operation"],
- buckets=(0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
+ buckets=(0.05, 0.1, 0.25, -0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
)
github_api_rate_limit_remaining = Gauge(...................................F
=================================== FAILURES ===================================
________ TestGitHubAppClientInit.test_init_creates_uninitialized_client ________
self = <tests.test_github_client.TestGitHubAppClientInit object at 0x7f5ee53ef9d0>
def test_init_creates_uninitialized_client(self):
"""Test that __init__ creates an uninitialized client."""
> from stampbot.github_client import GitHubAppClient
tests/test_github_client.py:19:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
stampbot/github_client.py:15: in <module>
from stampbot.metrics import (
stampbot/metrics.py:131: in <module>
github_api_request_duration_seconds = Histogram(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:625: in __init__
self._prepare_buckets(buckets)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <[AttributeError("'Histogram' object has no attribute '_name'") raised in repr()] Histogram object at 0x7f5ee3655150>
source_buckets = (0.05, 0.1, 0.25, -0.5, 1.0, 2.5, ...)
def _prepare_buckets(self, source_buckets: Sequence[Union[float, str]]) -> None:
buckets = [float(b) for b in source_buckets]
if buckets != sorted(buckets):
# This is probably an error on the part of the user,
# so raise rather than sorting for them.
> raise ValueError('Buckets not in sorted order')
E ValueError: Buckets not in sorted order
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:643: ValueError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGitHubAppClientInit::test_init_creates_uninitialized_client - ValueError: Buckets not in sorted order
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 35 passed, 3 deselected in 0.68s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/NumberReplacer, occurrence: 110
--- mutation diff ---
--- astampbot/metrics.py
+++ bstampbot/metrics.py
@@ -132,7 +132,7 @@
"stampbot_github_api_request_duration_seconds",
"GitHub API request duration in seconds",
["operation"],
- buckets=(0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
+ buckets=(0.05, 0.1, 0.25, 0.5, 2.0, 2.5, 5.0, 10.0, 30.0),
)
github_api_rate_limit_remaining = Gauge(........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.62s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/NumberReplacer, occurrence: 111
--- mutation diff ---
--- astampbot/metrics.py
+++ bstampbot/metrics.py
@@ -132,7 +132,7 @@
"stampbot_github_api_request_duration_seconds",
"GitHub API request duration in seconds",
["operation"],
- buckets=(0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
+ buckets=(0.05, 0.1, 0.25, 0.5, 0.0, 2.5, 5.0, 10.0, 30.0),
)
github_api_rate_limit_remaining = Gauge(...................................F
=================================== FAILURES ===================================
________ TestGitHubAppClientInit.test_init_creates_uninitialized_client ________
self = <tests.test_github_client.TestGitHubAppClientInit object at 0x7fbe100c39d0>
def test_init_creates_uninitialized_client(self):
"""Test that __init__ creates an uninitialized client."""
> from stampbot.github_client import GitHubAppClient
tests/test_github_client.py:19:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
stampbot/github_client.py:15: in <module>
from stampbot.metrics import (
stampbot/metrics.py:131: in <module>
github_api_request_duration_seconds = Histogram(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:625: in __init__
self._prepare_buckets(buckets)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <[AttributeError("'Histogram' object has no attribute '_name'") raised in repr()] Histogram object at 0x7fbe0e345150>
source_buckets = (0.05, 0.1, 0.25, 0.5, 0.0, 2.5, ...)
def _prepare_buckets(self, source_buckets: Sequence[Union[float, str]]) -> None:
buckets = [float(b) for b in source_buckets]
if buckets != sorted(buckets):
# This is probably an error on the part of the user,
# so raise rather than sorting for them.
> raise ValueError('Buckets not in sorted order')
E ValueError: Buckets not in sorted order
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:643: ValueError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGitHubAppClientInit::test_init_creates_uninitialized_client - ValueError: Buckets not in sorted order
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 35 passed, 3 deselected in 0.65s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/NumberReplacer, occurrence: 112
--- mutation diff ---
--- astampbot/metrics.py
+++ bstampbot/metrics.py
@@ -132,7 +132,7 @@
"stampbot_github_api_request_duration_seconds",
"GitHub API request duration in seconds",
["operation"],
- buckets=(0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
+ buckets=(0.05, 0.1, 0.25, 0.5, 1.0, 3.5, 5.0, 10.0, 30.0),
)
github_api_rate_limit_remaining = Gauge(........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/NumberReplacer, occurrence: 113
--- mutation diff ---
--- astampbot/metrics.py
+++ bstampbot/metrics.py
@@ -132,7 +132,7 @@
"stampbot_github_api_request_duration_seconds",
"GitHub API request duration in seconds",
["operation"],
- buckets=(0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
+ buckets=(0.05, 0.1, 0.25, 0.5, 1.0, 1.5, 5.0, 10.0, 30.0),
)
github_api_rate_limit_remaining = Gauge(........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/NumberReplacer, occurrence: 114
--- mutation diff ---
--- astampbot/metrics.py
+++ bstampbot/metrics.py
@@ -132,7 +132,7 @@
"stampbot_github_api_request_duration_seconds",
"GitHub API request duration in seconds",
["operation"],
- buckets=(0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
+ buckets=(0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 6.0, 10.0, 30.0),
)
github_api_rate_limit_remaining = Gauge(........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.64s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/NumberReplacer, occurrence: 115
--- mutation diff ---
--- astampbot/metrics.py
+++ bstampbot/metrics.py
@@ -132,7 +132,7 @@
"stampbot_github_api_request_duration_seconds",
"GitHub API request duration in seconds",
["operation"],
- buckets=(0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
+ buckets=(0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 4.0, 10.0, 30.0),
)
github_api_rate_limit_remaining = Gauge(........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/NumberReplacer, occurrence: 116
--- mutation diff ---
--- astampbot/metrics.py
+++ bstampbot/metrics.py
@@ -132,7 +132,7 @@
"stampbot_github_api_request_duration_seconds",
"GitHub API request duration in seconds",
["operation"],
- buckets=(0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
+ buckets=(0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 11.0, 30.0),
)
github_api_rate_limit_remaining = Gauge(........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/NumberReplacer, occurrence: 117
--- mutation diff ---
--- astampbot/metrics.py
+++ bstampbot/metrics.py
@@ -132,7 +132,7 @@
"stampbot_github_api_request_duration_seconds",
"GitHub API request duration in seconds",
["operation"],
- buckets=(0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
+ buckets=(0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 9.0, 30.0),
)
github_api_rate_limit_remaining = Gauge(........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.60s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/NumberReplacer, occurrence: 118
--- mutation diff ---
--- astampbot/metrics.py
+++ bstampbot/metrics.py
@@ -132,7 +132,7 @@
"stampbot_github_api_request_duration_seconds",
"GitHub API request duration in seconds",
["operation"],
- buckets=(0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
+ buckets=(0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 31.0),
)
github_api_rate_limit_remaining = Gauge(........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.62s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/NumberReplacer, occurrence: 119
--- mutation diff ---
--- astampbot/metrics.py
+++ bstampbot/metrics.py
@@ -132,7 +132,7 @@
"stampbot_github_api_request_duration_seconds",
"GitHub API request duration in seconds",
["operation"],
- buckets=(0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
+ buckets=(0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 29.0),
)
github_api_rate_limit_remaining = Gauge(........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.62s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceComparisonOperator_Eq_NotEq, occurrence: 0
--- mutation diff ---
--- astampbot/__main__.py
+++ bstampbot/__main__.py
@@ -7,7 +7,7 @@
from stampbot.config import settings
-if __name__ == "__main__":
+if __name__ != "__main__":
uvicorn.run(
"stampbot.main:app",
host=settings.host,........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.60s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceComparisonOperator_Eq_Lt, occurrence: 0
--- mutation diff ---
--- astampbot/__main__.py
+++ bstampbot/__main__.py
@@ -7,7 +7,7 @@
from stampbot.config import settings
-if __name__ == "__main__":
+if __name__ < "__main__":
uvicorn.run(
"stampbot.main:app",
host=settings.host,........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.62s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceComparisonOperator_Eq_LtE, occurrence: 0
--- mutation diff ---
--- astampbot/__main__.py
+++ bstampbot/__main__.py
@@ -7,7 +7,7 @@
from stampbot.config import settings
-if __name__ == "__main__":
+if __name__ <= "__main__":
uvicorn.run(
"stampbot.main:app",
host=settings.host,........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.60s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceComparisonOperator_Eq_Gt, occurrence: 0
--- mutation diff ---
--- astampbot/__main__.py
+++ bstampbot/__main__.py
@@ -7,7 +7,7 @@
from stampbot.config import settings
-if __name__ == "__main__":
+if __name__ > "__main__":
uvicorn.run(
"stampbot.main:app",
host=settings.host,........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.60s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceComparisonOperator_Eq_GtE, occurrence: 0
--- mutation diff ---
--- astampbot/__main__.py
+++ bstampbot/__main__.py
@@ -7,7 +7,7 @@
from stampbot.config import settings
-if __name__ == "__main__":
+if __name__ >= "__main__":
uvicorn.run(
"stampbot.main:app",
host=settings.host,........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.62s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceComparisonOperator_Eq_Is, occurrence: 0
--- mutation diff ---
--- astampbot/__main__.py
+++ bstampbot/__main__.py
@@ -7,7 +7,7 @@
from stampbot.config import settings
-if __name__ == "__main__":
+if __name__ is "__main__":
uvicorn.run(
"stampbot.main:app",
host=settings.host,........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.60s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceComparisonOperator_Eq_IsNot, occurrence: 0
--- mutation diff ---
--- astampbot/__main__.py
+++ bstampbot/__main__.py
@@ -7,7 +7,7 @@
from stampbot.config import settings
-if __name__ == "__main__":
+if __name__ is not "__main__":
uvicorn.run(
"stampbot.main:app",
host=settings.host,........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.70s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/AddNot, occurrence: 0
--- mutation diff ---
--- astampbot/__main__.py
+++ bstampbot/__main__.py
@@ -7,7 +7,7 @@
from stampbot.config import settings
-if __name__ == "__main__":
+if not __name__ == "__main__":
uvicorn.run(
"stampbot.main:app",
host=settings.host,........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceFalseWithTrue, occurrence: 0
--- mutation diff ---
--- astampbot/__main__.py
+++ bstampbot/__main__.py
@@ -13,6 +13,6 @@
host=settings.host,
port=settings.port,
log_level=settings.log_level.lower(),
- reload=False,
+ reload=True,
)
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.62s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceComparisonOperator_Eq_NotEq, occurrence: 0
--- mutation diff ---
--- astampbot/logger.py
+++ bstampbot/logger.py
@@ -33,7 +33,7 @@
"""
log_format = settings.log_format.lower()
- if log_format == "json":
+ if log_format != "json":
return structlog.processors.JSONRenderer()
elif log_format == "console":
return structlog.dev.ConsoleRenderer(colors=True)........................................................................ [ 34%]
...........F
=================================== FAILURES ===================================
________________________ test_get_log_renderer_console _________________________
def test_get_log_renderer_console():
"""Test console renderer selection."""
with patch("stampbot.logger.settings") as mock_settings:
mock_settings.log_format = "console"
from stampbot.logger import _get_log_renderer
renderer = _get_log_renderer()
> assert isinstance(renderer, structlog.dev.ConsoleRenderer)
E AssertionError: assert False
E + where False = isinstance(<structlog.processors.JSONRenderer object at 0x7ffae49f0a50>, <class 'structlog.dev.ConsoleRenderer'>)
E + where <class 'structlog.dev.ConsoleRenderer'> = <module 'structlog.dev' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/dev.py'>.ConsoleRenderer
E + where <module 'structlog.dev' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/dev.py'> = structlog.dev
tests/test_logger.py:46: AssertionError
=========================== short test summary info ============================
FAILED tests/test_logger.py::test_get_log_renderer_console - AssertionError: assert False
+ where False = isinstance(<structlog.processors.JSONRenderer object at 0x7ffae49f0a50>, <class 'structlog.dev.ConsoleRenderer'>)
+ where <class 'structlog.dev.ConsoleRenderer'> = <module 'structlog.dev' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/dev.py'>.ConsoleRenderer
+ where <module 'structlog.dev' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/dev.py'> = structlog.dev
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 83 passed, 3 deselected in 0.80s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceComparisonOperator_Eq_NotEq, occurrence: 1
--- mutation diff ---
--- astampbot/logger.py
+++ bstampbot/logger.py
@@ -35,7 +35,7 @@
if log_format == "json":
return structlog.processors.JSONRenderer()
- elif log_format == "console":
+ elif log_format != "console":
return structlog.dev.ConsoleRenderer(colors=True)
elif log_format == "auto":
# Auto-detect: use JSON in Kubernetes, console otherwise........................................................................ [ 34%]
...........F
=================================== FAILURES ===================================
________________________ test_get_log_renderer_console _________________________
def test_get_log_renderer_console():
"""Test console renderer selection."""
with patch("stampbot.logger.settings") as mock_settings:
mock_settings.log_format = "console"
from stampbot.logger import _get_log_renderer
renderer = _get_log_renderer()
> assert isinstance(renderer, structlog.dev.ConsoleRenderer)
E AssertionError: assert False
E + where False = isinstance(<structlog.processors.JSONRenderer object at 0x7f6a172f8a50>, <class 'structlog.dev.ConsoleRenderer'>)
E + where <class 'structlog.dev.ConsoleRenderer'> = <module 'structlog.dev' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/dev.py'>.ConsoleRenderer
E + where <module 'structlog.dev' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/dev.py'> = structlog.dev
tests/test_logger.py:46: AssertionError
=========================== short test summary info ============================
FAILED tests/test_logger.py::test_get_log_renderer_console - AssertionError: assert False
+ where False = isinstance(<structlog.processors.JSONRenderer object at 0x7f6a172f8a50>, <class 'structlog.dev.ConsoleRenderer'>)
+ where <class 'structlog.dev.ConsoleRenderer'> = <module 'structlog.dev' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/dev.py'>.ConsoleRenderer
+ where <module 'structlog.dev' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/dev.py'> = structlog.dev
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 83 passed, 3 deselected in 0.76s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceComparisonOperator_Eq_NotEq, occurrence: 2
--- mutation diff ---
--- astampbot/logger.py
+++ bstampbot/logger.py
@@ -37,7 +37,7 @@
return structlog.processors.JSONRenderer()
elif log_format == "console":
return structlog.dev.ConsoleRenderer(colors=True)
- elif log_format == "auto":
+ elif log_format != "auto":
# Auto-detect: use JSON in Kubernetes, console otherwise
if is_running_in_kubernetes():
return structlog.processors.JSONRenderer()........................................................................ [ 34%]
.............F
=================================== FAILURES ===================================
_______________________ test_get_log_renderer_auto_local _______________________
def test_get_log_renderer_auto_local():
"""Test auto renderer in local environment."""
with patch("stampbot.logger.settings") as mock_settings:
mock_settings.log_format = "auto"
with patch("stampbot.logger.is_running_in_kubernetes", return_value=False):
from stampbot.logger import _get_log_renderer
renderer = _get_log_renderer()
> assert isinstance(renderer, structlog.dev.ConsoleRenderer)
E AssertionError: assert False
E + where False = isinstance(<structlog.processors.JSONRenderer object at 0x7fb897bf8cd0>, <class 'structlog.dev.ConsoleRenderer'>)
E + where <class 'structlog.dev.ConsoleRenderer'> = <module 'structlog.dev' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/dev.py'>.ConsoleRenderer
E + where <module 'structlog.dev' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/dev.py'> = structlog.dev
tests/test_logger.py:68: AssertionError
=========================== short test summary info ============================
FAILED tests/test_logger.py::test_get_log_renderer_auto_local - AssertionError: assert False
+ where False = isinstance(<structlog.processors.JSONRenderer object at 0x7fb897bf8cd0>, <class 'structlog.dev.ConsoleRenderer'>)
+ where <class 'structlog.dev.ConsoleRenderer'> = <module 'structlog.dev' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/dev.py'>.ConsoleRenderer
+ where <module 'structlog.dev' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/dev.py'> = structlog.dev
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 85 passed, 3 deselected in 0.78s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceComparisonOperator_Eq_Lt, occurrence: 0
--- mutation diff ---
--- astampbot/logger.py
+++ bstampbot/logger.py
@@ -33,7 +33,7 @@
"""
log_format = settings.log_format.lower()
- if log_format == "json":
+ if log_format < "json":
return structlog.processors.JSONRenderer()
elif log_format == "console":
return structlog.dev.ConsoleRenderer(colors=True)........................................................................ [ 34%]
...........F
=================================== FAILURES ===================================
________________________ test_get_log_renderer_console _________________________
def test_get_log_renderer_console():
"""Test console renderer selection."""
with patch("stampbot.logger.settings") as mock_settings:
mock_settings.log_format = "console"
from stampbot.logger import _get_log_renderer
renderer = _get_log_renderer()
> assert isinstance(renderer, structlog.dev.ConsoleRenderer)
E AssertionError: assert False
E + where False = isinstance(<structlog.processors.JSONRenderer object at 0x7ff319100a50>, <class 'structlog.dev.ConsoleRenderer'>)
E + where <class 'structlog.dev.ConsoleRenderer'> = <module 'structlog.dev' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/dev.py'>.ConsoleRenderer
E + where <module 'structlog.dev' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/dev.py'> = structlog.dev
tests/test_logger.py:46: AssertionError
=========================== short test summary info ============================
FAILED tests/test_logger.py::test_get_log_renderer_console - AssertionError: assert False
+ where False = isinstance(<structlog.processors.JSONRenderer object at 0x7ff319100a50>, <class 'structlog.dev.ConsoleRenderer'>)
+ where <class 'structlog.dev.ConsoleRenderer'> = <module 'structlog.dev' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/dev.py'>.ConsoleRenderer
+ where <module 'structlog.dev' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/dev.py'> = structlog.dev
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 83 passed, 3 deselected in 0.78s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceComparisonOperator_Eq_Lt, occurrence: 1
--- mutation diff ---
--- astampbot/logger.py
+++ bstampbot/logger.py
@@ -35,7 +35,7 @@
if log_format == "json":
return structlog.processors.JSONRenderer()
- elif log_format == "console":
+ elif log_format < "console":
return structlog.dev.ConsoleRenderer(colors=True)
elif log_format == "auto":
# Auto-detect: use JSON in Kubernetes, console otherwise........................................................................ [ 34%]
...........F
=================================== FAILURES ===================================
________________________ test_get_log_renderer_console _________________________
def test_get_log_renderer_console():
"""Test console renderer selection."""
with patch("stampbot.logger.settings") as mock_settings:
mock_settings.log_format = "console"
from stampbot.logger import _get_log_renderer
renderer = _get_log_renderer()
> assert isinstance(renderer, structlog.dev.ConsoleRenderer)
E AssertionError: assert False
E + where False = isinstance(<structlog.processors.JSONRenderer object at 0x7f5d8e6eca50>, <class 'structlog.dev.ConsoleRenderer'>)
E + where <class 'structlog.dev.ConsoleRenderer'> = <module 'structlog.dev' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/dev.py'>.ConsoleRenderer
E + where <module 'structlog.dev' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/dev.py'> = structlog.dev
tests/test_logger.py:46: AssertionError
=========================== short test summary info ============================
FAILED tests/test_logger.py::test_get_log_renderer_console - AssertionError: assert False
+ where False = isinstance(<structlog.processors.JSONRenderer object at 0x7f5d8e6eca50>, <class 'structlog.dev.ConsoleRenderer'>)
+ where <class 'structlog.dev.ConsoleRenderer'> = <module 'structlog.dev' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/dev.py'>.ConsoleRenderer
+ where <module 'structlog.dev' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/dev.py'> = structlog.dev
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 83 passed, 3 deselected in 0.77s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceComparisonOperator_Eq_Lt, occurrence: 2
--- mutation diff ---
--- astampbot/logger.py
+++ bstampbot/logger.py
@@ -37,7 +37,7 @@
return structlog.processors.JSONRenderer()
elif log_format == "console":
return structlog.dev.ConsoleRenderer(colors=True)
- elif log_format == "auto":
+ elif log_format < "auto":
# Auto-detect: use JSON in Kubernetes, console otherwise
if is_running_in_kubernetes():
return structlog.processors.JSONRenderer()........................................................................ [ 34%]
.............F
=================================== FAILURES ===================================
_______________________ test_get_log_renderer_auto_local _______________________
def test_get_log_renderer_auto_local():
"""Test auto renderer in local environment."""
with patch("stampbot.logger.settings") as mock_settings:
mock_settings.log_format = "auto"
with patch("stampbot.logger.is_running_in_kubernetes", return_value=False):
from stampbot.logger import _get_log_renderer
renderer = _get_log_renderer()
> assert isinstance(renderer, structlog.dev.ConsoleRenderer)
E AssertionError: assert False
E + where False = isinstance(<structlog.processors.JSONRenderer object at 0x7fadb41d4cd0>, <class 'structlog.dev.ConsoleRenderer'>)
E + where <class 'structlog.dev.ConsoleRenderer'> = <module 'structlog.dev' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/dev.py'>.ConsoleRenderer
E + where <module 'structlog.dev' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/dev.py'> = structlog.dev
tests/test_logger.py:68: AssertionError
=========================== short test summary info ============================
FAILED tests/test_logger.py::test_get_log_renderer_auto_local - AssertionError: assert False
+ where False = isinstance(<structlog.processors.JSONRenderer object at 0x7fadb41d4cd0>, <class 'structlog.dev.ConsoleRenderer'>)
+ where <class 'structlog.dev.ConsoleRenderer'> = <module 'structlog.dev' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/dev.py'>.ConsoleRenderer
+ where <module 'structlog.dev' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/dev.py'> = structlog.dev
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 85 passed, 3 deselected in 0.78s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceComparisonOperator_Eq_LtE, occurrence: 0
--- mutation diff ---
--- astampbot/logger.py
+++ bstampbot/logger.py
@@ -33,7 +33,7 @@
"""
log_format = settings.log_format.lower()
- if log_format == "json":
+ if log_format <= "json":
return structlog.processors.JSONRenderer()
elif log_format == "console":
return structlog.dev.ConsoleRenderer(colors=True)........................................................................ [ 34%]
...........F
=================================== FAILURES ===================================
________________________ test_get_log_renderer_console _________________________
def test_get_log_renderer_console():
"""Test console renderer selection."""
with patch("stampbot.logger.settings") as mock_settings:
mock_settings.log_format = "console"
from stampbot.logger import _get_log_renderer
renderer = _get_log_renderer()
> assert isinstance(renderer, structlog.dev.ConsoleRenderer)
E AssertionError: assert False
E + where False = isinstance(<structlog.processors.JSONRenderer object at 0x7fdebf7f8a50>, <class 'structlog.dev.ConsoleRenderer'>)
E + where <class 'structlog.dev.ConsoleRenderer'> = <module 'structlog.dev' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/dev.py'>.ConsoleRenderer
E + where <module 'structlog.dev' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/dev.py'> = structlog.dev
tests/test_logger.py:46: AssertionError
=========================== short test summary info ============================
FAILED tests/test_logger.py::test_get_log_renderer_console - AssertionError: assert False
+ where False = isinstance(<structlog.processors.JSONRenderer object at 0x7fdebf7f8a50>, <class 'structlog.dev.ConsoleRenderer'>)
+ where <class 'structlog.dev.ConsoleRenderer'> = <module 'structlog.dev' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/dev.py'>.ConsoleRenderer
+ where <module 'structlog.dev' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/dev.py'> = structlog.dev
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 83 passed, 3 deselected in 0.80s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceComparisonOperator_Eq_LtE, occurrence: 1
--- mutation diff ---
--- astampbot/logger.py
+++ bstampbot/logger.py
@@ -35,7 +35,7 @@
if log_format == "json":
return structlog.processors.JSONRenderer()
- elif log_format == "console":
+ elif log_format <= "console":
return structlog.dev.ConsoleRenderer(colors=True)
elif log_format == "auto":
# Auto-detect: use JSON in Kubernetes, console otherwise........................................................................ [ 34%]
............F
=================================== FAILURES ===================================
____________________ test_get_log_renderer_auto_kubernetes _____________________
def test_get_log_renderer_auto_kubernetes():
"""Test auto renderer in Kubernetes environment."""
with patch("stampbot.logger.settings") as mock_settings:
mock_settings.log_format = "auto"
with patch("stampbot.logger.is_running_in_kubernetes", return_value=True):
from stampbot.logger import _get_log_renderer
renderer = _get_log_renderer()
> assert isinstance(renderer, structlog.processors.JSONRenderer)
E AssertionError: assert False
E + where False = isinstance(<structlog.dev.ConsoleRenderer object at 0x7f1134bf0cd0>, <class 'structlog.processors.JSONRenderer'>)
E + where <class 'structlog.processors.JSONRenderer'> = <module 'structlog.processors' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/processors.py'>.JSONRenderer
E + where <module 'structlog.processors' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/processors.py'> = structlog.processors
tests/test_logger.py:57: AssertionError
=========================== short test summary info ============================
FAILED tests/test_logger.py::test_get_log_renderer_auto_kubernetes - AssertionError: assert False
+ where False = isinstance(<structlog.dev.ConsoleRenderer object at 0x7f1134bf0cd0>, <class 'structlog.processors.JSONRenderer'>)
+ where <class 'structlog.processors.JSONRenderer'> = <module 'structlog.processors' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/processors.py'>.JSONRenderer
+ where <module 'structlog.processors' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/processors.py'> = structlog.processors
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 84 passed, 3 deselected in 0.77s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceComparisonOperator_Eq_LtE, occurrence: 2
--- mutation diff ---
--- astampbot/logger.py
+++ bstampbot/logger.py
@@ -37,7 +37,7 @@
return structlog.processors.JSONRenderer()
elif log_format == "console":
return structlog.dev.ConsoleRenderer(colors=True)
- elif log_format == "auto":
+ elif log_format <= "auto":
# Auto-detect: use JSON in Kubernetes, console otherwise
if is_running_in_kubernetes():
return structlog.processors.JSONRenderer()........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceComparisonOperator_Eq_Gt, occurrence: 0
--- mutation diff ---
--- astampbot/logger.py
+++ bstampbot/logger.py
@@ -33,7 +33,7 @@
"""
log_format = settings.log_format.lower()
- if log_format == "json":
+ if log_format > "json":
return structlog.processors.JSONRenderer()
elif log_format == "console":
return structlog.dev.ConsoleRenderer(colors=True)........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.60s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceComparisonOperator_Eq_Gt, occurrence: 1
--- mutation diff ---
--- astampbot/logger.py
+++ bstampbot/logger.py
@@ -35,7 +35,7 @@
if log_format == "json":
return structlog.processors.JSONRenderer()
- elif log_format == "console":
+ elif log_format > "console":
return structlog.dev.ConsoleRenderer(colors=True)
elif log_format == "auto":
# Auto-detect: use JSON in Kubernetes, console otherwise........................................................................ [ 34%]
...........F
=================================== FAILURES ===================================
________________________ test_get_log_renderer_console _________________________
def test_get_log_renderer_console():
"""Test console renderer selection."""
with patch("stampbot.logger.settings") as mock_settings:
mock_settings.log_format = "console"
from stampbot.logger import _get_log_renderer
renderer = _get_log_renderer()
> assert isinstance(renderer, structlog.dev.ConsoleRenderer)
E AssertionError: assert False
E + where False = isinstance(<structlog.processors.JSONRenderer object at 0x7f4ec99e4a50>, <class 'structlog.dev.ConsoleRenderer'>)
E + where <class 'structlog.dev.ConsoleRenderer'> = <module 'structlog.dev' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/dev.py'>.ConsoleRenderer
E + where <module 'structlog.dev' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/dev.py'> = structlog.dev
tests/test_logger.py:46: AssertionError
=========================== short test summary info ============================
FAILED tests/test_logger.py::test_get_log_renderer_console - AssertionError: assert False
+ where False = isinstance(<structlog.processors.JSONRenderer object at 0x7f4ec99e4a50>, <class 'structlog.dev.ConsoleRenderer'>)
+ where <class 'structlog.dev.ConsoleRenderer'> = <module 'structlog.dev' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/dev.py'>.ConsoleRenderer
+ where <module 'structlog.dev' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/dev.py'> = structlog.dev
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 83 passed, 3 deselected in 0.80s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceComparisonOperator_Eq_Gt, occurrence: 2
--- mutation diff ---
--- astampbot/logger.py
+++ bstampbot/logger.py
@@ -37,7 +37,7 @@
return structlog.processors.JSONRenderer()
elif log_format == "console":
return structlog.dev.ConsoleRenderer(colors=True)
- elif log_format == "auto":
+ elif log_format > "auto":
# Auto-detect: use JSON in Kubernetes, console otherwise
if is_running_in_kubernetes():
return structlog.processors.JSONRenderer()........................................................................ [ 34%]
.............F
=================================== FAILURES ===================================
_______________________ test_get_log_renderer_auto_local _______________________
def test_get_log_renderer_auto_local():
"""Test auto renderer in local environment."""
with patch("stampbot.logger.settings") as mock_settings:
mock_settings.log_format = "auto"
with patch("stampbot.logger.is_running_in_kubernetes", return_value=False):
from stampbot.logger import _get_log_renderer
renderer = _get_log_renderer()
> assert isinstance(renderer, structlog.dev.ConsoleRenderer)
E AssertionError: assert False
E + where False = isinstance(<structlog.processors.JSONRenderer object at 0x7ff25a9f4cd0>, <class 'structlog.dev.ConsoleRenderer'>)
E + where <class 'structlog.dev.ConsoleRenderer'> = <module 'structlog.dev' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/dev.py'>.ConsoleRenderer
E + where <module 'structlog.dev' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/dev.py'> = structlog.dev
tests/test_logger.py:68: AssertionError
=========================== short test summary info ============================
FAILED tests/test_logger.py::test_get_log_renderer_auto_local - AssertionError: assert False
+ where False = isinstance(<structlog.processors.JSONRenderer object at 0x7ff25a9f4cd0>, <class 'structlog.dev.ConsoleRenderer'>)
+ where <class 'structlog.dev.ConsoleRenderer'> = <module 'structlog.dev' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/dev.py'>.ConsoleRenderer
+ where <module 'structlog.dev' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/dev.py'> = structlog.dev
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 85 passed, 3 deselected in 0.78s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceComparisonOperator_Eq_GtE, occurrence: 0
--- mutation diff ---
--- astampbot/logger.py
+++ bstampbot/logger.py
@@ -33,7 +33,7 @@
"""
log_format = settings.log_format.lower()
- if log_format == "json":
+ if log_format >= "json":
return structlog.processors.JSONRenderer()
elif log_format == "console":
return structlog.dev.ConsoleRenderer(colors=True)........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceComparisonOperator_Eq_GtE, occurrence: 1
--- mutation diff ---
--- astampbot/logger.py
+++ bstampbot/logger.py
@@ -35,7 +35,7 @@
if log_format == "json":
return structlog.processors.JSONRenderer()
- elif log_format == "console":
+ elif log_format >= "console":
return structlog.dev.ConsoleRenderer(colors=True)
elif log_format == "auto":
# Auto-detect: use JSON in Kubernetes, console otherwise........................................................................ [ 34%]
..............F
=================================== FAILURES ===================================
_____________________ test_get_log_renderer_unknown_format _____________________
def test_get_log_renderer_unknown_format():
"""Test fallback to JSON for unknown format."""
with patch("stampbot.logger.settings") as mock_settings:
mock_settings.log_format = "unknown_format"
from stampbot.logger import _get_log_renderer
renderer = _get_log_renderer()
> assert isinstance(renderer, structlog.processors.JSONRenderer)
E AssertionError: assert False
E + where False = isinstance(<structlog.dev.ConsoleRenderer object at 0x7f0378a609d0>, <class 'structlog.processors.JSONRenderer'>)
E + where <class 'structlog.processors.JSONRenderer'> = <module 'structlog.processors' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/processors.py'>.JSONRenderer
E + where <module 'structlog.processors' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/processors.py'> = structlog.processors
tests/test_logger.py:78: AssertionError
=========================== short test summary info ============================
FAILED tests/test_logger.py::test_get_log_renderer_unknown_format - AssertionError: assert False
+ where False = isinstance(<structlog.dev.ConsoleRenderer object at 0x7f0378a609d0>, <class 'structlog.processors.JSONRenderer'>)
+ where <class 'structlog.processors.JSONRenderer'> = <module 'structlog.processors' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/processors.py'>.JSONRenderer
+ where <module 'structlog.processors' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/processors.py'> = structlog.processors
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 86 passed, 3 deselected in 0.78s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceComparisonOperator_Eq_GtE, occurrence: 2
--- mutation diff ---
--- astampbot/logger.py
+++ bstampbot/logger.py
@@ -37,7 +37,7 @@
return structlog.processors.JSONRenderer()
elif log_format == "console":
return structlog.dev.ConsoleRenderer(colors=True)
- elif log_format == "auto":
+ elif log_format >= "auto":
# Auto-detect: use JSON in Kubernetes, console otherwise
if is_running_in_kubernetes():
return structlog.processors.JSONRenderer()........................................................................ [ 34%]
..............F
=================================== FAILURES ===================================
_____________________ test_get_log_renderer_unknown_format _____________________
def test_get_log_renderer_unknown_format():
"""Test fallback to JSON for unknown format."""
with patch("stampbot.logger.settings") as mock_settings:
mock_settings.log_format = "unknown_format"
from stampbot.logger import _get_log_renderer
renderer = _get_log_renderer()
> assert isinstance(renderer, structlog.processors.JSONRenderer)
E AssertionError: assert False
E + where False = isinstance(<structlog.dev.ConsoleRenderer object at 0x7fd909f549d0>, <class 'structlog.processors.JSONRenderer'>)
E + where <class 'structlog.processors.JSONRenderer'> = <module 'structlog.processors' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/processors.py'>.JSONRenderer
E + where <module 'structlog.processors' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/processors.py'> = structlog.processors
tests/test_logger.py:78: AssertionError
=========================== short test summary info ============================
FAILED tests/test_logger.py::test_get_log_renderer_unknown_format - AssertionError: assert False
+ where False = isinstance(<structlog.dev.ConsoleRenderer object at 0x7fd909f549d0>, <class 'structlog.processors.JSONRenderer'>)
+ where <class 'structlog.processors.JSONRenderer'> = <module 'structlog.processors' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/processors.py'>.JSONRenderer
+ where <module 'structlog.processors' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/processors.py'> = structlog.processors
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 86 passed, 3 deselected in 0.76s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceComparisonOperator_Eq_Is, occurrence: 0
--- mutation diff ---
--- astampbot/logger.py
+++ bstampbot/logger.py
@@ -33,7 +33,7 @@
"""
log_format = settings.log_format.lower()
- if log_format == "json":
+ if log_format is "json":
return structlog.processors.JSONRenderer()
elif log_format == "console":
return structlog.dev.ConsoleRenderer(colors=True)........................................................................ [ 34%]
........................................................................ [ 68%]
................................................................... [100%]
=============================== warnings summary ===============================
stampbot/logger.py:36
/home/runner/work/stampbot/stampbot/stampbot/logger.py:36: SyntaxWarning: "is" with 'str' literal. Did you mean "=="?
if log_format is "json":
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
211 passed, 3 deselected, 1 warning in 1.73s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceComparisonOperator_Eq_Is, occurrence: 1
--- mutation diff ---
--- astampbot/logger.py
+++ bstampbot/logger.py
@@ -35,7 +35,7 @@
if log_format == "json":
return structlog.processors.JSONRenderer()
- elif log_format == "console":
+ elif log_format is "console":
return structlog.dev.ConsoleRenderer(colors=True)
elif log_format == "auto":
# Auto-detect: use JSON in Kubernetes, console otherwise........................................................................ [ 34%]
...........F
=================================== FAILURES ===================================
________________________ test_get_log_renderer_console _________________________
def test_get_log_renderer_console():
"""Test console renderer selection."""
with patch("stampbot.logger.settings") as mock_settings:
mock_settings.log_format = "console"
from stampbot.logger import _get_log_renderer
renderer = _get_log_renderer()
> assert isinstance(renderer, structlog.dev.ConsoleRenderer)
E AssertionError: assert False
E + where False = isinstance(<structlog.processors.JSONRenderer object at 0x7f7da92f8a50>, <class 'structlog.dev.ConsoleRenderer'>)
E + where <class 'structlog.dev.ConsoleRenderer'> = <module 'structlog.dev' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/dev.py'>.ConsoleRenderer
E + where <module 'structlog.dev' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/dev.py'> = structlog.dev
tests/test_logger.py:46: AssertionError
=============================== warnings summary ===============================
stampbot/logger.py:38
/home/runner/work/stampbot/stampbot/stampbot/logger.py:38: SyntaxWarning: "is" with 'str' literal. Did you mean "=="?
elif log_format is "console":
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_logger.py::test_get_log_renderer_console - AssertionError: assert False
+ where False = isinstance(<structlog.processors.JSONRenderer object at 0x7f7da92f8a50>, <class 'structlog.dev.ConsoleRenderer'>)
+ where <class 'structlog.dev.ConsoleRenderer'> = <module 'structlog.dev' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/dev.py'>.ConsoleRenderer
+ where <module 'structlog.dev' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/dev.py'> = structlog.dev
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 83 passed, 3 deselected, 1 warning in 0.77s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceComparisonOperator_Eq_Is, occurrence: 2
--- mutation diff ---
--- astampbot/logger.py
+++ bstampbot/logger.py
@@ -37,7 +37,7 @@
return structlog.processors.JSONRenderer()
elif log_format == "console":
return structlog.dev.ConsoleRenderer(colors=True)
- elif log_format == "auto":
+ elif log_format is "auto":
# Auto-detect: use JSON in Kubernetes, console otherwise
if is_running_in_kubernetes():
return structlog.processors.JSONRenderer()........................................................................ [ 34%]
.............F
=================================== FAILURES ===================================
_______________________ test_get_log_renderer_auto_local _______________________
def test_get_log_renderer_auto_local():
"""Test auto renderer in local environment."""
with patch("stampbot.logger.settings") as mock_settings:
mock_settings.log_format = "auto"
with patch("stampbot.logger.is_running_in_kubernetes", return_value=False):
from stampbot.logger import _get_log_renderer
renderer = _get_log_renderer()
> assert isinstance(renderer, structlog.dev.ConsoleRenderer)
E AssertionError: assert False
E + where False = isinstance(<structlog.processors.JSONRenderer object at 0x7f2a315f4cd0>, <class 'structlog.dev.ConsoleRenderer'>)
E + where <class 'structlog.dev.ConsoleRenderer'> = <module 'structlog.dev' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/dev.py'>.ConsoleRenderer
E + where <module 'structlog.dev' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/dev.py'> = structlog.dev
tests/test_logger.py:68: AssertionError
=============================== warnings summary ===============================
stampbot/logger.py:40
/home/runner/work/stampbot/stampbot/stampbot/logger.py:40: SyntaxWarning: "is" with 'str' literal. Did you mean "=="?
elif log_format is "auto":
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_logger.py::test_get_log_renderer_auto_local - AssertionError: assert False
+ where False = isinstance(<structlog.processors.JSONRenderer object at 0x7f2a315f4cd0>, <class 'structlog.dev.ConsoleRenderer'>)
+ where <class 'structlog.dev.ConsoleRenderer'> = <module 'structlog.dev' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/dev.py'>.ConsoleRenderer
+ where <module 'structlog.dev' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/dev.py'> = structlog.dev
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 85 passed, 3 deselected, 1 warning in 0.76s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceComparisonOperator_Eq_IsNot, occurrence: 0
--- mutation diff ---
--- astampbot/logger.py
+++ bstampbot/logger.py
@@ -33,7 +33,7 @@
"""
log_format = settings.log_format.lower()
- if log_format == "json":
+ if log_format is not "json":
return structlog.processors.JSONRenderer()
elif log_format == "console":
return structlog.dev.ConsoleRenderer(colors=True)........................................................................ [ 34%]
...........F
=================================== FAILURES ===================================
________________________ test_get_log_renderer_console _________________________
def test_get_log_renderer_console():
"""Test console renderer selection."""
with patch("stampbot.logger.settings") as mock_settings:
mock_settings.log_format = "console"
from stampbot.logger import _get_log_renderer
renderer = _get_log_renderer()
> assert isinstance(renderer, structlog.dev.ConsoleRenderer)
E AssertionError: assert False
E + where False = isinstance(<structlog.processors.JSONRenderer object at 0x7f3ba4400a50>, <class 'structlog.dev.ConsoleRenderer'>)
E + where <class 'structlog.dev.ConsoleRenderer'> = <module 'structlog.dev' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/dev.py'>.ConsoleRenderer
E + where <module 'structlog.dev' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/dev.py'> = structlog.dev
tests/test_logger.py:46: AssertionError
=============================== warnings summary ===============================
stampbot/logger.py:36
/home/runner/work/stampbot/stampbot/stampbot/logger.py:36: SyntaxWarning: "is not" with 'str' literal. Did you mean "!="?
if log_format is not "json":
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_logger.py::test_get_log_renderer_console - AssertionError: assert False
+ where False = isinstance(<structlog.processors.JSONRenderer object at 0x7f3ba4400a50>, <class 'structlog.dev.ConsoleRenderer'>)
+ where <class 'structlog.dev.ConsoleRenderer'> = <module 'structlog.dev' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/dev.py'>.ConsoleRenderer
+ where <module 'structlog.dev' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/dev.py'> = structlog.dev
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 83 passed, 3 deselected, 1 warning in 0.78s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceComparisonOperator_Eq_IsNot, occurrence: 1
--- mutation diff ---
--- astampbot/logger.py
+++ bstampbot/logger.py
@@ -35,7 +35,7 @@
if log_format == "json":
return structlog.processors.JSONRenderer()
- elif log_format == "console":
+ elif log_format is not "console":
return structlog.dev.ConsoleRenderer(colors=True)
elif log_format == "auto":
# Auto-detect: use JSON in Kubernetes, console otherwise........................................................................ [ 34%]
............F
=================================== FAILURES ===================================
____________________ test_get_log_renderer_auto_kubernetes _____________________
def test_get_log_renderer_auto_kubernetes():
"""Test auto renderer in Kubernetes environment."""
with patch("stampbot.logger.settings") as mock_settings:
mock_settings.log_format = "auto"
with patch("stampbot.logger.is_running_in_kubernetes", return_value=True):
from stampbot.logger import _get_log_renderer
renderer = _get_log_renderer()
> assert isinstance(renderer, structlog.processors.JSONRenderer)
E AssertionError: assert False
E + where False = isinstance(<structlog.dev.ConsoleRenderer object at 0x7fe1dadd8cd0>, <class 'structlog.processors.JSONRenderer'>)
E + where <class 'structlog.processors.JSONRenderer'> = <module 'structlog.processors' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/processors.py'>.JSONRenderer
E + where <module 'structlog.processors' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/processors.py'> = structlog.processors
tests/test_logger.py:57: AssertionError
=============================== warnings summary ===============================
stampbot/logger.py:38
/home/runner/work/stampbot/stampbot/stampbot/logger.py:38: SyntaxWarning: "is not" with 'str' literal. Did you mean "!="?
elif log_format is not "console":
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_logger.py::test_get_log_renderer_auto_kubernetes - AssertionError: assert False
+ where False = isinstance(<structlog.dev.ConsoleRenderer object at 0x7fe1dadd8cd0>, <class 'structlog.processors.JSONRenderer'>)
+ where <class 'structlog.processors.JSONRenderer'> = <module 'structlog.processors' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/processors.py'>.JSONRenderer
+ where <module 'structlog.processors' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/processors.py'> = structlog.processors
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 84 passed, 3 deselected, 1 warning in 0.77s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceComparisonOperator_Eq_IsNot, occurrence: 2
--- mutation diff ---
--- astampbot/logger.py
+++ bstampbot/logger.py
@@ -37,7 +37,7 @@
return structlog.processors.JSONRenderer()
elif log_format == "console":
return structlog.dev.ConsoleRenderer(colors=True)
- elif log_format == "auto":
+ elif log_format is not "auto":
# Auto-detect: use JSON in Kubernetes, console otherwise
if is_running_in_kubernetes():
return structlog.processors.JSONRenderer()........................................................................ [ 34%]
..............F
=================================== FAILURES ===================================
_____________________ test_get_log_renderer_unknown_format _____________________
def test_get_log_renderer_unknown_format():
"""Test fallback to JSON for unknown format."""
with patch("stampbot.logger.settings") as mock_settings:
mock_settings.log_format = "unknown_format"
from stampbot.logger import _get_log_renderer
renderer = _get_log_renderer()
> assert isinstance(renderer, structlog.processors.JSONRenderer)
E AssertionError: assert False
E + where False = isinstance(<structlog.dev.ConsoleRenderer object at 0x7f5f40b509d0>, <class 'structlog.processors.JSONRenderer'>)
E + where <class 'structlog.processors.JSONRenderer'> = <module 'structlog.processors' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/processors.py'>.JSONRenderer
E + where <module 'structlog.processors' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/processors.py'> = structlog.processors
tests/test_logger.py:78: AssertionError
=============================== warnings summary ===============================
stampbot/logger.py:40
/home/runner/work/stampbot/stampbot/stampbot/logger.py:40: SyntaxWarning: "is not" with 'str' literal. Did you mean "!="?
elif log_format is not "auto":
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_logger.py::test_get_log_renderer_unknown_format - AssertionError: assert False
+ where False = isinstance(<structlog.dev.ConsoleRenderer object at 0x7f5f40b509d0>, <class 'structlog.processors.JSONRenderer'>)
+ where <class 'structlog.processors.JSONRenderer'> = <module 'structlog.processors' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/processors.py'>.JSONRenderer
+ where <module 'structlog.processors' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/processors.py'> = structlog.processors
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 86 passed, 3 deselected, 1 warning in 0.79s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/AddNot, occurrence: 0
--- mutation diff ---
--- astampbot/logger.py
+++ bstampbot/logger.py
@@ -33,7 +33,7 @@
"""
log_format = settings.log_format.lower()
- if log_format == "json":
+ if not log_format == "json":
return structlog.processors.JSONRenderer()
elif log_format == "console":
return structlog.dev.ConsoleRenderer(colors=True)........................................................................ [ 34%]
...........F
=================================== FAILURES ===================================
________________________ test_get_log_renderer_console _________________________
def test_get_log_renderer_console():
"""Test console renderer selection."""
with patch("stampbot.logger.settings") as mock_settings:
mock_settings.log_format = "console"
from stampbot.logger import _get_log_renderer
renderer = _get_log_renderer()
> assert isinstance(renderer, structlog.dev.ConsoleRenderer)
E AssertionError: assert False
E + where False = isinstance(<structlog.processors.JSONRenderer object at 0x7f31cf7f4a50>, <class 'structlog.dev.ConsoleRenderer'>)
E + where <class 'structlog.dev.ConsoleRenderer'> = <module 'structlog.dev' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/dev.py'>.ConsoleRenderer
E + where <module 'structlog.dev' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/dev.py'> = structlog.dev
tests/test_logger.py:46: AssertionError
=========================== short test summary info ============================
FAILED tests/test_logger.py::test_get_log_renderer_console - AssertionError: assert False
+ where False = isinstance(<structlog.processors.JSONRenderer object at 0x7f31cf7f4a50>, <class 'structlog.dev.ConsoleRenderer'>)
+ where <class 'structlog.dev.ConsoleRenderer'> = <module 'structlog.dev' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/dev.py'>.ConsoleRenderer
+ where <module 'structlog.dev' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/dev.py'> = structlog.dev
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 83 passed, 3 deselected in 0.77s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/AddNot, occurrence: 1
--- mutation diff ---
--- astampbot/logger.py
+++ bstampbot/logger.py
@@ -39,7 +39,7 @@
return structlog.dev.ConsoleRenderer(colors=True)
elif log_format == "auto":
# Auto-detect: use JSON in Kubernetes, console otherwise
- if is_running_in_kubernetes():
+ if not is_running_in_kubernetes():
return structlog.processors.JSONRenderer()
else:
return structlog.dev.ConsoleRenderer(colors=True)........................................................................ [ 34%]
............F
=================================== FAILURES ===================================
____________________ test_get_log_renderer_auto_kubernetes _____________________
def test_get_log_renderer_auto_kubernetes():
"""Test auto renderer in Kubernetes environment."""
with patch("stampbot.logger.settings") as mock_settings:
mock_settings.log_format = "auto"
with patch("stampbot.logger.is_running_in_kubernetes", return_value=True):
from stampbot.logger import _get_log_renderer
renderer = _get_log_renderer()
> assert isinstance(renderer, structlog.processors.JSONRenderer)
E AssertionError: assert False
E + where False = isinstance(<structlog.dev.ConsoleRenderer object at 0x7f91565f0cd0>, <class 'structlog.processors.JSONRenderer'>)
E + where <class 'structlog.processors.JSONRenderer'> = <module 'structlog.processors' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/processors.py'>.JSONRenderer
E + where <module 'structlog.processors' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/processors.py'> = structlog.processors
tests/test_logger.py:57: AssertionError
=========================== short test summary info ============================
FAILED tests/test_logger.py::test_get_log_renderer_auto_kubernetes - AssertionError: assert False
+ where False = isinstance(<structlog.dev.ConsoleRenderer object at 0x7f91565f0cd0>, <class 'structlog.processors.JSONRenderer'>)
+ where <class 'structlog.processors.JSONRenderer'> = <module 'structlog.processors' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/processors.py'>.JSONRenderer
+ where <module 'structlog.processors' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/processors.py'> = structlog.processors
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 84 passed, 3 deselected in 0.79s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/AddNot, occurrence: 2
--- mutation diff ---
--- astampbot/logger.py
+++ bstampbot/logger.py
@@ -100,7 +100,7 @@
root_logger.setLevel(log_level_int)
# Instrument logging with OpenTelemetry if enabled
- if settings.otel_enabled:
+ if not settings.otel_enabled:
LoggingInstrumentor().instrument(set_logging_format=True)
........................................................................ [ 34%]
................F
=================================== FAILURES ===================================
___________________ test_configure_logging_with_otel_enabled ___________________
def test_configure_logging_with_otel_enabled():
"""Test configure_logging instruments logging when OTEL is enabled."""
with (
patch("stampbot.logger.settings") as mock_settings,
patch("stampbot.logger.LoggingInstrumentor") as mock_instrumentor,
):
mock_settings.log_format = "json"
mock_settings.log_level = "INFO"
mock_settings.otel_enabled = True
from stampbot.logger import configure_logging
configure_logging()
# Verify LoggingInstrumentor was called
> mock_instrumentor.return_value.instrument.assert_called_once_with(set_logging_format=True)
tests/test_logger.py:104:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <MagicMock name='LoggingInstrumentor().instrument' id='140676894991632'>
args = (), kwargs = {'set_logging_format': True}
msg = "Expected 'instrument' to be called once. Called 0 times."
def assert_called_once_with(self, /, *args, **kwargs):
"""assert that the mock was called exactly once and that that call was
with the specified arguments."""
if not self.call_count == 1:
msg = ("Expected '%s' to be called once. Called %s times.%s"
% (self._mock_name or 'mock',
self.call_count,
self._calls_repr()))
> raise AssertionError(msg)
E AssertionError: Expected 'instrument' to be called once. Called 0 times.
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:996: AssertionError
=========================== short test summary info ============================
FAILED tests/test_logger.py::test_configure_logging_with_otel_enabled - AssertionError: Expected 'instrument' to be called once. Called 0 times.
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 88 passed, 3 deselected in 0.82s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceTrueWithFalse, occurrence: 0
--- mutation diff ---
--- astampbot/logger.py
+++ bstampbot/logger.py
@@ -36,7 +36,7 @@
if log_format == "json":
return structlog.processors.JSONRenderer()
elif log_format == "console":
- return structlog.dev.ConsoleRenderer(colors=True)
+ return structlog.dev.ConsoleRenderer(colors=False)
elif log_format == "auto":
# Auto-detect: use JSON in Kubernetes, console otherwise
if is_running_in_kubernetes():........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.60s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceTrueWithFalse, occurrence: 1
--- mutation diff ---
--- astampbot/logger.py
+++ bstampbot/logger.py
@@ -42,7 +42,7 @@
if is_running_in_kubernetes():
return structlog.processors.JSONRenderer()
else:
- return structlog.dev.ConsoleRenderer(colors=True)
+ return structlog.dev.ConsoleRenderer(colors=False)
else:
# Unknown format, default to JSON for safety
return structlog.processors.JSONRenderer()........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.67s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceTrueWithFalse, occurrence: 2
--- mutation diff ---
--- astampbot/logger.py
+++ bstampbot/logger.py
@@ -78,7 +78,7 @@
context_class=dict,
# Route structlog through stdlib so all output shares one handler.
logger_factory=structlog.stdlib.LoggerFactory(),
- cache_logger_on_first_use=True,
+ cache_logger_on_first_use=False,
)
# ProcessorFormatter ensures uvicorn access logs and every other stdlib........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceTrueWithFalse, occurrence: 3
--- mutation diff ---
--- astampbot/logger.py
+++ bstampbot/logger.py
@@ -101,7 +101,7 @@
# Instrument logging with OpenTelemetry if enabled
if settings.otel_enabled:
- LoggingInstrumentor().instrument(set_logging_format=True)
+ LoggingInstrumentor().instrument(set_logging_format=False)
def get_logger(name: str) -> structlog.BoundLogger:........................................................................ [ 34%]
................F
=================================== FAILURES ===================================
___________________ test_configure_logging_with_otel_enabled ___________________
def test_configure_logging_with_otel_enabled():
"""Test configure_logging instruments logging when OTEL is enabled."""
with (
patch("stampbot.logger.settings") as mock_settings,
patch("stampbot.logger.LoggingInstrumentor") as mock_instrumentor,
):
mock_settings.log_format = "json"
mock_settings.log_level = "INFO"
mock_settings.otel_enabled = True
from stampbot.logger import configure_logging
configure_logging()
# Verify LoggingInstrumentor was called
> mock_instrumentor.return_value.instrument.assert_called_once_with(set_logging_format=True)
tests/test_logger.py:104:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:997: in assert_called_once_with
return self.assert_called_with(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <MagicMock name='LoggingInstrumentor().instrument' id='140572520801552'>
args = (), kwargs = {'set_logging_format': True}
expected = call(set_logging_format=True)
actual = call(set_logging_format=False)
_error_message = <function NonCallableMock.assert_called_with.<locals>._error_message at 0x7fd9972e43b0>
cause = None
def assert_called_with(self, /, *args, **kwargs):
"""assert that the last call was made with the specified arguments.
Raises an AssertionError if the args and keyword args passed in are
different to the last call to the mock."""
if self.call_args is None:
expected = self._format_mock_call_signature(args, kwargs)
actual = 'not called.'
error_message = ('expected call not found.\nExpected: %s\n Actual: %s'
% (expected, actual))
raise AssertionError(error_message)
def _error_message():
msg = self._format_mock_failure_message(args, kwargs)
return msg
expected = self._call_matcher(_Call((args, kwargs), two=True))
actual = self._call_matcher(self.call_args)
if actual != expected:
cause = expected if isinstance(expected, Exception) else None
> raise AssertionError(_error_message()) from cause
E AssertionError: expected call not found.
E Expected: instrument(set_logging_format=True)
E Actual: instrument(set_logging_format=False)
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:985: AssertionError
=========================== short test summary info ============================
FAILED tests/test_logger.py::test_configure_logging_with_otel_enabled - AssertionError: expected call not found.
Expected: instrument(set_logging_format=True)
Actual: instrument(set_logging_format=False)
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 88 passed, 3 deselected in 0.88s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_Add, occurrence: 0
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -168,7 +168,7 @@
retry=retry,
)
- duration = time.time() - start_time
+ duration = time.time() + start_time
github_api_request_duration_seconds.labels(operation="get_token").observe(duration)
github_api_requests_total.labels(operation="get_token", status="success").inc()
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.60s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_Add, occurrence: 1
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -176,7 +176,7 @@
return client
except Exception as e:
- duration = time.time() - start_time
+ duration = time.time() + start_time
github_api_request_duration_seconds.labels(operation="get_token").observe(duration)
github_api_requests_total.labels(operation="get_token", status="failure").inc()
set_span_error(span, e)........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.62s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_Add, occurrence: 2
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -240,7 +240,7 @@
event="APPROVE",
)
- duration = time.time() - start_time
+ duration = time.time() + start_time
github_api_request_duration_seconds.labels(operation="approve").observe(duration)
github_api_requests_total.labels(operation="approve", status="success").inc()
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.62s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_Add, occurrence: 3
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -260,7 +260,7 @@
return True
except Exception as e:
- duration = time.time() - start_time
+ duration = time.time() + start_time
github_api_request_duration_seconds.labels(operation="approve").observe(duration)
github_api_requests_total.labels(operation="approve", status="failure").inc()
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.62s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_Add, occurrence: 4
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -317,7 +317,7 @@
review = pr.get_review(review_id)
review.dismiss(message)
- duration = time.time() - start_time
+ duration = time.time() + start_time
github_api_request_duration_seconds.labels(operation="dismiss").observe(duration)
github_api_requests_total.labels(operation="dismiss", status="success").inc()
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.63s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_Add, occurrence: 5
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -338,7 +338,7 @@
return True
except Exception as e:
- duration = time.time() - start_time
+ duration = time.time() + start_time
github_api_request_duration_seconds.labels(operation="dismiss").observe(duration)
github_api_requests_total.labels(operation="dismiss", status="failure").inc()
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_Add, occurrence: 6
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -393,7 +393,7 @@
content = repo.get_contents(file_path, ref=ref) # type: ignore[arg-type]
if isinstance(content, list):
- duration = time.time() - start_time
+ duration = time.time() + start_time
github_api_request_duration_seconds.labels(operation="get_file").observe(
duration
)........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_Add, occurrence: 7
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -402,7 +402,7 @@
set_span_ok(span)
return None
- duration = time.time() - start_time
+ duration = time.time() + start_time
github_api_request_duration_seconds.labels(operation="get_file").observe(duration)
github_api_requests_total.labels(operation="get_file", status="success").inc()
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.60s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_Add, occurrence: 8
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -414,7 +414,7 @@
return content.decoded_content.decode("utf-8")
except Exception as e:
- duration = time.time() - start_time
+ duration = time.time() + start_time
github_api_request_duration_seconds.labels(operation="get_file").observe(duration)
github_api_requests_total.labels(operation="get_file", status="not_found").inc()
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.62s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_Add, occurrence: 9
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -473,7 +473,7 @@
if review.user.login == bot_user and review.state == "APPROVED":
bot_review_ids.append(review.id)
- duration = time.time() - start_time
+ duration = time.time() + start_time
github_api_request_duration_seconds.labels(operation="find_reviews").observe(
duration
)........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.62s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_Add, occurrence: 10
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -489,7 +489,7 @@
return bot_review_ids
except Exception as e:
- duration = time.time() - start_time
+ duration = time.time() + start_time
github_api_request_duration_seconds.labels(operation="find_reviews").observe(
duration
)........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.65s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_Add, occurrence: 11
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -546,7 +546,7 @@
pr.create_review(body=message, event="COMMENT")
- duration = time.time() - start_time
+ duration = time.time() + start_time
github_api_request_duration_seconds.labels(operation="comment").observe(duration)
github_api_requests_total.labels(operation="comment", status="success").inc()
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.63s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_Add, occurrence: 12
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -568,7 +568,7 @@
return True
except Exception as e:
- duration = time.time() - start_time
+ duration = time.time() + start_time
github_api_request_duration_seconds.labels(operation="comment").observe(duration)
github_api_requests_total.labels(operation="comment", status="failure").inc()
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.62s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_Add, occurrence: 13
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -619,7 +619,7 @@
repo = client.get_repo(repo_full_name)
repo.get_label(label_name)
- duration = time.time() - start_time
+ duration = time.time() + start_time
github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
github_api_requests_total.labels(operation="get_label", status="success").inc()
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.60s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_Add, occurrence: 14
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -630,7 +630,7 @@
return True
except GithubException as e:
- duration = time.time() - start_time
+ duration = time.time() + start_time
github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
github_api_requests_total.labels(operation="get_label", status="failure").inc()
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_Add, occurrence: 15
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -655,7 +655,7 @@
return None
except Exception as e:
- duration = time.time() - start_time
+ duration = time.time() + start_time
github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
github_api_requests_total.labels(operation="get_label", status="failure").inc()
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.70s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_Add, occurrence: 16
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -718,7 +718,7 @@
has_permission = permission_index >= required_index
- duration = time.time() - start_time
+ duration = time.time() + start_time
github_api_request_duration_seconds.labels(operation="get_permission").observe(
duration
)........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.70s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_Add, occurrence: 17
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -738,7 +738,7 @@
return has_permission
except Exception as e:
- duration = time.time() - start_time
+ duration = time.time() + start_time
github_api_request_duration_seconds.labels(operation="get_permission").observe(
duration
)........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_Add, occurrence: 18
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -829,7 +829,7 @@
)
continue
- duration = time.time() - start_time
+ duration = time.time() + start_time
github_api_request_duration_seconds.labels(operation="get_user_teams").observe(
duration
)........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.62s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_Add, occurrence: 19
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -849,7 +849,7 @@
return member_teams
except Exception as e:
- duration = time.time() - start_time
+ duration = time.time() + start_time
github_api_request_duration_seconds.labels(operation="get_user_teams").observe(
duration
)........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.62s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_Mul, occurrence: 0
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -168,7 +168,7 @@
retry=retry,
)
- duration = time.time() - start_time
+ duration = time.time() * start_time
github_api_request_duration_seconds.labels(operation="get_token").observe(duration)
github_api_requests_total.labels(operation="get_token", status="success").inc()
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_Mul, occurrence: 1
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -176,7 +176,7 @@
return client
except Exception as e:
- duration = time.time() - start_time
+ duration = time.time() * start_time
github_api_request_duration_seconds.labels(operation="get_token").observe(duration)
github_api_requests_total.labels(operation="get_token", status="failure").inc()
set_span_error(span, e)........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.66s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_Mul, occurrence: 2
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -240,7 +240,7 @@
event="APPROVE",
)
- duration = time.time() - start_time
+ duration = time.time() * start_time
github_api_request_duration_seconds.labels(operation="approve").observe(duration)
github_api_requests_total.labels(operation="approve", status="success").inc()
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_Mul, occurrence: 3
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -260,7 +260,7 @@
return True
except Exception as e:
- duration = time.time() - start_time
+ duration = time.time() * start_time
github_api_request_duration_seconds.labels(operation="approve").observe(duration)
github_api_requests_total.labels(operation="approve", status="failure").inc()
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.62s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_Mul, occurrence: 4
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -317,7 +317,7 @@
review = pr.get_review(review_id)
review.dismiss(message)
- duration = time.time() - start_time
+ duration = time.time() * start_time
github_api_request_duration_seconds.labels(operation="dismiss").observe(duration)
github_api_requests_total.labels(operation="dismiss", status="success").inc()
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.60s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_Mul, occurrence: 5
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -338,7 +338,7 @@
return True
except Exception as e:
- duration = time.time() - start_time
+ duration = time.time() * start_time
github_api_request_duration_seconds.labels(operation="dismiss").observe(duration)
github_api_requests_total.labels(operation="dismiss", status="failure").inc()
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.60s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_Mul, occurrence: 6
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -393,7 +393,7 @@
content = repo.get_contents(file_path, ref=ref) # type: ignore[arg-type]
if isinstance(content, list):
- duration = time.time() - start_time
+ duration = time.time() * start_time
github_api_request_duration_seconds.labels(operation="get_file").observe(
duration
)........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.62s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_Mul, occurrence: 7
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -402,7 +402,7 @@
set_span_ok(span)
return None
- duration = time.time() - start_time
+ duration = time.time() * start_time
github_api_request_duration_seconds.labels(operation="get_file").observe(duration)
github_api_requests_total.labels(operation="get_file", status="success").inc()
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_Mul, occurrence: 8
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -414,7 +414,7 @@
return content.decoded_content.decode("utf-8")
except Exception as e:
- duration = time.time() - start_time
+ duration = time.time() * start_time
github_api_request_duration_seconds.labels(operation="get_file").observe(duration)
github_api_requests_total.labels(operation="get_file", status="not_found").inc()
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.64s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_Mul, occurrence: 9
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -473,7 +473,7 @@
if review.user.login == bot_user and review.state == "APPROVED":
bot_review_ids.append(review.id)
- duration = time.time() - start_time
+ duration = time.time() * start_time
github_api_request_duration_seconds.labels(operation="find_reviews").observe(
duration
)........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.60s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_Mul, occurrence: 10
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -489,7 +489,7 @@
return bot_review_ids
except Exception as e:
- duration = time.time() - start_time
+ duration = time.time() * start_time
github_api_request_duration_seconds.labels(operation="find_reviews").observe(
duration
)........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_Mul, occurrence: 11
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -546,7 +546,7 @@
pr.create_review(body=message, event="COMMENT")
- duration = time.time() - start_time
+ duration = time.time() * start_time
github_api_request_duration_seconds.labels(operation="comment").observe(duration)
github_api_requests_total.labels(operation="comment", status="success").inc()
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.60s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_Mul, occurrence: 12
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -568,7 +568,7 @@
return True
except Exception as e:
- duration = time.time() - start_time
+ duration = time.time() * start_time
github_api_request_duration_seconds.labels(operation="comment").observe(duration)
github_api_requests_total.labels(operation="comment", status="failure").inc()
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.70s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_Mul, occurrence: 13
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -619,7 +619,7 @@
repo = client.get_repo(repo_full_name)
repo.get_label(label_name)
- duration = time.time() - start_time
+ duration = time.time() * start_time
github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
github_api_requests_total.labels(operation="get_label", status="success").inc()
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_Mul, occurrence: 14
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -630,7 +630,7 @@
return True
except GithubException as e:
- duration = time.time() - start_time
+ duration = time.time() * start_time
github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
github_api_requests_total.labels(operation="get_label", status="failure").inc()
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.63s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_Mul, occurrence: 15
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -655,7 +655,7 @@
return None
except Exception as e:
- duration = time.time() - start_time
+ duration = time.time() * start_time
github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
github_api_requests_total.labels(operation="get_label", status="failure").inc()
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.67s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_Mul, occurrence: 16
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -718,7 +718,7 @@
has_permission = permission_index >= required_index
- duration = time.time() - start_time
+ duration = time.time() * start_time
github_api_request_duration_seconds.labels(operation="get_permission").observe(
duration
)........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.62s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_Mul, occurrence: 17
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -738,7 +738,7 @@
return has_permission
except Exception as e:
- duration = time.time() - start_time
+ duration = time.time() * start_time
github_api_request_duration_seconds.labels(operation="get_permission").observe(
duration
)........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_Mul, occurrence: 18
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -829,7 +829,7 @@
)
continue
- duration = time.time() - start_time
+ duration = time.time() * start_time
github_api_request_duration_seconds.labels(operation="get_user_teams").observe(
duration
)........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.60s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_Mul, occurrence: 19
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -849,7 +849,7 @@
return member_teams
except Exception as e:
- duration = time.time() - start_time
+ duration = time.time() * start_time
github_api_request_duration_seconds.labels(operation="get_user_teams").observe(
duration
)........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_Div, occurrence: 0
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -168,7 +168,7 @@
retry=retry,
)
- duration = time.time() - start_time
+ duration = time.time() / start_time
github_api_request_duration_seconds.labels(operation="get_token").observe(duration)
github_api_requests_total.labels(operation="get_token", status="success").inc()
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.68s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_Div, occurrence: 1
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -176,7 +176,7 @@
return client
except Exception as e:
- duration = time.time() - start_time
+ duration = time.time() / start_time
github_api_request_duration_seconds.labels(operation="get_token").observe(duration)
github_api_requests_total.labels(operation="get_token", status="failure").inc()
set_span_error(span, e)........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.64s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_Div, occurrence: 2
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -240,7 +240,7 @@
event="APPROVE",
)
- duration = time.time() - start_time
+ duration = time.time() / start_time
github_api_request_duration_seconds.labels(operation="approve").observe(duration)
github_api_requests_total.labels(operation="approve", status="success").inc()
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.63s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_Div, occurrence: 3
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -260,7 +260,7 @@
return True
except Exception as e:
- duration = time.time() - start_time
+ duration = time.time() / start_time
github_api_request_duration_seconds.labels(operation="approve").observe(duration)
github_api_requests_total.labels(operation="approve", status="failure").inc()
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.68s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_Div, occurrence: 4
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -317,7 +317,7 @@
review = pr.get_review(review_id)
review.dismiss(message)
- duration = time.time() - start_time
+ duration = time.time() / start_time
github_api_request_duration_seconds.labels(operation="dismiss").observe(duration)
github_api_requests_total.labels(operation="dismiss", status="success").inc()
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_Div, occurrence: 5
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -338,7 +338,7 @@
return True
except Exception as e:
- duration = time.time() - start_time
+ duration = time.time() / start_time
github_api_request_duration_seconds.labels(operation="dismiss").observe(duration)
github_api_requests_total.labels(operation="dismiss", status="failure").inc()
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.60s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_Div, occurrence: 6
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -393,7 +393,7 @@
content = repo.get_contents(file_path, ref=ref) # type: ignore[arg-type]
if isinstance(content, list):
- duration = time.time() - start_time
+ duration = time.time() / start_time
github_api_request_duration_seconds.labels(operation="get_file").observe(
duration
)........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.62s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_Div, occurrence: 7
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -402,7 +402,7 @@
set_span_ok(span)
return None
- duration = time.time() - start_time
+ duration = time.time() / start_time
github_api_request_duration_seconds.labels(operation="get_file").observe(duration)
github_api_requests_total.labels(operation="get_file", status="success").inc()
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.64s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_Div, occurrence: 8
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -414,7 +414,7 @@
return content.decoded_content.decode("utf-8")
except Exception as e:
- duration = time.time() - start_time
+ duration = time.time() / start_time
github_api_request_duration_seconds.labels(operation="get_file").observe(duration)
github_api_requests_total.labels(operation="get_file", status="not_found").inc()
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.63s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_Div, occurrence: 9
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -473,7 +473,7 @@
if review.user.login == bot_user and review.state == "APPROVED":
bot_review_ids.append(review.id)
- duration = time.time() - start_time
+ duration = time.time() / start_time
github_api_request_duration_seconds.labels(operation="find_reviews").observe(
duration
)........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.62s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_Div, occurrence: 10
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -489,7 +489,7 @@
return bot_review_ids
except Exception as e:
- duration = time.time() - start_time
+ duration = time.time() / start_time
github_api_request_duration_seconds.labels(operation="find_reviews").observe(
duration
)........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.60s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_Div, occurrence: 11
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -546,7 +546,7 @@
pr.create_review(body=message, event="COMMENT")
- duration = time.time() - start_time
+ duration = time.time() / start_time
github_api_request_duration_seconds.labels(operation="comment").observe(duration)
github_api_requests_total.labels(operation="comment", status="success").inc()
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.60s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_Div, occurrence: 12
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -568,7 +568,7 @@
return True
except Exception as e:
- duration = time.time() - start_time
+ duration = time.time() / start_time
github_api_request_duration_seconds.labels(operation="comment").observe(duration)
github_api_requests_total.labels(operation="comment", status="failure").inc()
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.62s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_Div, occurrence: 13
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -619,7 +619,7 @@
repo = client.get_repo(repo_full_name)
repo.get_label(label_name)
- duration = time.time() - start_time
+ duration = time.time() / start_time
github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
github_api_requests_total.labels(operation="get_label", status="success").inc()
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_Div, occurrence: 14
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -630,7 +630,7 @@
return True
except GithubException as e:
- duration = time.time() - start_time
+ duration = time.time() / start_time
github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
github_api_requests_total.labels(operation="get_label", status="failure").inc()
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_Div, occurrence: 15
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -655,7 +655,7 @@
return None
except Exception as e:
- duration = time.time() - start_time
+ duration = time.time() / start_time
github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
github_api_requests_total.labels(operation="get_label", status="failure").inc()
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.67s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_Div, occurrence: 16
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -718,7 +718,7 @@
has_permission = permission_index >= required_index
- duration = time.time() - start_time
+ duration = time.time() / start_time
github_api_request_duration_seconds.labels(operation="get_permission").observe(
duration
)........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.63s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_Div, occurrence: 17
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -738,7 +738,7 @@
return has_permission
except Exception as e:
- duration = time.time() - start_time
+ duration = time.time() / start_time
github_api_request_duration_seconds.labels(operation="get_permission").observe(
duration
)........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.60s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_Div, occurrence: 18
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -829,7 +829,7 @@
)
continue
- duration = time.time() - start_time
+ duration = time.time() / start_time
github_api_request_duration_seconds.labels(operation="get_user_teams").observe(
duration
)........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.63s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_Div, occurrence: 19
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -849,7 +849,7 @@
return member_teams
except Exception as e:
- duration = time.time() - start_time
+ duration = time.time() / start_time
github_api_request_duration_seconds.labels(operation="get_user_teams").observe(
duration
)........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_FloorDiv, occurrence: 0
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -168,7 +168,7 @@
retry=retry,
)
- duration = time.time() - start_time
+ duration = time.time() // start_time
github_api_request_duration_seconds.labels(operation="get_token").observe(duration)
github_api_requests_total.labels(operation="get_token", status="success").inc()
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.60s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_FloorDiv, occurrence: 1
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -176,7 +176,7 @@
return client
except Exception as e:
- duration = time.time() - start_time
+ duration = time.time() // start_time
github_api_request_duration_seconds.labels(operation="get_token").observe(duration)
github_api_requests_total.labels(operation="get_token", status="failure").inc()
set_span_error(span, e)........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.62s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_FloorDiv, occurrence: 2
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -240,7 +240,7 @@
event="APPROVE",
)
- duration = time.time() - start_time
+ duration = time.time() // start_time
github_api_request_duration_seconds.labels(operation="approve").observe(duration)
github_api_requests_total.labels(operation="approve", status="success").inc()
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_FloorDiv, occurrence: 3
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -260,7 +260,7 @@
return True
except Exception as e:
- duration = time.time() - start_time
+ duration = time.time() // start_time
github_api_request_duration_seconds.labels(operation="approve").observe(duration)
github_api_requests_total.labels(operation="approve", status="failure").inc()
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_FloorDiv, occurrence: 4
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -317,7 +317,7 @@
review = pr.get_review(review_id)
review.dismiss(message)
- duration = time.time() - start_time
+ duration = time.time() // start_time
github_api_request_duration_seconds.labels(operation="dismiss").observe(duration)
github_api_requests_total.labels(operation="dismiss", status="success").inc()
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.62s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_FloorDiv, occurrence: 5
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -338,7 +338,7 @@
return True
except Exception as e:
- duration = time.time() - start_time
+ duration = time.time() // start_time
github_api_request_duration_seconds.labels(operation="dismiss").observe(duration)
github_api_requests_total.labels(operation="dismiss", status="failure").inc()
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.65s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_FloorDiv, occurrence: 6
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -393,7 +393,7 @@
content = repo.get_contents(file_path, ref=ref) # type: ignore[arg-type]
if isinstance(content, list):
- duration = time.time() - start_time
+ duration = time.time() // start_time
github_api_request_duration_seconds.labels(operation="get_file").observe(
duration
)........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.65s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_FloorDiv, occurrence: 7
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -402,7 +402,7 @@
set_span_ok(span)
return None
- duration = time.time() - start_time
+ duration = time.time() // start_time
github_api_request_duration_seconds.labels(operation="get_file").observe(duration)
github_api_requests_total.labels(operation="get_file", status="success").inc()
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.59s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_FloorDiv, occurrence: 8
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -414,7 +414,7 @@
return content.decoded_content.decode("utf-8")
except Exception as e:
- duration = time.time() - start_time
+ duration = time.time() // start_time
github_api_request_duration_seconds.labels(operation="get_file").observe(duration)
github_api_requests_total.labels(operation="get_file", status="not_found").inc()
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_FloorDiv, occurrence: 9
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -473,7 +473,7 @@
if review.user.login == bot_user and review.state == "APPROVED":
bot_review_ids.append(review.id)
- duration = time.time() - start_time
+ duration = time.time() // start_time
github_api_request_duration_seconds.labels(operation="find_reviews").observe(
duration
)........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.63s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_FloorDiv, occurrence: 10
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -489,7 +489,7 @@
return bot_review_ids
except Exception as e:
- duration = time.time() - start_time
+ duration = time.time() // start_time
github_api_request_duration_seconds.labels(operation="find_reviews").observe(
duration
)........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.62s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_FloorDiv, occurrence: 11
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -546,7 +546,7 @@
pr.create_review(body=message, event="COMMENT")
- duration = time.time() - start_time
+ duration = time.time() // start_time
github_api_request_duration_seconds.labels(operation="comment").observe(duration)
github_api_requests_total.labels(operation="comment", status="success").inc()
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_FloorDiv, occurrence: 12
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -568,7 +568,7 @@
return True
except Exception as e:
- duration = time.time() - start_time
+ duration = time.time() // start_time
github_api_request_duration_seconds.labels(operation="comment").observe(duration)
github_api_requests_total.labels(operation="comment", status="failure").inc()
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.60s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_FloorDiv, occurrence: 13
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -619,7 +619,7 @@
repo = client.get_repo(repo_full_name)
repo.get_label(label_name)
- duration = time.time() - start_time
+ duration = time.time() // start_time
github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
github_api_requests_total.labels(operation="get_label", status="success").inc()
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.63s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_FloorDiv, occurrence: 14
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -630,7 +630,7 @@
return True
except GithubException as e:
- duration = time.time() - start_time
+ duration = time.time() // start_time
github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
github_api_requests_total.labels(operation="get_label", status="failure").inc()
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_FloorDiv, occurrence: 15
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -655,7 +655,7 @@
return None
except Exception as e:
- duration = time.time() - start_time
+ duration = time.time() // start_time
github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
github_api_requests_total.labels(operation="get_label", status="failure").inc()
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.60s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_FloorDiv, occurrence: 16
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -718,7 +718,7 @@
has_permission = permission_index >= required_index
- duration = time.time() - start_time
+ duration = time.time() // start_time
github_api_request_duration_seconds.labels(operation="get_permission").observe(
duration
)........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.60s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_FloorDiv, occurrence: 17
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -738,7 +738,7 @@
return has_permission
except Exception as e:
- duration = time.time() - start_time
+ duration = time.time() // start_time
github_api_request_duration_seconds.labels(operation="get_permission").observe(
duration
)........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_FloorDiv, occurrence: 18
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -829,7 +829,7 @@
)
continue
- duration = time.time() - start_time
+ duration = time.time() // start_time
github_api_request_duration_seconds.labels(operation="get_user_teams").observe(
duration
)........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.67s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_FloorDiv, occurrence: 19
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -849,7 +849,7 @@
return member_teams
except Exception as e:
- duration = time.time() - start_time
+ duration = time.time() // start_time
github_api_request_duration_seconds.labels(operation="get_user_teams").observe(
duration
)........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_Mod, occurrence: 0
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -168,7 +168,7 @@
retry=retry,
)
- duration = time.time() - start_time
+ duration = time.time() % start_time
github_api_request_duration_seconds.labels(operation="get_token").observe(duration)
github_api_requests_total.labels(operation="get_token", status="success").inc()
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.69s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_Mod, occurrence: 1
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -176,7 +176,7 @@
return client
except Exception as e:
- duration = time.time() - start_time
+ duration = time.time() % start_time
github_api_request_duration_seconds.labels(operation="get_token").observe(duration)
github_api_requests_total.labels(operation="get_token", status="failure").inc()
set_span_error(span, e)........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.67s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_Mod, occurrence: 2
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -240,7 +240,7 @@
event="APPROVE",
)
- duration = time.time() - start_time
+ duration = time.time() % start_time
github_api_request_duration_seconds.labels(operation="approve").observe(duration)
github_api_requests_total.labels(operation="approve", status="success").inc()
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.62s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_Mod, occurrence: 3
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -260,7 +260,7 @@
return True
except Exception as e:
- duration = time.time() - start_time
+ duration = time.time() % start_time
github_api_request_duration_seconds.labels(operation="approve").observe(duration)
github_api_requests_total.labels(operation="approve", status="failure").inc()
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.65s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_Mod, occurrence: 4
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -317,7 +317,7 @@
review = pr.get_review(review_id)
review.dismiss(message)
- duration = time.time() - start_time
+ duration = time.time() % start_time
github_api_request_duration_seconds.labels(operation="dismiss").observe(duration)
github_api_requests_total.labels(operation="dismiss", status="success").inc()
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_Mod, occurrence: 5
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -338,7 +338,7 @@
return True
except Exception as e:
- duration = time.time() - start_time
+ duration = time.time() % start_time
github_api_request_duration_seconds.labels(operation="dismiss").observe(duration)
github_api_requests_total.labels(operation="dismiss", status="failure").inc()
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.62s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_Mod, occurrence: 6
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -393,7 +393,7 @@
content = repo.get_contents(file_path, ref=ref) # type: ignore[arg-type]
if isinstance(content, list):
- duration = time.time() - start_time
+ duration = time.time() % start_time
github_api_request_duration_seconds.labels(operation="get_file").observe(
duration
)........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_Mod, occurrence: 7
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -402,7 +402,7 @@
set_span_ok(span)
return None
- duration = time.time() - start_time
+ duration = time.time() % start_time
github_api_request_duration_seconds.labels(operation="get_file").observe(duration)
github_api_requests_total.labels(operation="get_file", status="success").inc()
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.62s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_Mod, occurrence: 8
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -414,7 +414,7 @@
return content.decoded_content.decode("utf-8")
except Exception as e:
- duration = time.time() - start_time
+ duration = time.time() % start_time
github_api_request_duration_seconds.labels(operation="get_file").observe(duration)
github_api_requests_total.labels(operation="get_file", status="not_found").inc()
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.62s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_Mod, occurrence: 9
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -473,7 +473,7 @@
if review.user.login == bot_user and review.state == "APPROVED":
bot_review_ids.append(review.id)
- duration = time.time() - start_time
+ duration = time.time() % start_time
github_api_request_duration_seconds.labels(operation="find_reviews").observe(
duration
)........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.62s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_Mod, occurrence: 10
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -489,7 +489,7 @@
return bot_review_ids
except Exception as e:
- duration = time.time() - start_time
+ duration = time.time() % start_time
github_api_request_duration_seconds.labels(operation="find_reviews").observe(
duration
)........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_Mod, occurrence: 11
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -546,7 +546,7 @@
pr.create_review(body=message, event="COMMENT")
- duration = time.time() - start_time
+ duration = time.time() % start_time
github_api_request_duration_seconds.labels(operation="comment").observe(duration)
github_api_requests_total.labels(operation="comment", status="success").inc()
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_Mod, occurrence: 12
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -568,7 +568,7 @@
return True
except Exception as e:
- duration = time.time() - start_time
+ duration = time.time() % start_time
github_api_request_duration_seconds.labels(operation="comment").observe(duration)
github_api_requests_total.labels(operation="comment", status="failure").inc()
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.62s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_Mod, occurrence: 13
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -619,7 +619,7 @@
repo = client.get_repo(repo_full_name)
repo.get_label(label_name)
- duration = time.time() - start_time
+ duration = time.time() % start_time
github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
github_api_requests_total.labels(operation="get_label", status="success").inc()
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.60s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_Mod, occurrence: 14
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -630,7 +630,7 @@
return True
except GithubException as e:
- duration = time.time() - start_time
+ duration = time.time() % start_time
github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
github_api_requests_total.labels(operation="get_label", status="failure").inc()
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_Mod, occurrence: 15
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -655,7 +655,7 @@
return None
except Exception as e:
- duration = time.time() - start_time
+ duration = time.time() % start_time
github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
github_api_requests_total.labels(operation="get_label", status="failure").inc()
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_Mod, occurrence: 16
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -718,7 +718,7 @@
has_permission = permission_index >= required_index
- duration = time.time() - start_time
+ duration = time.time() % start_time
github_api_request_duration_seconds.labels(operation="get_permission").observe(
duration
)........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_Mod, occurrence: 17
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -738,7 +738,7 @@
return has_permission
except Exception as e:
- duration = time.time() - start_time
+ duration = time.time() % start_time
github_api_request_duration_seconds.labels(operation="get_permission").observe(
duration
)........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.64s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_Mod, occurrence: 18
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -829,7 +829,7 @@
)
continue
- duration = time.time() - start_time
+ duration = time.time() % start_time
github_api_request_duration_seconds.labels(operation="get_user_teams").observe(
duration
)........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_Mod, occurrence: 19
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -849,7 +849,7 @@
return member_teams
except Exception as e:
- duration = time.time() - start_time
+ duration = time.time() % start_time
github_api_request_duration_seconds.labels(operation="get_user_teams").observe(
duration
)........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_Pow, occurrence: 0
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -168,7 +168,7 @@
retry=retry,
)
- duration = time.time() - start_time
+ duration = time.time() ** start_time
github_api_request_duration_seconds.labels(operation="get_token").observe(duration)
github_api_requests_total.labels(operation="get_token", status="success").inc()
...............................................F
=================================== FAILURES ===================================
________ TestGetInstallationClient.test_get_installation_client_success ________
self = <tests.test_github_client.TestGetInstallationClient object at 0x7fbff0238050>
def test_get_installation_client_success(self):
"""Test successful installation client creation."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
> result = client._get_installation_client(123456)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_github_client.py:211:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.github_client.GitHubAppClient object at 0x7fbfec278750>
installation_id = 123456
def _get_installation_client(self, installation_id: int) -> Github:
"""Get authenticated GitHub client for an installation.
Args:
installation_id: GitHub App installation ID.
Returns:
Authenticated Github client instance with timeout and retry configured.
Raises:
github.GithubException: If token exchange fails.
"""
start_time = time.time()
with create_span(
"github.get_installation_token",
{"github.installation_id": installation_id},
) as span:
try:
auth = self.integration.get_access_token(installation_id)
# Configure retry with exponential backoff
retry = Retry(
total=GITHUB_API_RETRY_TOTAL,
backoff_factor=GITHUB_API_RETRY_BACKOFF,
status_forcelist=[500, 502, 503, 504],
allowed_methods=["GET", "POST", "PUT", "DELETE", "PATCH"],
)
client = Github(
auth=Auth.Token(auth.token),
timeout=GITHUB_API_TIMEOUT,
retry=retry,
)
> duration = time.time() ** start_time
^^^^^^^^^^^^^^^^^^^^^^^^^
E OverflowError: (34, 'Numerical result out of range')
stampbot/github_client.py:171: OverflowError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetInstallationClient::test_get_installation_client_success - OverflowError: (34, 'Numerical result out of range')
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 47 passed, 3 deselected in 0.77s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_Pow, occurrence: 1
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -176,7 +176,7 @@
return client
except Exception as e:
- duration = time.time() - start_time
+ duration = time.time() ** start_time
github_api_request_duration_seconds.labels(operation="get_token").observe(duration)
github_api_requests_total.labels(operation="get_token", status="failure").inc()
set_span_error(span, e)................................................F
=================================== FAILURES ===================================
________ TestGetInstallationClient.test_get_installation_client_failure ________
self = <stampbot.github_client.GitHubAppClient object at 0x7f4067cdd160>
installation_id = 123456
def _get_installation_client(self, installation_id: int) -> Github:
"""Get authenticated GitHub client for an installation.
Args:
installation_id: GitHub App installation ID.
Returns:
Authenticated Github client instance with timeout and retry configured.
Raises:
github.GithubException: If token exchange fails.
"""
start_time = time.time()
with create_span(
"github.get_installation_token",
{"github.installation_id": installation_id},
) as span:
try:
> auth = self.integration.get_access_token(installation_id)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/github_client.py:155:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1175: in __call__
return self._mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1179: in _mock_call
return self._execute_mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <Mock name='GithubIntegration().get_access_token' id='139914577231952'>
args = (123456,), kwargs = {}, effect = Exception('Token exchange failed')
def _execute_mock_call(self, /, *args, **kwargs):
# separate from _increment_mock_call so that awaited functions are
# executed separately from their call, also AsyncMock overrides this method
effect = self.side_effect
if effect is not None:
if _is_exception(effect):
> raise effect
E Exception: Token exchange failed
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1234: Exception
During handling of the above exception, another exception occurred:
self = <tests.test_github_client.TestGetInstallationClient object at 0x7f406a990190>
def test_get_installation_client_failure(self):
"""Test installation client creation failure."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_integration.get_access_token.side_effect = Exception("Token exchange failed")
mock_integration_cls.return_value = mock_integration
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
with pytest.raises(Exception, match="Token exchange failed"):
> client._get_installation_client(123456)
tests/test_github_client.py:240:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.github_client.GitHubAppClient object at 0x7f4067cdd160>
installation_id = 123456
def _get_installation_client(self, installation_id: int) -> Github:
"""Get authenticated GitHub client for an installation.
Args:
installation_id: GitHub App installation ID.
Returns:
Authenticated Github client instance with timeout and retry configured.
Raises:
github.GithubException: If token exchange fails.
"""
start_time = time.time()
with create_span(
"github.get_installation_token",
{"github.installation_id": installation_id},
) as span:
try:
auth = self.integration.get_access_token(installation_id)
# Configure retry with exponential backoff
retry = Retry(
total=GITHUB_API_RETRY_TOTAL,
backoff_factor=GITHUB_API_RETRY_BACKOFF,
status_forcelist=[500, 502, 503, 504],
allowed_methods=["GET", "POST", "PUT", "DELETE", "PATCH"],
)
client = Github(
auth=Auth.Token(auth.token),
timeout=GITHUB_API_TIMEOUT,
retry=retry,
)
duration = time.time() - start_time
github_api_request_duration_seconds.labels(operation="get_token").observe(duration)
github_api_requests_total.labels(operation="get_token", status="success").inc()
set_span_ok(span)
return client
except Exception as e:
> duration = time.time() ** start_time
^^^^^^^^^^^^^^^^^^^^^^^^^
E OverflowError: (34, 'Numerical result out of range')
stampbot/github_client.py:179: OverflowError
During handling of the above exception, another exception occurred:
self = <tests.test_github_client.TestGetInstallationClient object at 0x7f406a990190>
def test_get_installation_client_failure(self):
"""Test installation client creation failure."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_integration.get_access_token.side_effect = Exception("Token exchange failed")
mock_integration_cls.return_value = mock_integration
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
> with pytest.raises(Exception, match="Token exchange failed"):
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E AssertionError: Regex pattern did not match.
E Expected regex: 'Token exchange failed'
E Actual message: "(34, 'Numerical result out of range')"
tests/test_github_client.py:239: AssertionError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetInstallationClient::test_get_installation_client_failure - AssertionError: Regex pattern did not match.
Expected regex: 'Token exchange failed'
Actual message: "(34, 'Numerical result out of range')"
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 48 passed, 3 deselected in 0.85s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_Pow, occurrence: 2
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -240,7 +240,7 @@
event="APPROVE",
)
- duration = time.time() - start_time
+ duration = time.time() ** start_time
github_api_request_duration_seconds.labels(operation="approve").observe(duration)
github_api_requests_total.labels(operation="approve", status="success").inc()
...................................................F
=================================== FAILURES ===================================
____________________ TestApprovePR.test_approve_pr_success _____________________
self = <tests.test_github_client.TestApprovePR object at 0x7f9c367d8550>
def test_approve_pr_success(self):
"""Test successful PR approval."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_pr = Mock()
mock_repo = Mock()
mock_repo.get_pull.return_value = mock_pr
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
result = client.approve_pr(123456, "owner/repo", 42)
> assert result is True
E assert False is True
tests/test_github_client.py:313: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-05-16 19:51:47 [error ] Failed to approve PR #42 in owner/repo: (34, 'Numerical result out of range') extra={'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': "(34, 'Numerical result out of range')"}
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestApprovePR::test_approve_pr_success - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 51 passed, 3 deselected in 0.74s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_Pow, occurrence: 3
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -260,7 +260,7 @@
return True
except Exception as e:
- duration = time.time() - start_time
+ duration = time.time() ** start_time
github_api_request_duration_seconds.labels(operation="approve").observe(duration)
github_api_requests_total.labels(operation="approve", status="failure").inc()
....................................................F
=================================== FAILURES ===================================
____________________ TestApprovePR.test_approve_pr_failure _____________________
self = <stampbot.github_client.GitHubAppClient object at 0x7f7e5cc5ed00>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42
comment = 'Auto-approved by Stampbot'
def approve_pr(
self,
installation_id: int,
repo_full_name: str,
pr_number: int,
comment: str = "Auto-approved by Stampbot",
) -> bool:
"""Approve a pull request.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name (owner/repo)
pr_number: Pull request number
comment: Review comment
Returns:
True if successful, False otherwise
"""
start_time = time.time()
with create_span(
"github.approve_pr",
{
"github.repo": repo_full_name,
"github.pr_number": pr_number,
"github.installation_id": installation_id,
},
) as span:
try:
client = self._get_installation_client(installation_id)
repo = client.get_repo(repo_full_name)
pr = repo.get_pull(pr_number)
# Create approval review
> pr.create_review(
body=comment,
event="APPROVE",
)
stampbot/github_client.py:238:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1175: in __call__
return self._mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1179: in _mock_call
return self._execute_mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <Mock name='Github().get_repo().get_pull().create_review' id='140180699478544'>
args = (), kwargs = {'body': 'Auto-approved by Stampbot', 'event': 'APPROVE'}
effect = Exception('API Error')
def _execute_mock_call(self, /, *args, **kwargs):
# separate from _increment_mock_call so that awaited functions are
# executed separately from their call, also AsyncMock overrides this method
effect = self.side_effect
if effect is not None:
if _is_exception(effect):
> raise effect
E Exception: API Error
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1234: Exception
During handling of the above exception, another exception occurred:
self = <tests.test_github_client.TestApprovePR object at 0x7f7e60c88690>
def test_approve_pr_failure(self):
"""Test PR approval failure."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_pr = Mock()
mock_pr.create_review.side_effect = Exception("API Error")
mock_repo = Mock()
mock_repo.get_pull.return_value = mock_pr
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
> result = client.approve_pr(123456, "owner/repo", 42)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_github_client.py:350:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.github_client.GitHubAppClient object at 0x7f7e5cc5ed00>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42
comment = 'Auto-approved by Stampbot'
def approve_pr(
self,
installation_id: int,
repo_full_name: str,
pr_number: int,
comment: str = "Auto-approved by Stampbot",
) -> bool:
"""Approve a pull request.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name (owner/repo)
pr_number: Pull request number
comment: Review comment
Returns:
True if successful, False otherwise
"""
start_time = time.time()
with create_span(
"github.approve_pr",
{
"github.repo": repo_full_name,
"github.pr_number": pr_number,
"github.installation_id": installation_id,
},
) as span:
try:
client = self._get_installation_client(installation_id)
repo = client.get_repo(repo_full_name)
pr = repo.get_pull(pr_number)
# Create approval review
pr.create_review(
body=comment,
event="APPROVE",
)
duration = time.time() - start_time
github_api_request_duration_seconds.labels(operation="approve").observe(duration)
github_api_requests_total.labels(operation="approve", status="success").inc()
self._update_rate_limit_metrics(client, installation_id)
add_span_attributes(span, {"github.result": "approved"})
set_span_ok(span)
logger.info(
f"Approved PR #{pr_number} in {repo_full_name}",
extra={
"repo": repo_full_name,
"pr_number": pr_number,
"installation_id": installation_id,
},
)
return True
except Exception as e:
> duration = time.time() ** start_time
^^^^^^^^^^^^^^^^^^^^^^^^^
E OverflowError: (34, 'Numerical result out of range')
stampbot/github_client.py:263: OverflowError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestApprovePR::test_approve_pr_failure - OverflowError: (34, 'Numerical result out of range')
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 52 passed, 3 deselected in 0.85s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_Pow, occurrence: 4
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -317,7 +317,7 @@
review = pr.get_review(review_id)
review.dismiss(message)
- duration = time.time() - start_time
+ duration = time.time() ** start_time
github_api_request_duration_seconds.labels(operation="dismiss").observe(duration)
github_api_requests_total.labels(operation="dismiss", status="success").inc()
.....................................................F
=================================== FAILURES ===================================
______________ TestDismissApproval.test_dismiss_approval_success _______________
self = <tests.test_github_client.TestDismissApproval object at 0x7f54787c87d0>
def test_dismiss_approval_success(self):
"""Test successful approval dismissal."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_review = Mock()
mock_pr = Mock()
mock_pr.get_review.return_value = mock_review
mock_repo = Mock()
mock_repo.get_pull.return_value = mock_pr
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
result = client.dismiss_approval(123456, "owner/repo", 42, 999)
> assert result is True
E assert False is True
tests/test_github_client.py:396: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-05-16 19:43:15 [error ] Failed to dismiss approval for PR #42 in owner/repo: (34, 'Numerical result out of range') extra={'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': "(34, 'Numerical result out of range')"}
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestDismissApproval::test_dismiss_approval_success - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 53 passed, 3 deselected in 0.74s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_Pow, occurrence: 5
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -338,7 +338,7 @@
return True
except Exception as e:
- duration = time.time() - start_time
+ duration = time.time() ** start_time
github_api_request_duration_seconds.labels(operation="dismiss").observe(duration)
github_api_requests_total.labels(operation="dismiss", status="failure").inc()
......................................................F
=================================== FAILURES ===================================
______________ TestDismissApproval.test_dismiss_approval_failure _______________
self = <stampbot.github_client.GitHubAppClient object at 0x7efbfd2268f0>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42
review_id = 999, message = 'Approval dismissed by Stampbot'
def dismiss_approval(
self,
installation_id: int,
repo_full_name: str,
pr_number: int,
review_id: int,
message: str = "Approval dismissed by Stampbot",
) -> bool:
"""Dismiss a pull request approval.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name (owner/repo)
pr_number: Pull request number
review_id: Review ID to dismiss
message: Dismissal message
Returns:
True if successful, False otherwise
"""
start_time = time.time()
with create_span(
"github.dismiss_approval",
{
"github.repo": repo_full_name,
"github.pr_number": pr_number,
"github.review_id": review_id,
"github.installation_id": installation_id,
},
) as span:
try:
client = self._get_installation_client(installation_id)
repo = client.get_repo(repo_full_name)
pr = repo.get_pull(pr_number)
# Get the review and dismiss it
review = pr.get_review(review_id)
> review.dismiss(message)
stampbot/github_client.py:318:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1175: in __call__
return self._mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1179: in _mock_call
return self._execute_mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <Mock name='Github().get_repo().get_pull().get_review().dismiss' id='139620747654352'>
args = ('Approval dismissed by Stampbot',), kwargs = {}
effect = Exception('API Error')
def _execute_mock_call(self, /, *args, **kwargs):
# separate from _increment_mock_call so that awaited functions are
# executed separately from their call, also AsyncMock overrides this method
effect = self.side_effect
if effect is not None:
if _is_exception(effect):
> raise effect
E Exception: API Error
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1234: Exception
During handling of the above exception, another exception occurred:
self = <tests.test_github_client.TestDismissApproval object at 0x7efc0154c910>
def test_dismiss_approval_failure(self):
"""Test approval dismissal failure."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_review = Mock()
mock_review.dismiss.side_effect = Exception("API Error")
mock_pr = Mock()
mock_pr.get_review.return_value = mock_review
mock_repo = Mock()
mock_repo.get_pull.return_value = mock_pr
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
> result = client.dismiss_approval(123456, "owner/repo", 42, 999)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_github_client.py:435:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.github_client.GitHubAppClient object at 0x7efbfd2268f0>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42
review_id = 999, message = 'Approval dismissed by Stampbot'
def dismiss_approval(
self,
installation_id: int,
repo_full_name: str,
pr_number: int,
review_id: int,
message: str = "Approval dismissed by Stampbot",
) -> bool:
"""Dismiss a pull request approval.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name (owner/repo)
pr_number: Pull request number
review_id: Review ID to dismiss
message: Dismissal message
Returns:
True if successful, False otherwise
"""
start_time = time.time()
with create_span(
"github.dismiss_approval",
{
"github.repo": repo_full_name,
"github.pr_number": pr_number,
"github.review_id": review_id,
"github.installation_id": installation_id,
},
) as span:
try:
client = self._get_installation_client(installation_id)
repo = client.get_repo(repo_full_name)
pr = repo.get_pull(pr_number)
# Get the review and dismiss it
review = pr.get_review(review_id)
review.dismiss(message)
duration = time.time() - start_time
github_api_request_duration_seconds.labels(operation="dismiss").observe(duration)
github_api_requests_total.labels(operation="dismiss", status="success").inc()
self._update_rate_limit_metrics(client, installation_id)
add_span_attributes(span, {"github.result": "dismissed"})
set_span_ok(span)
logger.info(
f"Dismissed approval for PR #{pr_number} in {repo_full_name}",
extra={
"repo": repo_full_name,
"pr_number": pr_number,
"installation_id": installation_id,
"review_id": review_id,
},
)
return True
except Exception as e:
> duration = time.time() ** start_time
^^^^^^^^^^^^^^^^^^^^^^^^^
E OverflowError: (34, 'Numerical result out of range')
stampbot/github_client.py:341: OverflowError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestDismissApproval::test_dismiss_approval_failure - OverflowError: (34, 'Numerical result out of range')
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 54 passed, 3 deselected in 0.86s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_Pow, occurrence: 6
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -393,7 +393,7 @@
content = repo.get_contents(file_path, ref=ref) # type: ignore[arg-type]
if isinstance(content, list):
- duration = time.time() - start_time
+ duration = time.time() ** start_time
github_api_request_duration_seconds.labels(operation="get_file").observe(
duration
)........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.65s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_Pow, occurrence: 7
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -402,7 +402,7 @@
set_span_ok(span)
return None
- duration = time.time() - start_time
+ duration = time.time() ** start_time
github_api_request_duration_seconds.labels(operation="get_file").observe(duration)
github_api_requests_total.labels(operation="get_file", status="success").inc()
.......................................................F
=================================== FAILURES ===================================
__________________ TestGetRepoFile.test_get_repo_file_success __________________
self = <tests.test_github_client.TestGetRepoFile object at 0x7f1b1dad8a50>
def test_get_repo_file_success(self):
"""Test successful file retrieval."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_content = Mock()
mock_content.decoded_content = b"file content"
mock_repo = Mock()
mock_repo.get_contents.return_value = mock_content
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
result = client.get_repo_file(123456, "owner/repo", "stampbot.toml")
> assert result == "file content"
E AssertionError: assert None == 'file content'
tests/test_github_client.py:480: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-05-16 19:43:07 [debug ] Could not fetch stampbot.toml from owner/repo: (34, 'Numerical result out of range') extra={'repo': 'owner/repo', 'file_path': 'stampbot.toml', 'installation_id': 123456}
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetRepoFile::test_get_repo_file_success - AssertionError: assert None == 'file content'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 55 passed, 3 deselected in 0.75s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_Pow, occurrence: 8
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -414,7 +414,7 @@
return content.decoded_content.decode("utf-8")
except Exception as e:
- duration = time.time() - start_time
+ duration = time.time() ** start_time
github_api_request_duration_seconds.labels(operation="get_file").observe(duration)
github_api_requests_total.labels(operation="get_file", status="not_found").inc()
.........................................................F
=================================== FAILURES ===================================
___________ TestGetRepoFile.test_get_repo_file_returns_none_on_error ___________
self = <stampbot.github_client.GitHubAppClient object at 0x7f381b650cd0>
installation_id = 123456, repo_full_name = 'owner/repo'
file_path = 'nonexistent.toml', ref = None
def get_repo_file(
self,
installation_id: int,
repo_full_name: str,
file_path: str,
ref: str | None = None,
) -> str | None:
"""Get file content from repository.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name (owner/repo)
file_path: Path to file in repository
ref: Git reference (branch, tag, commit). Defaults to default branch
Returns:
File content as string, or None if not found
"""
start_time = time.time()
with create_span(
"github.get_file",
{
"github.repo": repo_full_name,
"github.file_path": file_path,
"github.ref": ref or "default",
"github.installation_id": installation_id,
},
) as span:
try:
client = self._get_installation_client(installation_id)
repo = client.get_repo(repo_full_name)
> content = repo.get_contents(file_path, ref=ref) # type: ignore[arg-type]
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/github_client.py:394:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1175: in __call__
return self._mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1179: in _mock_call
return self._execute_mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <Mock name='Github().get_repo().get_contents' id='139878954813152'>
args = ('nonexistent.toml',), kwargs = {'ref': None}
effect = Exception('File not found')
def _execute_mock_call(self, /, *args, **kwargs):
# separate from _increment_mock_call so that awaited functions are
# executed separately from their call, also AsyncMock overrides this method
effect = self.side_effect
if effect is not None:
if _is_exception(effect):
> raise effect
E Exception: File not found
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1234: Exception
During handling of the above exception, another exception occurred:
self = <tests.test_github_client.TestGetRepoFile object at 0x7f381fa55940>
def test_get_repo_file_returns_none_on_error(self):
"""Test that get_repo_file returns None when file not found."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_repo = Mock()
mock_repo.get_contents.side_effect = Exception("File not found")
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
> result = client.get_repo_file(123456, "owner/repo", "nonexistent.toml")
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_github_client.py:551:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.github_client.GitHubAppClient object at 0x7f381b650cd0>
installation_id = 123456, repo_full_name = 'owner/repo'
file_path = 'nonexistent.toml', ref = None
def get_repo_file(
self,
installation_id: int,
repo_full_name: str,
file_path: str,
ref: str | None = None,
) -> str | None:
"""Get file content from repository.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name (owner/repo)
file_path: Path to file in repository
ref: Git reference (branch, tag, commit). Defaults to default branch
Returns:
File content as string, or None if not found
"""
start_time = time.time()
with create_span(
"github.get_file",
{
"github.repo": repo_full_name,
"github.file_path": file_path,
"github.ref": ref or "default",
"github.installation_id": installation_id,
},
) as span:
try:
client = self._get_installation_client(installation_id)
repo = client.get_repo(repo_full_name)
content = repo.get_contents(file_path, ref=ref) # type: ignore[arg-type]
if isinstance(content, list):
duration = time.time() - start_time
github_api_request_duration_seconds.labels(operation="get_file").observe(
duration
)
github_api_requests_total.labels(operation="get_file", status="not_found").inc()
add_span_attributes(span, {"github.result": "not_found"})
set_span_ok(span)
return None
duration = time.time() - start_time
github_api_request_duration_seconds.labels(operation="get_file").observe(duration)
github_api_requests_total.labels(operation="get_file", status="success").inc()
self._update_rate_limit_metrics(client, installation_id)
add_span_attributes(span, {"github.result": "found"})
set_span_ok(span)
return content.decoded_content.decode("utf-8")
except Exception as e:
> duration = time.time() ** start_time
^^^^^^^^^^^^^^^^^^^^^^^^^
E OverflowError: (34, 'Numerical result out of range')
stampbot/github_client.py:417: OverflowError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetRepoFile::test_get_repo_file_returns_none_on_error - OverflowError: (34, 'Numerical result out of range')
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 57 passed, 3 deselected in 0.87s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_Pow, occurrence: 9
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -473,7 +473,7 @@
if review.user.login == bot_user and review.state == "APPROVED":
bot_review_ids.append(review.id)
- duration = time.time() - start_time
+ duration = time.time() ** start_time
github_api_request_duration_seconds.labels(operation="find_reviews").observe(
duration
)..........................................................F
=================================== FAILURES ===================================
_______________ TestFindBotReviews.test_find_bot_reviews_success _______________
self = <tests.test_github_client.TestFindBotReviews object at 0x7fecc3f74cd0>
def test_find_bot_reviews_success(self):
"""Test successful bot review finding."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration.get_app.return_value = Mock(slug="stampbot")
mock_integration_cls.return_value = mock_integration
# Create mock reviews
bot_review = Mock()
bot_review.user.login = "stampbot[bot]"
bot_review.state = "APPROVED"
bot_review.id = 123
other_review = Mock()
other_review.user.login = "other-user"
other_review.state = "APPROVED"
other_review.id = 456
mock_pr = Mock()
mock_pr.get_reviews.return_value = [bot_review, other_review]
mock_repo = Mock()
mock_repo.get_pull.return_value = mock_pr
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
result = client.find_bot_reviews(123456, "owner/repo", 42)
> assert result == [123]
E assert [] == [123]
E
E Right contains one more item: 123
E
E Full diff:
E + []
E - [
E - 123,
E - ]
tests/test_github_client.py:608: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-05-16 19:28:56 [error ] Failed to find bot reviews for PR #42 in owner/repo: (34, 'Numerical result out of range') extra={'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': "(34, 'Numerical result out of range')"}
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestFindBotReviews::test_find_bot_reviews_success - assert [] == [123]
Right contains one more item: 123
Full diff:
+ []
- [
- 123,
- ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 58 passed, 3 deselected in 0.75s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_Pow, occurrence: 10
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -489,7 +489,7 @@
return bot_review_ids
except Exception as e:
- duration = time.time() - start_time
+ duration = time.time() ** start_time
github_api_request_duration_seconds.labels(operation="find_reviews").observe(
duration
)...........................................................F
=================================== FAILURES ===================================
_______ TestFindBotReviews.test_find_bot_reviews_returns_empty_on_error ________
self = <stampbot.github_client.GitHubAppClient object at 0x7f5b15a74f30>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42
def find_bot_reviews(
self,
installation_id: int,
repo_full_name: str,
pr_number: int,
) -> list[int]:
"""Find all reviews created by this bot on a PR.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name (owner/repo)
pr_number: Pull request number
Returns:
List of review IDs created by the bot
"""
start_time = time.time()
with create_span(
"github.find_bot_reviews",
{
"github.repo": repo_full_name,
"github.pr_number": pr_number,
"github.installation_id": installation_id,
},
) as span:
try:
client = self._get_installation_client(installation_id)
> repo = client.get_repo(repo_full_name)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/github_client.py:462:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1175: in __call__
return self._mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1179: in _mock_call
return self._execute_mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <Mock name='Github().get_repo' id='140029182509808'>
args = ('owner/repo',), kwargs = {}, effect = Exception('API Error')
def _execute_mock_call(self, /, *args, **kwargs):
# separate from _increment_mock_call so that awaited functions are
# executed separately from their call, also AsyncMock overrides this method
effect = self.side_effect
if effect is not None:
if _is_exception(effect):
> raise effect
E Exception: API Error
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1234: Exception
During handling of the above exception, another exception occurred:
self = <tests.test_github_client.TestFindBotReviews object at 0x7f5b19a64e10>
def test_find_bot_reviews_returns_empty_on_error(self):
"""Test that find_bot_reviews returns empty list on error."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_github = Mock()
mock_github.get_repo.side_effect = Exception("API Error")
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
> result = client.find_bot_reviews(123456, "owner/repo", 42)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_github_client.py:640:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.github_client.GitHubAppClient object at 0x7f5b15a74f30>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42
def find_bot_reviews(
self,
installation_id: int,
repo_full_name: str,
pr_number: int,
) -> list[int]:
"""Find all reviews created by this bot on a PR.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name (owner/repo)
pr_number: Pull request number
Returns:
List of review IDs created by the bot
"""
start_time = time.time()
with create_span(
"github.find_bot_reviews",
{
"github.repo": repo_full_name,
"github.pr_number": pr_number,
"github.installation_id": installation_id,
},
) as span:
try:
client = self._get_installation_client(installation_id)
repo = client.get_repo(repo_full_name)
pr = repo.get_pull(pr_number)
# Get bot user via JWT-authenticated integration (installation tokens
# cannot call GET /user, which is restricted by GitHub Apps integration)
app_info = self.integration.get_app()
bot_user = f"{app_info.slug}[bot]"
# Find all reviews by bot that are approvals
bot_review_ids = []
for review in pr.get_reviews():
if review.user.login == bot_user and review.state == "APPROVED":
bot_review_ids.append(review.id)
duration = time.time() - start_time
github_api_request_duration_seconds.labels(operation="find_reviews").observe(
duration
)
github_api_requests_total.labels(operation="find_reviews", status="success").inc()
self._update_rate_limit_metrics(client, installation_id)
add_span_attributes(
span, {"github.reviews_found": len(bot_review_ids), "github.bot_user": bot_user}
)
set_span_ok(span)
return bot_review_ids
except Exception as e:
> duration = time.time() ** start_time
^^^^^^^^^^^^^^^^^^^^^^^^^
E OverflowError: (34, 'Numerical result out of range')
stampbot/github_client.py:492: OverflowError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestFindBotReviews::test_find_bot_reviews_returns_empty_on_error - OverflowError: (34, 'Numerical result out of range')
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 59 passed, 3 deselected in 0.87s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_Pow, occurrence: 11
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -546,7 +546,7 @@
pr.create_review(body=message, event="COMMENT")
- duration = time.time() - start_time
+ duration = time.time() ** start_time
github_api_request_duration_seconds.labels(operation="comment").observe(duration)
github_api_requests_total.labels(operation="comment", status="success").inc()
........................................................................ [ 34%]
.F
=================================== FAILURES ===================================
_______ TestCreatePrReviewComment.test_create_pr_review_comment_success ________
self = <tests.test_github_client.TestCreatePrReviewComment object at 0x7f91cda856d0>
def test_create_pr_review_comment_success(self):
"""Test successful review comment creation."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_pr = Mock()
mock_repo = Mock()
mock_repo.get_pull.return_value = mock_pr
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=1, limit=2))
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
result = client.create_pr_review_comment(
123456,
"owner/repo",
42,
"Config error",
)
> assert result is True
E assert False is True
tests/test_github_client.py:1213: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-05-16 19:43:36 [warning ] Failed to post config error review for PR #42 in owner/repo: (34, 'Numerical result out of range') extra={'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': "(34, 'Numerical result out of range')"}
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestCreatePrReviewComment::test_create_pr_review_comment_success - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 73 passed, 3 deselected in 0.80s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_Pow, occurrence: 12
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -568,7 +568,7 @@
return True
except Exception as e:
- duration = time.time() - start_time
+ duration = time.time() ** start_time
github_api_request_duration_seconds.labels(operation="comment").observe(duration)
github_api_requests_total.labels(operation="comment", status="failure").inc()
........................................................................ [ 34%]
..F
=================================== FAILURES ===================================
_______ TestCreatePrReviewComment.test_create_pr_review_comment_failure ________
self = <stampbot.github_client.GitHubAppClient object at 0x7f1fdf6b6390>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42
message = 'Config error'
def create_pr_review_comment(
self,
installation_id: int,
repo_full_name: str,
pr_number: int,
message: str,
) -> bool:
"""Create a comment review on a pull request.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name (owner/repo)
pr_number: Pull request number
message: Review comment body
Returns:
True if successful, False otherwise
"""
start_time = time.time()
with create_span(
"github.create_review_comment",
{
"github.repo": repo_full_name,
"github.pr_number": pr_number,
"github.installation_id": installation_id,
},
) as span:
try:
client = self._get_installation_client(installation_id)
repo = client.get_repo(repo_full_name)
> pr = repo.get_pull(pr_number)
^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/github_client.py:545:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1175: in __call__
return self._mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1179: in _mock_call
return self._execute_mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <Mock name='Github().get_repo().get_pull' id='139774868999200'>
args = (42,), kwargs = {}, effect = Exception('API Error')
def _execute_mock_call(self, /, *args, **kwargs):
# separate from _increment_mock_call so that awaited functions are
# executed separately from their call, also AsyncMock overrides this method
effect = self.side_effect
if effect is not None:
if _is_exception(effect):
> raise effect
E Exception: API Error
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1234: Exception
During handling of the above exception, another exception occurred:
self = <tests.test_github_client.TestCreatePrReviewComment object at 0x7f1fe3c55810>
def test_create_pr_review_comment_failure(self):
"""Test review comment creation handles errors."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_repo = Mock()
mock_repo.get_pull.side_effect = Exception("API Error")
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
> result = client.create_pr_review_comment(
123456,
"owner/repo",
42,
"Config error",
)
tests/test_github_client.py:1248:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.github_client.GitHubAppClient object at 0x7f1fdf6b6390>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42
message = 'Config error'
def create_pr_review_comment(
self,
installation_id: int,
repo_full_name: str,
pr_number: int,
message: str,
) -> bool:
"""Create a comment review on a pull request.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name (owner/repo)
pr_number: Pull request number
message: Review comment body
Returns:
True if successful, False otherwise
"""
start_time = time.time()
with create_span(
"github.create_review_comment",
{
"github.repo": repo_full_name,
"github.pr_number": pr_number,
"github.installation_id": installation_id,
},
) as span:
try:
client = self._get_installation_client(installation_id)
repo = client.get_repo(repo_full_name)
pr = repo.get_pull(pr_number)
pr.create_review(body=message, event="COMMENT")
duration = time.time() - start_time
github_api_request_duration_seconds.labels(operation="comment").observe(duration)
github_api_requests_total.labels(operation="comment", status="success").inc()
self._update_rate_limit_metrics(client, installation_id)
add_span_attributes(span, {"github.result": "commented"})
set_span_ok(span)
logger.info(
"Posted config error review on PR #%d in %s",
pr_number,
repo_full_name,
extra={
"repo": repo_full_name,
"pr_number": pr_number,
"installation_id": installation_id,
},
)
return True
except Exception as e:
> duration = time.time() ** start_time
^^^^^^^^^^^^^^^^^^^^^^^^^
E OverflowError: (34, 'Numerical result out of range')
stampbot/github_client.py:571: OverflowError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestCreatePrReviewComment::test_create_pr_review_comment_failure - OverflowError: (34, 'Numerical result out of range')
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 74 passed, 3 deselected in 0.92s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_Pow, occurrence: 13
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -619,7 +619,7 @@
repo = client.get_repo(repo_full_name)
repo.get_label(label_name)
- duration = time.time() - start_time
+ duration = time.time() ** start_time
github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
github_api_requests_total.labels(operation="get_label", status="success").inc()
.....................................................................F
=================================== FAILURES ===================================
__________________ TestRepoHasLabel.test_repo_has_label_true ___________________
self = <tests.test_github_client.TestRepoHasLabel object at 0x7f5adaf79450>
def test_repo_has_label_true(self):
"""Test repo_has_label returns True when label exists."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_repo = Mock()
mock_repo.get_label.return_value = Mock()
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=1, limit=2))
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
result = client.repo_has_label(123456, "owner/repo", "autoapprove")
> assert result is True
E assert None is True
tests/test_github_client.py:1054: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-05-16 19:44:53 [debug ] Could not verify label autoapprove in owner/repo: (34, 'Numerical result out of range') extra={'repo': 'owner/repo', 'label': 'autoapprove', 'installation_id': 123456, 'error': "(34, 'Numerical result out of range')"}
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestRepoHasLabel::test_repo_has_label_true - assert None is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 69 passed, 3 deselected in 0.78s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_Pow, occurrence: 14
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -630,7 +630,7 @@
return True
except GithubException as e:
- duration = time.time() - start_time
+ duration = time.time() ** start_time
github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
github_api_requests_total.labels(operation="get_label", status="failure").inc()
......................................................................F
=================================== FAILURES ===================================
________________ TestRepoHasLabel.test_repo_has_label_not_found ________________
self = <stampbot.github_client.GitHubAppClient object at 0x7f9363e31f70>
installation_id = 123456, repo_full_name = 'owner/repo', label_name = 'missing'
def repo_has_label(
self,
installation_id: int,
repo_full_name: str,
label_name: str,
) -> bool | None:
"""Check whether a repository has a label.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name (owner/repo)
label_name: Label name to look up
Returns:
True if label exists, False if not found, None if unknown due to error
"""
start_time = time.time()
with create_span(
"github.get_label",
{
"github.repo": repo_full_name,
"github.label": label_name,
"github.installation_id": installation_id,
},
) as span:
try:
client = self._get_installation_client(installation_id)
repo = client.get_repo(repo_full_name)
> repo.get_label(label_name)
stampbot/github_client.py:620:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1175: in __call__
return self._mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1179: in _mock_call
return self._execute_mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <Mock name='Github().get_repo().get_label' id='140271012571392'>
args = ('missing',), kwargs = {}
effect = GithubException(404 {"message": "Not Found"})
def _execute_mock_call(self, /, *args, **kwargs):
# separate from _increment_mock_call so that awaited functions are
# executed separately from their call, also AsyncMock overrides this method
effect = self.side_effect
if effect is not None:
if _is_exception(effect):
> raise effect
E github.GithubException.GithubException: 404 {"message": "Not Found"}
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1234: GithubException
During handling of the above exception, another exception occurred:
self = <tests.test_github_client.TestRepoHasLabel object at 0x7f936c2a5590>
def test_repo_has_label_not_found(self):
"""Test repo_has_label returns False when label is missing."""
from github.GithubException import GithubException
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_repo = Mock()
mock_repo.get_label.side_effect = GithubException(404, {"message": "Not Found"}, None)
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
> result = client.repo_has_label(123456, "owner/repo", "missing")
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_github_client.py:1090:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.github_client.GitHubAppClient object at 0x7f9363e31f70>
installation_id = 123456, repo_full_name = 'owner/repo', label_name = 'missing'
def repo_has_label(
self,
installation_id: int,
repo_full_name: str,
label_name: str,
) -> bool | None:
"""Check whether a repository has a label.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name (owner/repo)
label_name: Label name to look up
Returns:
True if label exists, False if not found, None if unknown due to error
"""
start_time = time.time()
with create_span(
"github.get_label",
{
"github.repo": repo_full_name,
"github.label": label_name,
"github.installation_id": installation_id,
},
) as span:
try:
client = self._get_installation_client(installation_id)
repo = client.get_repo(repo_full_name)
repo.get_label(label_name)
duration = time.time() - start_time
github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
github_api_requests_total.labels(operation="get_label", status="success").inc()
self._update_rate_limit_metrics(client, installation_id)
add_span_attributes(span, {"github.label_found": True})
set_span_ok(span)
return True
except GithubException as e:
> duration = time.time() ** start_time
^^^^^^^^^^^^^^^^^^^^^^^^^
E OverflowError: (34, 'Numerical result out of range')
stampbot/github_client.py:633: OverflowError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestRepoHasLabel::test_repo_has_label_not_found - OverflowError: (34, 'Numerical result out of range')
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 70 passed, 3 deselected in 0.91s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_Pow, occurrence: 15
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -655,7 +655,7 @@
return None
except Exception as e:
- duration = time.time() - start_time
+ duration = time.time() ** start_time
github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
github_api_requests_total.labels(operation="get_label", status="failure").inc()
........................................................................ [ 34%]
F
=================================== FAILURES ===================================
__________________ TestRepoHasLabel.test_repo_has_label_error __________________
self = <stampbot.github_client.GitHubAppClient object at 0x7fd3fd9641d0>
installation_id = 123456, repo_full_name = 'owner/repo'
label_name = 'autoapprove'
def repo_has_label(
self,
installation_id: int,
repo_full_name: str,
label_name: str,
) -> bool | None:
"""Check whether a repository has a label.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name (owner/repo)
label_name: Label name to look up
Returns:
True if label exists, False if not found, None if unknown due to error
"""
start_time = time.time()
with create_span(
"github.get_label",
{
"github.repo": repo_full_name,
"github.label": label_name,
"github.installation_id": installation_id,
},
) as span:
try:
client = self._get_installation_client(installation_id)
repo = client.get_repo(repo_full_name)
> repo.get_label(label_name)
stampbot/github_client.py:620:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1175: in __call__
return self._mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1179: in _mock_call
return self._execute_mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <Mock name='Github().get_repo().get_label' id='140548469200544'>
args = ('autoapprove',), kwargs = {}, effect = Exception('API Error')
def _execute_mock_call(self, /, *args, **kwargs):
# separate from _increment_mock_call so that awaited functions are
# executed separately from their call, also AsyncMock overrides this method
effect = self.side_effect
if effect is not None:
if _is_exception(effect):
> raise effect
E Exception: API Error
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1234: Exception
During handling of the above exception, another exception occurred:
self = <tests.test_github_client.TestRepoHasLabel object at 0x7fd401ee2060>
def test_repo_has_label_error(self):
"""Test repo_has_label returns None on error."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_repo = Mock()
mock_repo.get_label.side_effect = Exception("API Error")
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
> result = client.repo_has_label(123456, "owner/repo", "autoapprove")
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_github_client.py:1164:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.github_client.GitHubAppClient object at 0x7fd3fd9641d0>
installation_id = 123456, repo_full_name = 'owner/repo'
label_name = 'autoapprove'
def repo_has_label(
self,
installation_id: int,
repo_full_name: str,
label_name: str,
) -> bool | None:
"""Check whether a repository has a label.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name (owner/repo)
label_name: Label name to look up
Returns:
True if label exists, False if not found, None if unknown due to error
"""
start_time = time.time()
with create_span(
"github.get_label",
{
"github.repo": repo_full_name,
"github.label": label_name,
"github.installation_id": installation_id,
},
) as span:
try:
client = self._get_installation_client(installation_id)
repo = client.get_repo(repo_full_name)
repo.get_label(label_name)
duration = time.time() - start_time
github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
github_api_requests_total.labels(operation="get_label", status="success").inc()
self._update_rate_limit_metrics(client, installation_id)
add_span_attributes(span, {"github.label_found": True})
set_span_ok(span)
return True
except GithubException as e:
duration = time.time() - start_time
github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
github_api_requests_total.labels(operation="get_label", status="failure").inc()
if e.status == 404:
add_span_attributes(span, {"github.label_found": False})
set_span_ok(span)
return False
set_span_error(span, e)
logger.debug(
"Could not verify label %s in %s: %s",
label_name,
repo_full_name,
_sanitize_error(e),
extra={
"repo": repo_full_name,
"label": label_name,
"installation_id": installation_id,
"error": _sanitize_error(e),
},
)
return None
except Exception as e:
> duration = time.time() ** start_time
^^^^^^^^^^^^^^^^^^^^^^^^^
E OverflowError: (34, 'Numerical result out of range')
stampbot/github_client.py:658: OverflowError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestRepoHasLabel::test_repo_has_label_error - OverflowError: (34, 'Numerical result out of range')
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 72 passed, 3 deselected in 0.94s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_Pow, occurrence: 16
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -718,7 +718,7 @@
has_permission = permission_index >= required_index
- duration = time.time() - start_time
+ duration = time.time() ** start_time
github_api_request_duration_seconds.labels(operation="get_permission").observe(
duration
)............................................................F
=================================== FAILURES ===================================
_____________ TestUserHasPermission.test_user_has_permission_true ______________
self = <tests.test_github_client.TestUserHasPermission object at 0x7fea62e20f50>
def test_user_has_permission_true(self):
"""Test required permission satisfied returns True."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_repo = Mock()
mock_repo.get_collaborator_permission.return_value = "write"
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=1, limit=2))
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
result = client.user_has_permission(123456, "owner/repo", "alice", "write")
> assert result is True
E assert False is True
tests/test_github_client.py:683: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-05-16 19:28:08 [warning ] Failed to check collaborator permission for alice in owner/repo: (34, 'Numerical result out of range') extra={'repo': 'owner/repo', 'username': 'alice', 'installation_id': 123456, 'error': "(34, 'Numerical result out of range')"}
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestUserHasPermission::test_user_has_permission_true - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 60 passed, 3 deselected in 0.76s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_Pow, occurrence: 17
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -738,7 +738,7 @@
return has_permission
except Exception as e:
- duration = time.time() - start_time
+ duration = time.time() ** start_time
github_api_request_duration_seconds.labels(operation="get_permission").observe(
duration
)..............................................................F
=================================== FAILURES ===================================
_____________ TestUserHasPermission.test_user_has_permission_error _____________
self = <stampbot.github_client.GitHubAppClient object at 0x7f4cd6c342f0>
installation_id = 123456, repo_full_name = 'owner/repo', username = 'carol'
required_permission = 'maintain'
def user_has_permission(
self,
installation_id: int,
repo_full_name: str,
username: str,
required_permission: str,
) -> bool:
"""Check whether a user has required access to a repository.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name (owner/repo)
username: GitHub username to check
required_permission: Minimum required repository permission
Returns:
True if user meets or exceeds required permission, False otherwise
"""
start_time = time.time()
with create_span(
"github.get_collaborator_permission",
{
"github.repo": repo_full_name,
"github.username": username,
"github.required_permission": required_permission,
"github.installation_id": installation_id,
},
) as span:
try:
client = self._get_installation_client(installation_id)
repo = client.get_repo(repo_full_name)
> permission = repo.get_collaborator_permission(username)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/github_client.py:710:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1175: in __call__
return self._mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1179: in _mock_call
return self._execute_mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <Mock name='Github().get_repo().get_collaborator_permission' id='139967997316128'>
args = ('carol',), kwargs = {}, effect = Exception('API Error')
def _execute_mock_call(self, /, *args, **kwargs):
# separate from _increment_mock_call so that awaited functions are
# executed separately from their call, also AsyncMock overrides this method
effect = self.side_effect
if effect is not None:
if _is_exception(effect):
> raise effect
E Exception: API Error
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1234: Exception
During handling of the above exception, another exception occurred:
self = <tests.test_github_client.TestUserHasPermission object at 0x7f4cdb0f5a70>
def test_user_has_permission_error(self):
"""Test permission check returns False on error."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_repo = Mock()
mock_repo.get_collaborator_permission.side_effect = Exception("API Error")
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
> result = client.user_has_permission(123456, "owner/repo", "carol", "maintain")
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_github_client.py:754:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.github_client.GitHubAppClient object at 0x7f4cd6c342f0>
installation_id = 123456, repo_full_name = 'owner/repo', username = 'carol'
required_permission = 'maintain'
def user_has_permission(
self,
installation_id: int,
repo_full_name: str,
username: str,
required_permission: str,
) -> bool:
"""Check whether a user has required access to a repository.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name (owner/repo)
username: GitHub username to check
required_permission: Minimum required repository permission
Returns:
True if user meets or exceeds required permission, False otherwise
"""
start_time = time.time()
with create_span(
"github.get_collaborator_permission",
{
"github.repo": repo_full_name,
"github.username": username,
"github.required_permission": required_permission,
"github.installation_id": installation_id,
},
) as span:
try:
client = self._get_installation_client(installation_id)
repo = client.get_repo(repo_full_name)
permission = repo.get_collaborator_permission(username)
permission_order = ["none", "read", "triage", "write", "maintain", "admin"]
try:
permission_index = permission_order.index(permission)
required_index = permission_order.index(required_permission)
except ValueError:
permission_index = -1
required_index = 99
has_permission = permission_index >= required_index
duration = time.time() - start_time
github_api_request_duration_seconds.labels(operation="get_permission").observe(
duration
)
github_api_requests_total.labels(operation="get_permission", status="success").inc()
self._update_rate_limit_metrics(client, installation_id)
add_span_attributes(
span,
{
"github.permission": permission,
"github.has_permission": has_permission,
},
)
set_span_ok(span)
return has_permission
except Exception as e:
> duration = time.time() ** start_time
^^^^^^^^^^^^^^^^^^^^^^^^^
E OverflowError: (34, 'Numerical result out of range')
stampbot/github_client.py:741: OverflowError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestUserHasPermission::test_user_has_permission_error - OverflowError: (34, 'Numerical result out of range')
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 62 passed, 3 deselected in 0.88s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_Pow, occurrence: 18
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -829,7 +829,7 @@
)
continue
- duration = time.time() - start_time
+ duration = time.time() ** start_time
github_api_request_duration_seconds.labels(operation="get_user_teams").observe(
duration
)................................................................F
=================================== FAILURES ===================================
____________ TestGetUserTeamSlugs.test_get_user_team_slugs_success _____________
self = <tests.test_github_client.TestGetUserTeamSlugs object at 0x7f51e5e591d0>
def test_get_user_team_slugs_success(self):
"""Test successful team membership check."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
# Mock user
mock_user = Mock()
mock_user.login = "alice"
# Mock team that has alice as member
mock_team = Mock()
mock_team.has_in_members.return_value = True
# Mock org
mock_org = Mock()
mock_org.get_team_by_slug.return_value = mock_team
mock_github = Mock()
mock_github.get_organization.return_value = mock_org
mock_github.get_user.return_value = mock_user
mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
result = client.get_user_team_slugs(
123456, "acme", "alice", ["release-team", "deploy-team"]
)
> assert result == ["release-team", "deploy-team"]
E AssertionError: assert [] == ['release-tea...'deploy-team']
E
E Right contains 2 more items, first extra item: 'release-team'
E
E Full diff:
E + []
E - [
E - 'release-team',
E - 'deploy-team',
E - ]
tests/test_github_client.py:848: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-05-16 19:41:16 [debug ] User alice is a member of team release-team extra={'org': 'acme', 'username': 'alice', 'team': 'release-team'}
2026-05-16 19:41:16 [debug ] User alice is a member of team deploy-team extra={'org': 'acme', 'username': 'alice', 'team': 'deploy-team'}
2026-05-16 19:41:16 [warning ] Failed to check team memberships for alice in acme: (34, 'Numerical result out of range') extra={'org': 'acme', 'username': 'alice', 'installation_id': 123456, 'error': "(34, 'Numerical result out of range')"}
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetUserTeamSlugs::test_get_user_team_slugs_success - AssertionError: assert [] == ['release-tea...'deploy-team']
Right contains 2 more items, first extra item: 'release-team'
Full diff:
+ []
- [
- 'release-team',
- 'deploy-team',
- ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 64 passed, 3 deselected in 0.78s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_Pow, occurrence: 19
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -849,7 +849,7 @@
return member_teams
except Exception as e:
- duration = time.time() - start_time
+ duration = time.time() ** start_time
github_api_request_duration_seconds.labels(operation="get_user_teams").observe(
duration
)....................................................................F
=================================== FAILURES ===================================
_______ TestGetUserTeamSlugs.test_get_user_team_slugs_general_exception ________
self = <stampbot.github_client.GitHubAppClient object at 0x7fd14df13110>
installation_id = 123456, org_name = 'acme', username = 'alice'
allowed_teams = ['release-team']
def get_user_team_slugs(
self,
installation_id: int,
org_name: str,
username: str,
allowed_teams: list[str],
) -> list[str]:
"""Get team slugs the user is a member of from the allowed teams list.
Args:
installation_id: GitHub App installation ID
org_name: Organization name
username: GitHub username to check
allowed_teams: List of team slugs to check (can be "org/team" or just "team")
Returns:
List of team slugs the user is a member of
"""
start_time = time.time()
with create_span(
"github.get_user_team_slugs",
{
"github.org": org_name,
"github.username": username,
"github.installation_id": installation_id,
"github.teams_to_check": len(allowed_teams),
},
) as span:
member_teams: list[str] = []
try:
client = self._get_installation_client(installation_id)
> org = client.get_organization(org_name)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/github_client.py:796:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1175: in __call__
return self._mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1179: in _mock_call
return self._execute_mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <Mock name='Github().get_organization' id='140536934365376'>
args = ('acme',), kwargs = {}, effect = Exception('Network error')
def _execute_mock_call(self, /, *args, **kwargs):
# separate from _increment_mock_call so that awaited functions are
# executed separately from their call, also AsyncMock overrides this method
effect = self.side_effect
if effect is not None:
if _is_exception(effect):
> raise effect
E Exception: Network error
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1234: Exception
During handling of the above exception, another exception occurred:
self = <tests.test_github_client.TestGetUserTeamSlugs object at 0x7fd1526845f0>
def test_get_user_team_slugs_general_exception(self):
"""Test team membership check with general exception."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_github = Mock()
mock_github.get_organization.side_effect = Exception("Network error")
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
> result = client.get_user_team_slugs(123456, "acme", "alice", ["release-team"])
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_github_client.py:1010:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.github_client.GitHubAppClient object at 0x7fd14df13110>
installation_id = 123456, org_name = 'acme', username = 'alice'
allowed_teams = ['release-team']
def get_user_team_slugs(
self,
installation_id: int,
org_name: str,
username: str,
allowed_teams: list[str],
) -> list[str]:
"""Get team slugs the user is a member of from the allowed teams list.
Args:
installation_id: GitHub App installation ID
org_name: Organization name
username: GitHub username to check
allowed_teams: List of team slugs to check (can be "org/team" or just "team")
Returns:
List of team slugs the user is a member of
"""
start_time = time.time()
with create_span(
"github.get_user_team_slugs",
{
"github.org": org_name,
"github.username": username,
"github.installation_id": installation_id,
"github.teams_to_check": len(allowed_teams),
},
) as span:
member_teams: list[str] = []
try:
client = self._get_installation_client(installation_id)
org = client.get_organization(org_name)
for team_ref in allowed_teams:
# Extract team slug (handle both "org/team" and "team" formats)
team_slug = team_ref.split("/")[-1] if "/" in team_ref else team_ref
try:
team = org.get_team_by_slug(team_slug)
# Check if user is a member
user = client.get_user(username)
if team.has_in_members(user): # type: ignore[arg-type]
member_teams.append(team_slug)
logger.debug(
"User %s is a member of team %s",
username,
team_slug,
extra={
"org": org_name,
"username": username,
"team": team_slug,
},
)
except GithubException as e:
# Team not found or no access - skip silently
logger.debug(
"Could not check team %s membership: %s",
team_slug,
e.data.get("message", str(e)) if hasattr(e, "data") else str(e),
extra={
"org": org_name,
"team": team_slug,
"username": username,
},
)
continue
duration = time.time() - start_time
github_api_request_duration_seconds.labels(operation="get_user_teams").observe(
duration
)
github_api_requests_total.labels(operation="get_user_teams", status="success").inc()
self._update_rate_limit_metrics(client, installation_id)
add_span_attributes(
span,
{
"github.member_teams": len(member_teams),
"github.teams_checked": len(allowed_teams),
},
)
set_span_ok(span)
return member_teams
except Exception as e:
> duration = time.time() ** start_time
^^^^^^^^^^^^^^^^^^^^^^^^^
E OverflowError: (34, 'Numerical result out of range')
stampbot/github_client.py:852: OverflowError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetUserTeamSlugs::test_get_user_team_slugs_general_exception - OverflowError: (34, 'Numerical result out of range')
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 68 passed, 3 deselected in 0.92s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_RShift, occurrence: 0
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -168,7 +168,7 @@
retry=retry,
)
- duration = time.time() - start_time
+ duration = time.time() >> start_time
github_api_request_duration_seconds.labels(operation="get_token").observe(duration)
github_api_requests_total.labels(operation="get_token", status="success").inc()
...............................................F
=================================== FAILURES ===================================
________ TestGetInstallationClient.test_get_installation_client_success ________
self = <tests.test_github_client.TestGetInstallationClient object at 0x7f6cdf2b4050>
def test_get_installation_client_success(self):
"""Test successful installation client creation."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
> result = client._get_installation_client(123456)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_github_client.py:211:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.github_client.GitHubAppClient object at 0x7f6cdaf98750>
installation_id = 123456
def _get_installation_client(self, installation_id: int) -> Github:
"""Get authenticated GitHub client for an installation.
Args:
installation_id: GitHub App installation ID.
Returns:
Authenticated Github client instance with timeout and retry configured.
Raises:
github.GithubException: If token exchange fails.
"""
start_time = time.time()
with create_span(
"github.get_installation_token",
{"github.installation_id": installation_id},
) as span:
try:
auth = self.integration.get_access_token(installation_id)
# Configure retry with exponential backoff
retry = Retry(
total=GITHUB_API_RETRY_TOTAL,
backoff_factor=GITHUB_API_RETRY_BACKOFF,
status_forcelist=[500, 502, 503, 504],
allowed_methods=["GET", "POST", "PUT", "DELETE", "PATCH"],
)
client = Github(
auth=Auth.Token(auth.token),
timeout=GITHUB_API_TIMEOUT,
retry=retry,
)
> duration = time.time() >> start_time
^^^^^^^^^^^^^^^^^^^^^^^^^
E TypeError: unsupported operand type(s) for >>: 'float' and 'float'
stampbot/github_client.py:171: TypeError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetInstallationClient::test_get_installation_client_success - TypeError: unsupported operand type(s) for >>: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 47 passed, 3 deselected in 0.76s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_RShift, occurrence: 1
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -176,7 +176,7 @@
return client
except Exception as e:
- duration = time.time() - start_time
+ duration = time.time() >> start_time
github_api_request_duration_seconds.labels(operation="get_token").observe(duration)
github_api_requests_total.labels(operation="get_token", status="failure").inc()
set_span_error(span, e)................................................F
=================================== FAILURES ===================================
________ TestGetInstallationClient.test_get_installation_client_failure ________
self = <stampbot.github_client.GitHubAppClient object at 0x7f7b2f4bd160>
installation_id = 123456
def _get_installation_client(self, installation_id: int) -> Github:
"""Get authenticated GitHub client for an installation.
Args:
installation_id: GitHub App installation ID.
Returns:
Authenticated Github client instance with timeout and retry configured.
Raises:
github.GithubException: If token exchange fails.
"""
start_time = time.time()
with create_span(
"github.get_installation_token",
{"github.installation_id": installation_id},
) as span:
try:
> auth = self.integration.get_access_token(installation_id)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/github_client.py:155:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1175: in __call__
return self._mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1179: in _mock_call
return self._execute_mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <Mock name='GithubIntegration().get_access_token' id='140167032275024'>
args = (123456,), kwargs = {}, effect = Exception('Token exchange failed')
def _execute_mock_call(self, /, *args, **kwargs):
# separate from _increment_mock_call so that awaited functions are
# executed separately from their call, also AsyncMock overrides this method
effect = self.side_effect
if effect is not None:
if _is_exception(effect):
> raise effect
E Exception: Token exchange failed
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1234: Exception
During handling of the above exception, another exception occurred:
self = <tests.test_github_client.TestGetInstallationClient object at 0x7f7b3266c190>
def test_get_installation_client_failure(self):
"""Test installation client creation failure."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_integration.get_access_token.side_effect = Exception("Token exchange failed")
mock_integration_cls.return_value = mock_integration
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
with pytest.raises(Exception, match="Token exchange failed"):
> client._get_installation_client(123456)
tests/test_github_client.py:240:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.github_client.GitHubAppClient object at 0x7f7b2f4bd160>
installation_id = 123456
def _get_installation_client(self, installation_id: int) -> Github:
"""Get authenticated GitHub client for an installation.
Args:
installation_id: GitHub App installation ID.
Returns:
Authenticated Github client instance with timeout and retry configured.
Raises:
github.GithubException: If token exchange fails.
"""
start_time = time.time()
with create_span(
"github.get_installation_token",
{"github.installation_id": installation_id},
) as span:
try:
auth = self.integration.get_access_token(installation_id)
# Configure retry with exponential backoff
retry = Retry(
total=GITHUB_API_RETRY_TOTAL,
backoff_factor=GITHUB_API_RETRY_BACKOFF,
status_forcelist=[500, 502, 503, 504],
allowed_methods=["GET", "POST", "PUT", "DELETE", "PATCH"],
)
client = Github(
auth=Auth.Token(auth.token),
timeout=GITHUB_API_TIMEOUT,
retry=retry,
)
duration = time.time() - start_time
github_api_request_duration_seconds.labels(operation="get_token").observe(duration)
github_api_requests_total.labels(operation="get_token", status="success").inc()
set_span_ok(span)
return client
except Exception as e:
> duration = time.time() >> start_time
^^^^^^^^^^^^^^^^^^^^^^^^^
E TypeError: unsupported operand type(s) for >>: 'float' and 'float'
stampbot/github_client.py:179: TypeError
During handling of the above exception, another exception occurred:
self = <tests.test_github_client.TestGetInstallationClient object at 0x7f7b3266c190>
def test_get_installation_client_failure(self):
"""Test installation client creation failure."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_integration.get_access_token.side_effect = Exception("Token exchange failed")
mock_integration_cls.return_value = mock_integration
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
> with pytest.raises(Exception, match="Token exchange failed"):
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E AssertionError: Regex pattern did not match.
E Expected regex: 'Token exchange failed'
E Actual message: "unsupported operand type(s) for >>: 'float' and 'float'"
tests/test_github_client.py:239: AssertionError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetInstallationClient::test_get_installation_client_failure - AssertionError: Regex pattern did not match.
Expected regex: 'Token exchange failed'
Actual message: "unsupported operand type(s) for >>: 'float' and 'float'"
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 48 passed, 3 deselected in 0.87s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_RShift, occurrence: 2
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -240,7 +240,7 @@
event="APPROVE",
)
- duration = time.time() - start_time
+ duration = time.time() >> start_time
github_api_request_duration_seconds.labels(operation="approve").observe(duration)
github_api_requests_total.labels(operation="approve", status="success").inc()
...................................................F
=================================== FAILURES ===================================
____________________ TestApprovePR.test_approve_pr_success _____________________
self = <tests.test_github_client.TestApprovePR object at 0x7f2fbb15c550>
def test_approve_pr_success(self):
"""Test successful PR approval."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_pr = Mock()
mock_repo = Mock()
mock_repo.get_pull.return_value = mock_pr
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
result = client.approve_pr(123456, "owner/repo", 42)
> assert result is True
E assert False is True
tests/test_github_client.py:313: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-05-16 19:25:48 [error ] Failed to approve PR #42 in owner/repo: unsupported operand type(s) for >>: 'float' and 'float' extra={'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': "unsupported operand type(s) for >>: 'float' and 'float'"}
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestApprovePR::test_approve_pr_success - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 51 passed, 3 deselected in 0.73s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_RShift, occurrence: 3
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -260,7 +260,7 @@
return True
except Exception as e:
- duration = time.time() - start_time
+ duration = time.time() >> start_time
github_api_request_duration_seconds.labels(operation="approve").observe(duration)
github_api_requests_total.labels(operation="approve", status="failure").inc()
....................................................F
=================================== FAILURES ===================================
____________________ TestApprovePR.test_approve_pr_failure _____________________
self = <stampbot.github_client.GitHubAppClient object at 0x7f039e06ed00>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42
comment = 'Auto-approved by Stampbot'
def approve_pr(
self,
installation_id: int,
repo_full_name: str,
pr_number: int,
comment: str = "Auto-approved by Stampbot",
) -> bool:
"""Approve a pull request.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name (owner/repo)
pr_number: Pull request number
comment: Review comment
Returns:
True if successful, False otherwise
"""
start_time = time.time()
with create_span(
"github.approve_pr",
{
"github.repo": repo_full_name,
"github.pr_number": pr_number,
"github.installation_id": installation_id,
},
) as span:
try:
client = self._get_installation_client(installation_id)
repo = client.get_repo(repo_full_name)
pr = repo.get_pull(pr_number)
# Create approval review
> pr.create_review(
body=comment,
event="APPROVE",
)
stampbot/github_client.py:238:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1175: in __call__
return self._mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1179: in _mock_call
return self._execute_mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <Mock name='Github().get_repo().get_pull().create_review' id='139653513296400'>
args = (), kwargs = {'body': 'Auto-approved by Stampbot', 'event': 'APPROVE'}
effect = Exception('API Error')
def _execute_mock_call(self, /, *args, **kwargs):
# separate from _increment_mock_call so that awaited functions are
# executed separately from their call, also AsyncMock overrides this method
effect = self.side_effect
if effect is not None:
if _is_exception(effect):
> raise effect
E Exception: API Error
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1234: Exception
During handling of the above exception, another exception occurred:
self = <tests.test_github_client.TestApprovePR object at 0x7f03a23c4690>
def test_approve_pr_failure(self):
"""Test PR approval failure."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_pr = Mock()
mock_pr.create_review.side_effect = Exception("API Error")
mock_repo = Mock()
mock_repo.get_pull.return_value = mock_pr
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
> result = client.approve_pr(123456, "owner/repo", 42)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_github_client.py:350:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.github_client.GitHubAppClient object at 0x7f039e06ed00>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42
comment = 'Auto-approved by Stampbot'
def approve_pr(
self,
installation_id: int,
repo_full_name: str,
pr_number: int,
comment: str = "Auto-approved by Stampbot",
) -> bool:
"""Approve a pull request.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name (owner/repo)
pr_number: Pull request number
comment: Review comment
Returns:
True if successful, False otherwise
"""
start_time = time.time()
with create_span(
"github.approve_pr",
{
"github.repo": repo_full_name,
"github.pr_number": pr_number,
"github.installation_id": installation_id,
},
) as span:
try:
client = self._get_installation_client(installation_id)
repo = client.get_repo(repo_full_name)
pr = repo.get_pull(pr_number)
# Create approval review
pr.create_review(
body=comment,
event="APPROVE",
)
duration = time.time() - start_time
github_api_request_duration_seconds.labels(operation="approve").observe(duration)
github_api_requests_total.labels(operation="approve", status="success").inc()
self._update_rate_limit_metrics(client, installation_id)
add_span_attributes(span, {"github.result": "approved"})
set_span_ok(span)
logger.info(
f"Approved PR #{pr_number} in {repo_full_name}",
extra={
"repo": repo_full_name,
"pr_number": pr_number,
"installation_id": installation_id,
},
)
return True
except Exception as e:
> duration = time.time() >> start_time
^^^^^^^^^^^^^^^^^^^^^^^^^
E TypeError: unsupported operand type(s) for >>: 'float' and 'float'
stampbot/github_client.py:263: TypeError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestApprovePR::test_approve_pr_failure - TypeError: unsupported operand type(s) for >>: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 52 passed, 3 deselected in 0.87s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_RShift, occurrence: 4
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -317,7 +317,7 @@
review = pr.get_review(review_id)
review.dismiss(message)
- duration = time.time() - start_time
+ duration = time.time() >> start_time
github_api_request_duration_seconds.labels(operation="dismiss").observe(duration)
github_api_requests_total.labels(operation="dismiss", status="success").inc()
.....................................................F
=================================== FAILURES ===================================
______________ TestDismissApproval.test_dismiss_approval_success _______________
self = <tests.test_github_client.TestDismissApproval object at 0x7f6852b807d0>
def test_dismiss_approval_success(self):
"""Test successful approval dismissal."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_review = Mock()
mock_pr = Mock()
mock_pr.get_review.return_value = mock_review
mock_repo = Mock()
mock_repo.get_pull.return_value = mock_pr
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
result = client.dismiss_approval(123456, "owner/repo", 42, 999)
> assert result is True
E assert False is True
tests/test_github_client.py:396: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-05-16 19:52:11 [error ] Failed to dismiss approval for PR #42 in owner/repo: unsupported operand type(s) for >>: 'float' and 'float' extra={'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': "unsupported operand type(s) for >>: 'float' and 'float'"}
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestDismissApproval::test_dismiss_approval_success - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 53 passed, 3 deselected in 0.74s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_RShift, occurrence: 5
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -338,7 +338,7 @@
return True
except Exception as e:
- duration = time.time() - start_time
+ duration = time.time() >> start_time
github_api_request_duration_seconds.labels(operation="dismiss").observe(duration)
github_api_requests_total.labels(operation="dismiss", status="failure").inc()
......................................................F
=================================== FAILURES ===================================
______________ TestDismissApproval.test_dismiss_approval_failure _______________
self = <stampbot.github_client.GitHubAppClient object at 0x7fe2c3d228f0>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42
review_id = 999, message = 'Approval dismissed by Stampbot'
def dismiss_approval(
self,
installation_id: int,
repo_full_name: str,
pr_number: int,
review_id: int,
message: str = "Approval dismissed by Stampbot",
) -> bool:
"""Dismiss a pull request approval.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name (owner/repo)
pr_number: Pull request number
review_id: Review ID to dismiss
message: Dismissal message
Returns:
True if successful, False otherwise
"""
start_time = time.time()
with create_span(
"github.dismiss_approval",
{
"github.repo": repo_full_name,
"github.pr_number": pr_number,
"github.review_id": review_id,
"github.installation_id": installation_id,
},
) as span:
try:
client = self._get_installation_client(installation_id)
repo = client.get_repo(repo_full_name)
pr = repo.get_pull(pr_number)
# Get the review and dismiss it
review = pr.get_review(review_id)
> review.dismiss(message)
stampbot/github_client.py:318:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1175: in __call__
return self._mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1179: in _mock_call
return self._execute_mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <Mock name='Github().get_repo().get_pull().get_review().dismiss' id='140611923522768'>
args = ('Approval dismissed by Stampbot',), kwargs = {}
effect = Exception('API Error')
def _execute_mock_call(self, /, *args, **kwargs):
# separate from _increment_mock_call so that awaited functions are
# executed separately from their call, also AsyncMock overrides this method
effect = self.side_effect
if effect is not None:
if _is_exception(effect):
> raise effect
E Exception: API Error
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1234: Exception
During handling of the above exception, another exception occurred:
self = <tests.test_github_client.TestDismissApproval object at 0x7fe2c80a4910>
def test_dismiss_approval_failure(self):
"""Test approval dismissal failure."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_review = Mock()
mock_review.dismiss.side_effect = Exception("API Error")
mock_pr = Mock()
mock_pr.get_review.return_value = mock_review
mock_repo = Mock()
mock_repo.get_pull.return_value = mock_pr
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
> result = client.dismiss_approval(123456, "owner/repo", 42, 999)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_github_client.py:435:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.github_client.GitHubAppClient object at 0x7fe2c3d228f0>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42
review_id = 999, message = 'Approval dismissed by Stampbot'
def dismiss_approval(
self,
installation_id: int,
repo_full_name: str,
pr_number: int,
review_id: int,
message: str = "Approval dismissed by Stampbot",
) -> bool:
"""Dismiss a pull request approval.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name (owner/repo)
pr_number: Pull request number
review_id: Review ID to dismiss
message: Dismissal message
Returns:
True if successful, False otherwise
"""
start_time = time.time()
with create_span(
"github.dismiss_approval",
{
"github.repo": repo_full_name,
"github.pr_number": pr_number,
"github.review_id": review_id,
"github.installation_id": installation_id,
},
) as span:
try:
client = self._get_installation_client(installation_id)
repo = client.get_repo(repo_full_name)
pr = repo.get_pull(pr_number)
# Get the review and dismiss it
review = pr.get_review(review_id)
review.dismiss(message)
duration = time.time() - start_time
github_api_request_duration_seconds.labels(operation="dismiss").observe(duration)
github_api_requests_total.labels(operation="dismiss", status="success").inc()
self._update_rate_limit_metrics(client, installation_id)
add_span_attributes(span, {"github.result": "dismissed"})
set_span_ok(span)
logger.info(
f"Dismissed approval for PR #{pr_number} in {repo_full_name}",
extra={
"repo": repo_full_name,
"pr_number": pr_number,
"installation_id": installation_id,
"review_id": review_id,
},
)
return True
except Exception as e:
> duration = time.time() >> start_time
^^^^^^^^^^^^^^^^^^^^^^^^^
E TypeError: unsupported operand type(s) for >>: 'float' and 'float'
stampbot/github_client.py:341: TypeError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestDismissApproval::test_dismiss_approval_failure - TypeError: unsupported operand type(s) for >>: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 54 passed, 3 deselected in 0.87s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_RShift, occurrence: 6
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -393,7 +393,7 @@
content = repo.get_contents(file_path, ref=ref) # type: ignore[arg-type]
if isinstance(content, list):
- duration = time.time() - start_time
+ duration = time.time() >> start_time
github_api_request_duration_seconds.labels(operation="get_file").observe(
duration
)........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.62s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_RShift, occurrence: 7
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -402,7 +402,7 @@
set_span_ok(span)
return None
- duration = time.time() - start_time
+ duration = time.time() >> start_time
github_api_request_duration_seconds.labels(operation="get_file").observe(duration)
github_api_requests_total.labels(operation="get_file", status="success").inc()
.......................................................F
=================================== FAILURES ===================================
__________________ TestGetRepoFile.test_get_repo_file_success __________________
self = <tests.test_github_client.TestGetRepoFile object at 0x7f022c284a50>
def test_get_repo_file_success(self):
"""Test successful file retrieval."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_content = Mock()
mock_content.decoded_content = b"file content"
mock_repo = Mock()
mock_repo.get_contents.return_value = mock_content
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
result = client.get_repo_file(123456, "owner/repo", "stampbot.toml")
> assert result == "file content"
E AssertionError: assert None == 'file content'
tests/test_github_client.py:480: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-05-16 19:43:46 [debug ] Could not fetch stampbot.toml from owner/repo: unsupported operand type(s) for >>: 'float' and 'float' extra={'repo': 'owner/repo', 'file_path': 'stampbot.toml', 'installation_id': 123456}
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetRepoFile::test_get_repo_file_success - AssertionError: assert None == 'file content'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 55 passed, 3 deselected in 0.76s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_RShift, occurrence: 8
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -414,7 +414,7 @@
return content.decoded_content.decode("utf-8")
except Exception as e:
- duration = time.time() - start_time
+ duration = time.time() >> start_time
github_api_request_duration_seconds.labels(operation="get_file").observe(duration)
github_api_requests_total.labels(operation="get_file", status="not_found").inc()
.........................................................F
=================================== FAILURES ===================================
___________ TestGetRepoFile.test_get_repo_file_returns_none_on_error ___________
self = <stampbot.github_client.GitHubAppClient object at 0x7f8b8a158d50>
installation_id = 123456, repo_full_name = 'owner/repo'
file_path = 'nonexistent.toml', ref = None
def get_repo_file(
self,
installation_id: int,
repo_full_name: str,
file_path: str,
ref: str | None = None,
) -> str | None:
"""Get file content from repository.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name (owner/repo)
file_path: Path to file in repository
ref: Git reference (branch, tag, commit). Defaults to default branch
Returns:
File content as string, or None if not found
"""
start_time = time.time()
with create_span(
"github.get_file",
{
"github.repo": repo_full_name,
"github.file_path": file_path,
"github.ref": ref or "default",
"github.installation_id": installation_id,
},
) as span:
try:
client = self._get_installation_client(installation_id)
repo = client.get_repo(repo_full_name)
> content = repo.get_contents(file_path, ref=ref) # type: ignore[arg-type]
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/github_client.py:394:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1175: in __call__
return self._mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1179: in _mock_call
return self._execute_mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <Mock name='Github().get_repo().get_contents' id='140237294159584'>
args = ('nonexistent.toml',), kwargs = {'ref': None}
effect = Exception('File not found')
def _execute_mock_call(self, /, *args, **kwargs):
# separate from _increment_mock_call so that awaited functions are
# executed separately from their call, also AsyncMock overrides this method
effect = self.side_effect
if effect is not None:
if _is_exception(effect):
> raise effect
E Exception: File not found
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1234: Exception
During handling of the above exception, another exception occurred:
self = <tests.test_github_client.TestGetRepoFile object at 0x7f8b8e485940>
def test_get_repo_file_returns_none_on_error(self):
"""Test that get_repo_file returns None when file not found."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_repo = Mock()
mock_repo.get_contents.side_effect = Exception("File not found")
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
> result = client.get_repo_file(123456, "owner/repo", "nonexistent.toml")
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_github_client.py:551:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.github_client.GitHubAppClient object at 0x7f8b8a158d50>
installation_id = 123456, repo_full_name = 'owner/repo'
file_path = 'nonexistent.toml', ref = None
def get_repo_file(
self,
installation_id: int,
repo_full_name: str,
file_path: str,
ref: str | None = None,
) -> str | None:
"""Get file content from repository.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name (owner/repo)
file_path: Path to file in repository
ref: Git reference (branch, tag, commit). Defaults to default branch
Returns:
File content as string, or None if not found
"""
start_time = time.time()
with create_span(
"github.get_file",
{
"github.repo": repo_full_name,
"github.file_path": file_path,
"github.ref": ref or "default",
"github.installation_id": installation_id,
},
) as span:
try:
client = self._get_installation_client(installation_id)
repo = client.get_repo(repo_full_name)
content = repo.get_contents(file_path, ref=ref) # type: ignore[arg-type]
if isinstance(content, list):
duration = time.time() - start_time
github_api_request_duration_seconds.labels(operation="get_file").observe(
duration
)
github_api_requests_total.labels(operation="get_file", status="not_found").inc()
add_span_attributes(span, {"github.result": "not_found"})
set_span_ok(span)
return None
duration = time.time() - start_time
github_api_request_duration_seconds.labels(operation="get_file").observe(duration)
github_api_requests_total.labels(operation="get_file", status="success").inc()
self._update_rate_limit_metrics(client, installation_id)
add_span_attributes(span, {"github.result": "found"})
set_span_ok(span)
return content.decoded_content.decode("utf-8")
except Exception as e:
> duration = time.time() >> start_time
^^^^^^^^^^^^^^^^^^^^^^^^^
E TypeError: unsupported operand type(s) for >>: 'float' and 'float'
stampbot/github_client.py:417: TypeError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetRepoFile::test_get_repo_file_returns_none_on_error - TypeError: unsupported operand type(s) for >>: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 57 passed, 3 deselected in 0.87s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_RShift, occurrence: 9
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -473,7 +473,7 @@
if review.user.login == bot_user and review.state == "APPROVED":
bot_review_ids.append(review.id)
- duration = time.time() - start_time
+ duration = time.time() >> start_time
github_api_request_duration_seconds.labels(operation="find_reviews").observe(
duration
)..........................................................F
=================================== FAILURES ===================================
_______________ TestFindBotReviews.test_find_bot_reviews_success _______________
self = <tests.test_github_client.TestFindBotReviews object at 0x7f03544c0cd0>
def test_find_bot_reviews_success(self):
"""Test successful bot review finding."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration.get_app.return_value = Mock(slug="stampbot")
mock_integration_cls.return_value = mock_integration
# Create mock reviews
bot_review = Mock()
bot_review.user.login = "stampbot[bot]"
bot_review.state = "APPROVED"
bot_review.id = 123
other_review = Mock()
other_review.user.login = "other-user"
other_review.state = "APPROVED"
other_review.id = 456
mock_pr = Mock()
mock_pr.get_reviews.return_value = [bot_review, other_review]
mock_repo = Mock()
mock_repo.get_pull.return_value = mock_pr
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
result = client.find_bot_reviews(123456, "owner/repo", 42)
> assert result == [123]
E assert [] == [123]
E
E Right contains one more item: 123
E
E Full diff:
E + []
E - [
E - 123,
E - ]
tests/test_github_client.py:608: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-05-16 19:51:34 [error ] Failed to find bot reviews for PR #42 in owner/repo: unsupported operand type(s) for >>: 'float' and 'float' extra={'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': "unsupported operand type(s) for >>: 'float' and 'float'"}
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestFindBotReviews::test_find_bot_reviews_success - assert [] == [123]
Right contains one more item: 123
Full diff:
+ []
- [
- 123,
- ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 58 passed, 3 deselected in 0.75s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_RShift, occurrence: 10
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -489,7 +489,7 @@
return bot_review_ids
except Exception as e:
- duration = time.time() - start_time
+ duration = time.time() >> start_time
github_api_request_duration_seconds.labels(operation="find_reviews").observe(
duration
)...........................................................F
=================================== FAILURES ===================================
_______ TestFindBotReviews.test_find_bot_reviews_returns_empty_on_error ________
self = <stampbot.github_client.GitHubAppClient object at 0x7fc1af140f30>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42
def find_bot_reviews(
self,
installation_id: int,
repo_full_name: str,
pr_number: int,
) -> list[int]:
"""Find all reviews created by this bot on a PR.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name (owner/repo)
pr_number: Pull request number
Returns:
List of review IDs created by the bot
"""
start_time = time.time()
with create_span(
"github.find_bot_reviews",
{
"github.repo": repo_full_name,
"github.pr_number": pr_number,
"github.installation_id": installation_id,
},
) as span:
try:
client = self._get_installation_client(installation_id)
> repo = client.get_repo(repo_full_name)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/github_client.py:462:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1175: in __call__
return self._mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1179: in _mock_call
return self._execute_mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <Mock name='Github().get_repo' id='140469843215088'>
args = ('owner/repo',), kwargs = {}, effect = Exception('API Error')
def _execute_mock_call(self, /, *args, **kwargs):
# separate from _increment_mock_call so that awaited functions are
# executed separately from their call, also AsyncMock overrides this method
effect = self.side_effect
if effect is not None:
if _is_exception(effect):
> raise effect
E Exception: API Error
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1234: Exception
During handling of the above exception, another exception occurred:
self = <tests.test_github_client.TestFindBotReviews object at 0x7fc1b3270e10>
def test_find_bot_reviews_returns_empty_on_error(self):
"""Test that find_bot_reviews returns empty list on error."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_github = Mock()
mock_github.get_repo.side_effect = Exception("API Error")
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
> result = client.find_bot_reviews(123456, "owner/repo", 42)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_github_client.py:640:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.github_client.GitHubAppClient object at 0x7fc1af140f30>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42
def find_bot_reviews(
self,
installation_id: int,
repo_full_name: str,
pr_number: int,
) -> list[int]:
"""Find all reviews created by this bot on a PR.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name (owner/repo)
pr_number: Pull request number
Returns:
List of review IDs created by the bot
"""
start_time = time.time()
with create_span(
"github.find_bot_reviews",
{
"github.repo": repo_full_name,
"github.pr_number": pr_number,
"github.installation_id": installation_id,
},
) as span:
try:
client = self._get_installation_client(installation_id)
repo = client.get_repo(repo_full_name)
pr = repo.get_pull(pr_number)
# Get bot user via JWT-authenticated integration (installation tokens
# cannot call GET /user, which is restricted by GitHub Apps integration)
app_info = self.integration.get_app()
bot_user = f"{app_info.slug}[bot]"
# Find all reviews by bot that are approvals
bot_review_ids = []
for review in pr.get_reviews():
if review.user.login == bot_user and review.state == "APPROVED":
bot_review_ids.append(review.id)
duration = time.time() - start_time
github_api_request_duration_seconds.labels(operation="find_reviews").observe(
duration
)
github_api_requests_total.labels(operation="find_reviews", status="success").inc()
self._update_rate_limit_metrics(client, installation_id)
add_span_attributes(
span, {"github.reviews_found": len(bot_review_ids), "github.bot_user": bot_user}
)
set_span_ok(span)
return bot_review_ids
except Exception as e:
> duration = time.time() >> start_time
^^^^^^^^^^^^^^^^^^^^^^^^^
E TypeError: unsupported operand type(s) for >>: 'float' and 'float'
stampbot/github_client.py:492: TypeError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestFindBotReviews::test_find_bot_reviews_returns_empty_on_error - TypeError: unsupported operand type(s) for >>: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 59 passed, 3 deselected in 0.88s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_RShift, occurrence: 11
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -546,7 +546,7 @@
pr.create_review(body=message, event="COMMENT")
- duration = time.time() - start_time
+ duration = time.time() >> start_time
github_api_request_duration_seconds.labels(operation="comment").observe(duration)
github_api_requests_total.labels(operation="comment", status="success").inc()
........................................................................ [ 34%]
.F
=================================== FAILURES ===================================
_______ TestCreatePrReviewComment.test_create_pr_review_comment_success ________
self = <tests.test_github_client.TestCreatePrReviewComment object at 0x7f1f86c916d0>
def test_create_pr_review_comment_success(self):
"""Test successful review comment creation."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_pr = Mock()
mock_repo = Mock()
mock_repo.get_pull.return_value = mock_pr
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=1, limit=2))
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
result = client.create_pr_review_comment(
123456,
"owner/repo",
42,
"Config error",
)
> assert result is True
E assert False is True
tests/test_github_client.py:1213: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-05-16 19:42:57 [warning ] Failed to post config error review for PR #42 in owner/repo: unsupported operand type(s) for >>: 'float' and 'float' extra={'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': "unsupported operand type(s) for >>: 'float' and 'float'"}
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestCreatePrReviewComment::test_create_pr_review_comment_success - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 73 passed, 3 deselected in 0.79s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_RShift, occurrence: 12
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -568,7 +568,7 @@
return True
except Exception as e:
- duration = time.time() - start_time
+ duration = time.time() >> start_time
github_api_request_duration_seconds.labels(operation="comment").observe(duration)
github_api_requests_total.labels(operation="comment", status="failure").inc()
........................................................................ [ 34%]
..F
=================================== FAILURES ===================================
_______ TestCreatePrReviewComment.test_create_pr_review_comment_failure ________
self = <stampbot.github_client.GitHubAppClient object at 0x7f91697ae570>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42
message = 'Config error'
def create_pr_review_comment(
self,
installation_id: int,
repo_full_name: str,
pr_number: int,
message: str,
) -> bool:
"""Create a comment review on a pull request.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name (owner/repo)
pr_number: Pull request number
message: Review comment body
Returns:
True if successful, False otherwise
"""
start_time = time.time()
with create_span(
"github.create_review_comment",
{
"github.repo": repo_full_name,
"github.pr_number": pr_number,
"github.installation_id": installation_id,
},
) as span:
try:
client = self._get_installation_client(installation_id)
repo = client.get_repo(repo_full_name)
> pr = repo.get_pull(pr_number)
^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/github_client.py:545:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1175: in __call__
return self._mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1179: in _mock_call
return self._execute_mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <Mock name='Github().get_repo().get_pull' id='140262516575264'>
args = (42,), kwargs = {}, effect = Exception('API Error')
def _execute_mock_call(self, /, *args, **kwargs):
# separate from _increment_mock_call so that awaited functions are
# executed separately from their call, also AsyncMock overrides this method
effect = self.side_effect
if effect is not None:
if _is_exception(effect):
> raise effect
E Exception: API Error
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1234: Exception
During handling of the above exception, another exception occurred:
self = <tests.test_github_client.TestCreatePrReviewComment object at 0x7f916de89810>
def test_create_pr_review_comment_failure(self):
"""Test review comment creation handles errors."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_repo = Mock()
mock_repo.get_pull.side_effect = Exception("API Error")
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
> result = client.create_pr_review_comment(
123456,
"owner/repo",
42,
"Config error",
)
tests/test_github_client.py:1248:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.github_client.GitHubAppClient object at 0x7f91697ae570>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42
message = 'Config error'
def create_pr_review_comment(
self,
installation_id: int,
repo_full_name: str,
pr_number: int,
message: str,
) -> bool:
"""Create a comment review on a pull request.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name (owner/repo)
pr_number: Pull request number
message: Review comment body
Returns:
True if successful, False otherwise
"""
start_time = time.time()
with create_span(
"github.create_review_comment",
{
"github.repo": repo_full_name,
"github.pr_number": pr_number,
"github.installation_id": installation_id,
},
) as span:
try:
client = self._get_installation_client(installation_id)
repo = client.get_repo(repo_full_name)
pr = repo.get_pull(pr_number)
pr.create_review(body=message, event="COMMENT")
duration = time.time() - start_time
github_api_request_duration_seconds.labels(operation="comment").observe(duration)
github_api_requests_total.labels(operation="comment", status="success").inc()
self._update_rate_limit_metrics(client, installation_id)
add_span_attributes(span, {"github.result": "commented"})
set_span_ok(span)
logger.info(
"Posted config error review on PR #%d in %s",
pr_number,
repo_full_name,
extra={
"repo": repo_full_name,
"pr_number": pr_number,
"installation_id": installation_id,
},
)
return True
except Exception as e:
> duration = time.time() >> start_time
^^^^^^^^^^^^^^^^^^^^^^^^^
E TypeError: unsupported operand type(s) for >>: 'float' and 'float'
stampbot/github_client.py:571: TypeError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestCreatePrReviewComment::test_create_pr_review_comment_failure - TypeError: unsupported operand type(s) for >>: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 74 passed, 3 deselected in 0.90s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_RShift, occurrence: 13
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -619,7 +619,7 @@
repo = client.get_repo(repo_full_name)
repo.get_label(label_name)
- duration = time.time() - start_time
+ duration = time.time() >> start_time
github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
github_api_requests_total.labels(operation="get_label", status="success").inc()
.....................................................................F
=================================== FAILURES ===================================
__________________ TestRepoHasLabel.test_repo_has_label_true ___________________
self = <tests.test_github_client.TestRepoHasLabel object at 0x7f56f237d450>
def test_repo_has_label_true(self):
"""Test repo_has_label returns True when label exists."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_repo = Mock()
mock_repo.get_label.return_value = Mock()
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=1, limit=2))
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
result = client.repo_has_label(123456, "owner/repo", "autoapprove")
> assert result is True
E assert None is True
tests/test_github_client.py:1054: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-05-16 20:07:01 [debug ] Could not verify label autoapprove in owner/repo: unsupported operand type(s) for >>: 'float' and 'float' extra={'repo': 'owner/repo', 'label': 'autoapprove', 'installation_id': 123456, 'error': "unsupported operand type(s) for >>: 'float' and 'float'"}
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestRepoHasLabel::test_repo_has_label_true - assert None is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 69 passed, 3 deselected in 0.82s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_RShift, occurrence: 14
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -630,7 +630,7 @@
return True
except GithubException as e:
- duration = time.time() - start_time
+ duration = time.time() >> start_time
github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
github_api_requests_total.labels(operation="get_label", status="failure").inc()
......................................................................F
=================================== FAILURES ===================================
________________ TestRepoHasLabel.test_repo_has_label_not_found ________________
self = <stampbot.github_client.GitHubAppClient object at 0x7f8a53e31bb0>
installation_id = 123456, repo_full_name = 'owner/repo', label_name = 'missing'
def repo_has_label(
self,
installation_id: int,
repo_full_name: str,
label_name: str,
) -> bool | None:
"""Check whether a repository has a label.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name (owner/repo)
label_name: Label name to look up
Returns:
True if label exists, False if not found, None if unknown due to error
"""
start_time = time.time()
with create_span(
"github.get_label",
{
"github.repo": repo_full_name,
"github.label": label_name,
"github.installation_id": installation_id,
},
) as span:
try:
client = self._get_installation_client(installation_id)
repo = client.get_repo(repo_full_name)
> repo.get_label(label_name)
stampbot/github_client.py:620:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1175: in __call__
return self._mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1179: in _mock_call
return self._execute_mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <Mock name='Github().get_repo().get_label' id='140232089430272'>
args = ('missing',), kwargs = {}
effect = GithubException(404 {"message": "Not Found"})
def _execute_mock_call(self, /, *args, **kwargs):
# separate from _increment_mock_call so that awaited functions are
# executed separately from their call, also AsyncMock overrides this method
effect = self.side_effect
if effect is not None:
if _is_exception(effect):
> raise effect
E github.GithubException.GithubException: 404 {"message": "Not Found"}
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1234: GithubException
During handling of the above exception, another exception occurred:
self = <tests.test_github_client.TestRepoHasLabel object at 0x7f8a5c1e9590>
def test_repo_has_label_not_found(self):
"""Test repo_has_label returns False when label is missing."""
from github.GithubException import GithubException
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_repo = Mock()
mock_repo.get_label.side_effect = GithubException(404, {"message": "Not Found"}, None)
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
> result = client.repo_has_label(123456, "owner/repo", "missing")
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_github_client.py:1090:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.github_client.GitHubAppClient object at 0x7f8a53e31bb0>
installation_id = 123456, repo_full_name = 'owner/repo', label_name = 'missing'
def repo_has_label(
self,
installation_id: int,
repo_full_name: str,
label_name: str,
) -> bool | None:
"""Check whether a repository has a label.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name (owner/repo)
label_name: Label name to look up
Returns:
True if label exists, False if not found, None if unknown due to error
"""
start_time = time.time()
with create_span(
"github.get_label",
{
"github.repo": repo_full_name,
"github.label": label_name,
"github.installation_id": installation_id,
},
) as span:
try:
client = self._get_installation_client(installation_id)
repo = client.get_repo(repo_full_name)
repo.get_label(label_name)
duration = time.time() - start_time
github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
github_api_requests_total.labels(operation="get_label", status="success").inc()
self._update_rate_limit_metrics(client, installation_id)
add_span_attributes(span, {"github.label_found": True})
set_span_ok(span)
return True
except GithubException as e:
> duration = time.time() >> start_time
^^^^^^^^^^^^^^^^^^^^^^^^^
E TypeError: unsupported operand type(s) for >>: 'float' and 'float'
stampbot/github_client.py:633: TypeError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestRepoHasLabel::test_repo_has_label_not_found - TypeError: unsupported operand type(s) for >>: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 70 passed, 3 deselected in 0.93s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_RShift, occurrence: 15
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -655,7 +655,7 @@
return None
except Exception as e:
- duration = time.time() - start_time
+ duration = time.time() >> start_time
github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
github_api_requests_total.labels(operation="get_label", status="failure").inc()
........................................................................ [ 34%]
F
=================================== FAILURES ===================================
__________________ TestRepoHasLabel.test_repo_has_label_error __________________
self = <stampbot.github_client.GitHubAppClient object at 0x7fc2d9b97950>
installation_id = 123456, repo_full_name = 'owner/repo'
label_name = 'autoapprove'
def repo_has_label(
self,
installation_id: int,
repo_full_name: str,
label_name: str,
) -> bool | None:
"""Check whether a repository has a label.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name (owner/repo)
label_name: Label name to look up
Returns:
True if label exists, False if not found, None if unknown due to error
"""
start_time = time.time()
with create_span(
"github.get_label",
{
"github.repo": repo_full_name,
"github.label": label_name,
"github.installation_id": installation_id,
},
) as span:
try:
client = self._get_installation_client(installation_id)
repo = client.get_repo(repo_full_name)
> repo.get_label(label_name)
stampbot/github_client.py:620:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1175: in __call__
return self._mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1179: in _mock_call
return self._execute_mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <Mock name='Github().get_repo().get_label' id='140474853086880'>
args = ('autoapprove',), kwargs = {}, effect = Exception('API Error')
def _execute_mock_call(self, /, *args, **kwargs):
# separate from _increment_mock_call so that awaited functions are
# executed separately from their call, also AsyncMock overrides this method
effect = self.side_effect
if effect is not None:
if _is_exception(effect):
> raise effect
E Exception: API Error
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1234: Exception
During handling of the above exception, another exception occurred:
self = <tests.test_github_client.TestRepoHasLabel object at 0x7fc2dde9e060>
def test_repo_has_label_error(self):
"""Test repo_has_label returns None on error."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_repo = Mock()
mock_repo.get_label.side_effect = Exception("API Error")
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
> result = client.repo_has_label(123456, "owner/repo", "autoapprove")
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_github_client.py:1164:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.github_client.GitHubAppClient object at 0x7fc2d9b97950>
installation_id = 123456, repo_full_name = 'owner/repo'
label_name = 'autoapprove'
def repo_has_label(
self,
installation_id: int,
repo_full_name: str,
label_name: str,
) -> bool | None:
"""Check whether a repository has a label.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name (owner/repo)
label_name: Label name to look up
Returns:
True if label exists, False if not found, None if unknown due to error
"""
start_time = time.time()
with create_span(
"github.get_label",
{
"github.repo": repo_full_name,
"github.label": label_name,
"github.installation_id": installation_id,
},
) as span:
try:
client = self._get_installation_client(installation_id)
repo = client.get_repo(repo_full_name)
repo.get_label(label_name)
duration = time.time() - start_time
github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
github_api_requests_total.labels(operation="get_label", status="success").inc()
self._update_rate_limit_metrics(client, installation_id)
add_span_attributes(span, {"github.label_found": True})
set_span_ok(span)
return True
except GithubException as e:
duration = time.time() - start_time
github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
github_api_requests_total.labels(operation="get_label", status="failure").inc()
if e.status == 404:
add_span_attributes(span, {"github.label_found": False})
set_span_ok(span)
return False
set_span_error(span, e)
logger.debug(
"Could not verify label %s in %s: %s",
label_name,
repo_full_name,
_sanitize_error(e),
extra={
"repo": repo_full_name,
"label": label_name,
"installation_id": installation_id,
"error": _sanitize_error(e),
},
)
return None
except Exception as e:
> duration = time.time() >> start_time
^^^^^^^^^^^^^^^^^^^^^^^^^
E TypeError: unsupported operand type(s) for >>: 'float' and 'float'
stampbot/github_client.py:658: TypeError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestRepoHasLabel::test_repo_has_label_error - TypeError: unsupported operand type(s) for >>: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 72 passed, 3 deselected in 0.91s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_RShift, occurrence: 16
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -718,7 +718,7 @@
has_permission = permission_index >= required_index
- duration = time.time() - start_time
+ duration = time.time() >> start_time
github_api_request_duration_seconds.labels(operation="get_permission").observe(
duration
)............................................................F
=================================== FAILURES ===================================
_____________ TestUserHasPermission.test_user_has_permission_true ______________
self = <tests.test_github_client.TestUserHasPermission object at 0x7f7fc38ecf50>
def test_user_has_permission_true(self):
"""Test required permission satisfied returns True."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_repo = Mock()
mock_repo.get_collaborator_permission.return_value = "write"
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=1, limit=2))
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
result = client.user_has_permission(123456, "owner/repo", "alice", "write")
> assert result is True
E assert False is True
tests/test_github_client.py:683: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-05-16 19:46:30 [warning ] Failed to check collaborator permission for alice in owner/repo: unsupported operand type(s) for >>: 'float' and 'float' extra={'repo': 'owner/repo', 'username': 'alice', 'installation_id': 123456, 'error': "unsupported operand type(s) for >>: 'float' and 'float'"}
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestUserHasPermission::test_user_has_permission_true - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 60 passed, 3 deselected in 0.75s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_RShift, occurrence: 17
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -738,7 +738,7 @@
return has_permission
except Exception as e:
- duration = time.time() - start_time
+ duration = time.time() >> start_time
github_api_request_duration_seconds.labels(operation="get_permission").observe(
duration
)..............................................................F
=================================== FAILURES ===================================
_____________ TestUserHasPermission.test_user_has_permission_error _____________
self = <stampbot.github_client.GitHubAppClient object at 0x7f7472234b30>
installation_id = 123456, repo_full_name = 'owner/repo', username = 'carol'
required_permission = 'maintain'
def user_has_permission(
self,
installation_id: int,
repo_full_name: str,
username: str,
required_permission: str,
) -> bool:
"""Check whether a user has required access to a repository.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name (owner/repo)
username: GitHub username to check
required_permission: Minimum required repository permission
Returns:
True if user meets or exceeds required permission, False otherwise
"""
start_time = time.time()
with create_span(
"github.get_collaborator_permission",
{
"github.repo": repo_full_name,
"github.username": username,
"github.required_permission": required_permission,
"github.installation_id": installation_id,
},
) as span:
try:
client = self._get_installation_client(installation_id)
repo = client.get_repo(repo_full_name)
> permission = repo.get_collaborator_permission(username)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/github_client.py:710:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1175: in __call__
return self._mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1179: in _mock_call
return self._execute_mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <Mock name='Github().get_repo().get_collaborator_permission' id='140138107800608'>
args = ('carol',), kwargs = {}, effect = Exception('API Error')
def _execute_mock_call(self, /, *args, **kwargs):
# separate from _increment_mock_call so that awaited functions are
# executed separately from their call, also AsyncMock overrides this method
effect = self.side_effect
if effect is not None:
if _is_exception(effect):
> raise effect
E Exception: API Error
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1234: Exception
During handling of the above exception, another exception occurred:
self = <tests.test_github_client.TestUserHasPermission object at 0x7f7476705a70>
def test_user_has_permission_error(self):
"""Test permission check returns False on error."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_repo = Mock()
mock_repo.get_collaborator_permission.side_effect = Exception("API Error")
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
> result = client.user_has_permission(123456, "owner/repo", "carol", "maintain")
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_github_client.py:754:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.github_client.GitHubAppClient object at 0x7f7472234b30>
installation_id = 123456, repo_full_name = 'owner/repo', username = 'carol'
required_permission = 'maintain'
def user_has_permission(
self,
installation_id: int,
repo_full_name: str,
username: str,
required_permission: str,
) -> bool:
"""Check whether a user has required access to a repository.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name (owner/repo)
username: GitHub username to check
required_permission: Minimum required repository permission
Returns:
True if user meets or exceeds required permission, False otherwise
"""
start_time = time.time()
with create_span(
"github.get_collaborator_permission",
{
"github.repo": repo_full_name,
"github.username": username,
"github.required_permission": required_permission,
"github.installation_id": installation_id,
},
) as span:
try:
client = self._get_installation_client(installation_id)
repo = client.get_repo(repo_full_name)
permission = repo.get_collaborator_permission(username)
permission_order = ["none", "read", "triage", "write", "maintain", "admin"]
try:
permission_index = permission_order.index(permission)
required_index = permission_order.index(required_permission)
except ValueError:
permission_index = -1
required_index = 99
has_permission = permission_index >= required_index
duration = time.time() - start_time
github_api_request_duration_seconds.labels(operation="get_permission").observe(
duration
)
github_api_requests_total.labels(operation="get_permission", status="success").inc()
self._update_rate_limit_metrics(client, installation_id)
add_span_attributes(
span,
{
"github.permission": permission,
"github.has_permission": has_permission,
},
)
set_span_ok(span)
return has_permission
except Exception as e:
> duration = time.time() >> start_time
^^^^^^^^^^^^^^^^^^^^^^^^^
E TypeError: unsupported operand type(s) for >>: 'float' and 'float'
stampbot/github_client.py:741: TypeError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestUserHasPermission::test_user_has_permission_error - TypeError: unsupported operand type(s) for >>: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 62 passed, 3 deselected in 0.89s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_RShift, occurrence: 18
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -829,7 +829,7 @@
)
continue
- duration = time.time() - start_time
+ duration = time.time() >> start_time
github_api_request_duration_seconds.labels(operation="get_user_teams").observe(
duration
)................................................................F
=================================== FAILURES ===================================
____________ TestGetUserTeamSlugs.test_get_user_team_slugs_success _____________
self = <tests.test_github_client.TestGetUserTeamSlugs object at 0x7efd6649d1d0>
def test_get_user_team_slugs_success(self):
"""Test successful team membership check."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
# Mock user
mock_user = Mock()
mock_user.login = "alice"
# Mock team that has alice as member
mock_team = Mock()
mock_team.has_in_members.return_value = True
# Mock org
mock_org = Mock()
mock_org.get_team_by_slug.return_value = mock_team
mock_github = Mock()
mock_github.get_organization.return_value = mock_org
mock_github.get_user.return_value = mock_user
mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
result = client.get_user_team_slugs(
123456, "acme", "alice", ["release-team", "deploy-team"]
)
> assert result == ["release-team", "deploy-team"]
E AssertionError: assert [] == ['release-tea...'deploy-team']
E
E Right contains 2 more items, first extra item: 'release-team'
E
E Full diff:
E + []
E - [
E - 'release-team',
E - 'deploy-team',
E - ]
tests/test_github_client.py:848: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-05-16 20:07:16 [debug ] User alice is a member of team release-team extra={'org': 'acme', 'username': 'alice', 'team': 'release-team'}
2026-05-16 20:07:16 [debug ] User alice is a member of team deploy-team extra={'org': 'acme', 'username': 'alice', 'team': 'deploy-team'}
2026-05-16 20:07:16 [warning ] Failed to check team memberships for alice in acme: unsupported operand type(s) for >>: 'float' and 'float' extra={'org': 'acme', 'username': 'alice', 'installation_id': 123456, 'error': "unsupported operand type(s) for >>: 'float' and 'float'"}
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetUserTeamSlugs::test_get_user_team_slugs_success - AssertionError: assert [] == ['release-tea...'deploy-team']
Right contains 2 more items, first extra item: 'release-team'
Full diff:
+ []
- [
- 'release-team',
- 'deploy-team',
- ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 64 passed, 3 deselected in 0.80s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_RShift, occurrence: 19
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -849,7 +849,7 @@
return member_teams
except Exception as e:
- duration = time.time() - start_time
+ duration = time.time() >> start_time
github_api_request_duration_seconds.labels(operation="get_user_teams").observe(
duration
)....................................................................F
=================================== FAILURES ===================================
_______ TestGetUserTeamSlugs.test_get_user_team_slugs_general_exception ________
self = <stampbot.github_client.GitHubAppClient object at 0x7f651a9e6e10>
installation_id = 123456, org_name = 'acme', username = 'alice'
allowed_teams = ['release-team']
def get_user_team_slugs(
self,
installation_id: int,
org_name: str,
username: str,
allowed_teams: list[str],
) -> list[str]:
"""Get team slugs the user is a member of from the allowed teams list.
Args:
installation_id: GitHub App installation ID
org_name: Organization name
username: GitHub username to check
allowed_teams: List of team slugs to check (can be "org/team" or just "team")
Returns:
List of team slugs the user is a member of
"""
start_time = time.time()
with create_span(
"github.get_user_team_slugs",
{
"github.org": org_name,
"github.username": username,
"github.installation_id": installation_id,
"github.teams_to_check": len(allowed_teams),
},
) as span:
member_teams: list[str] = []
try:
client = self._get_installation_client(installation_id)
> org = client.get_organization(org_name)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/github_client.py:796:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1175: in __call__
return self._mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1179: in _mock_call
return self._execute_mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <Mock name='Github().get_organization' id='140072214755520'>
args = ('acme',), kwargs = {}, effect = Exception('Network error')
def _execute_mock_call(self, /, *args, **kwargs):
# separate from _increment_mock_call so that awaited functions are
# executed separately from their call, also AsyncMock overrides this method
effect = self.side_effect
if effect is not None:
if _is_exception(effect):
> raise effect
E Exception: Network error
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1234: Exception
During handling of the above exception, another exception occurred:
self = <tests.test_github_client.TestGetUserTeamSlugs object at 0x7f651ee745f0>
def test_get_user_team_slugs_general_exception(self):
"""Test team membership check with general exception."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_github = Mock()
mock_github.get_organization.side_effect = Exception("Network error")
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
> result = client.get_user_team_slugs(123456, "acme", "alice", ["release-team"])
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_github_client.py:1010:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.github_client.GitHubAppClient object at 0x7f651a9e6e10>
installation_id = 123456, org_name = 'acme', username = 'alice'
allowed_teams = ['release-team']
def get_user_team_slugs(
self,
installation_id: int,
org_name: str,
username: str,
allowed_teams: list[str],
) -> list[str]:
"""Get team slugs the user is a member of from the allowed teams list.
Args:
installation_id: GitHub App installation ID
org_name: Organization name
username: GitHub username to check
allowed_teams: List of team slugs to check (can be "org/team" or just "team")
Returns:
List of team slugs the user is a member of
"""
start_time = time.time()
with create_span(
"github.get_user_team_slugs",
{
"github.org": org_name,
"github.username": username,
"github.installation_id": installation_id,
"github.teams_to_check": len(allowed_teams),
},
) as span:
member_teams: list[str] = []
try:
client = self._get_installation_client(installation_id)
org = client.get_organization(org_name)
for team_ref in allowed_teams:
# Extract team slug (handle both "org/team" and "team" formats)
team_slug = team_ref.split("/")[-1] if "/" in team_ref else team_ref
try:
team = org.get_team_by_slug(team_slug)
# Check if user is a member
user = client.get_user(username)
if team.has_in_members(user): # type: ignore[arg-type]
member_teams.append(team_slug)
logger.debug(
"User %s is a member of team %s",
username,
team_slug,
extra={
"org": org_name,
"username": username,
"team": team_slug,
},
)
except GithubException as e:
# Team not found or no access - skip silently
logger.debug(
"Could not check team %s membership: %s",
team_slug,
e.data.get("message", str(e)) if hasattr(e, "data") else str(e),
extra={
"org": org_name,
"team": team_slug,
"username": username,
},
)
continue
duration = time.time() - start_time
github_api_request_duration_seconds.labels(operation="get_user_teams").observe(
duration
)
github_api_requests_total.labels(operation="get_user_teams", status="success").inc()
self._update_rate_limit_metrics(client, installation_id)
add_span_attributes(
span,
{
"github.member_teams": len(member_teams),
"github.teams_checked": len(allowed_teams),
},
)
set_span_ok(span)
return member_teams
except Exception as e:
> duration = time.time() >> start_time
^^^^^^^^^^^^^^^^^^^^^^^^^
E TypeError: unsupported operand type(s) for >>: 'float' and 'float'
stampbot/github_client.py:852: TypeError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetUserTeamSlugs::test_get_user_team_slugs_general_exception - TypeError: unsupported operand type(s) for >>: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 68 passed, 3 deselected in 0.91s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_LShift, occurrence: 0
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -168,7 +168,7 @@
retry=retry,
)
- duration = time.time() - start_time
+ duration = time.time() << start_time
github_api_request_duration_seconds.labels(operation="get_token").observe(duration)
github_api_requests_total.labels(operation="get_token", status="success").inc()
...............................................F
=================================== FAILURES ===================================
________ TestGetInstallationClient.test_get_installation_client_success ________
self = <tests.test_github_client.TestGetInstallationClient object at 0x7f35758c8050>
def test_get_installation_client_success(self):
"""Test successful installation client creation."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
> result = client._get_installation_client(123456)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_github_client.py:211:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.github_client.GitHubAppClient object at 0x7f3571888750>
installation_id = 123456
def _get_installation_client(self, installation_id: int) -> Github:
"""Get authenticated GitHub client for an installation.
Args:
installation_id: GitHub App installation ID.
Returns:
Authenticated Github client instance with timeout and retry configured.
Raises:
github.GithubException: If token exchange fails.
"""
start_time = time.time()
with create_span(
"github.get_installation_token",
{"github.installation_id": installation_id},
) as span:
try:
auth = self.integration.get_access_token(installation_id)
# Configure retry with exponential backoff
retry = Retry(
total=GITHUB_API_RETRY_TOTAL,
backoff_factor=GITHUB_API_RETRY_BACKOFF,
status_forcelist=[500, 502, 503, 504],
allowed_methods=["GET", "POST", "PUT", "DELETE", "PATCH"],
)
client = Github(
auth=Auth.Token(auth.token),
timeout=GITHUB_API_TIMEOUT,
retry=retry,
)
> duration = time.time() << start_time
^^^^^^^^^^^^^^^^^^^^^^^^^
E TypeError: unsupported operand type(s) for <<: 'float' and 'float'
stampbot/github_client.py:171: TypeError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetInstallationClient::test_get_installation_client_success - TypeError: unsupported operand type(s) for <<: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 47 passed, 3 deselected in 0.77s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_LShift, occurrence: 1
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -176,7 +176,7 @@
return client
except Exception as e:
- duration = time.time() - start_time
+ duration = time.time() << start_time
github_api_request_duration_seconds.labels(operation="get_token").observe(duration)
github_api_requests_total.labels(operation="get_token", status="failure").inc()
set_span_error(span, e)................................................F
=================================== FAILURES ===================================
________ TestGetInstallationClient.test_get_installation_client_failure ________
self = <stampbot.github_client.GitHubAppClient object at 0x7f095d9d5160>
installation_id = 123456
def _get_installation_client(self, installation_id: int) -> Github:
"""Get authenticated GitHub client for an installation.
Args:
installation_id: GitHub App installation ID.
Returns:
Authenticated Github client instance with timeout and retry configured.
Raises:
github.GithubException: If token exchange fails.
"""
start_time = time.time()
with create_span(
"github.get_installation_token",
{"github.installation_id": installation_id},
) as span:
try:
> auth = self.integration.get_access_token(installation_id)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/github_client.py:155:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1175: in __call__
return self._mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1179: in _mock_call
return self._execute_mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <Mock name='GithubIntegration().get_access_token' id='139678183080016'>
args = (123456,), kwargs = {}, effect = Exception('Token exchange failed')
def _execute_mock_call(self, /, *args, **kwargs):
# separate from _increment_mock_call so that awaited functions are
# executed separately from their call, also AsyncMock overrides this method
effect = self.side_effect
if effect is not None:
if _is_exception(effect):
> raise effect
E Exception: Token exchange failed
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1234: Exception
During handling of the above exception, another exception occurred:
self = <tests.test_github_client.TestGetInstallationClient object at 0x7f0960b80190>
def test_get_installation_client_failure(self):
"""Test installation client creation failure."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_integration.get_access_token.side_effect = Exception("Token exchange failed")
mock_integration_cls.return_value = mock_integration
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
with pytest.raises(Exception, match="Token exchange failed"):
> client._get_installation_client(123456)
tests/test_github_client.py:240:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.github_client.GitHubAppClient object at 0x7f095d9d5160>
installation_id = 123456
def _get_installation_client(self, installation_id: int) -> Github:
"""Get authenticated GitHub client for an installation.
Args:
installation_id: GitHub App installation ID.
Returns:
Authenticated Github client instance with timeout and retry configured.
Raises:
github.GithubException: If token exchange fails.
"""
start_time = time.time()
with create_span(
"github.get_installation_token",
{"github.installation_id": installation_id},
) as span:
try:
auth = self.integration.get_access_token(installation_id)
# Configure retry with exponential backoff
retry = Retry(
total=GITHUB_API_RETRY_TOTAL,
backoff_factor=GITHUB_API_RETRY_BACKOFF,
status_forcelist=[500, 502, 503, 504],
allowed_methods=["GET", "POST", "PUT", "DELETE", "PATCH"],
)
client = Github(
auth=Auth.Token(auth.token),
timeout=GITHUB_API_TIMEOUT,
retry=retry,
)
duration = time.time() - start_time
github_api_request_duration_seconds.labels(operation="get_token").observe(duration)
github_api_requests_total.labels(operation="get_token", status="success").inc()
set_span_ok(span)
return client
except Exception as e:
> duration = time.time() << start_time
^^^^^^^^^^^^^^^^^^^^^^^^^
E TypeError: unsupported operand type(s) for <<: 'float' and 'float'
stampbot/github_client.py:179: TypeError
During handling of the above exception, another exception occurred:
self = <tests.test_github_client.TestGetInstallationClient object at 0x7f0960b80190>
def test_get_installation_client_failure(self):
"""Test installation client creation failure."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_integration.get_access_token.side_effect = Exception("Token exchange failed")
mock_integration_cls.return_value = mock_integration
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
> with pytest.raises(Exception, match="Token exchange failed"):
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E AssertionError: Regex pattern did not match.
E Expected regex: 'Token exchange failed'
E Actual message: "unsupported operand type(s) for <<: 'float' and 'float'"
tests/test_github_client.py:239: AssertionError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetInstallationClient::test_get_installation_client_failure - AssertionError: Regex pattern did not match.
Expected regex: 'Token exchange failed'
Actual message: "unsupported operand type(s) for <<: 'float' and 'float'"
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 48 passed, 3 deselected in 0.85s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_LShift, occurrence: 2
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -240,7 +240,7 @@
event="APPROVE",
)
- duration = time.time() - start_time
+ duration = time.time() << start_time
github_api_request_duration_seconds.labels(operation="approve").observe(duration)
github_api_requests_total.labels(operation="approve", status="success").inc()
...................................................F
=================================== FAILURES ===================================
____________________ TestApprovePR.test_approve_pr_success _____________________
self = <tests.test_github_client.TestApprovePR object at 0x7f4bb8c0c550>
def test_approve_pr_success(self):
"""Test successful PR approval."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_pr = Mock()
mock_repo = Mock()
mock_repo.get_pull.return_value = mock_pr
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
result = client.approve_pr(123456, "owner/repo", 42)
> assert result is True
E assert False is True
tests/test_github_client.py:313: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-05-16 19:52:31 [error ] Failed to approve PR #42 in owner/repo: unsupported operand type(s) for <<: 'float' and 'float' extra={'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': "unsupported operand type(s) for <<: 'float' and 'float'"}
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestApprovePR::test_approve_pr_success - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 51 passed, 3 deselected in 0.77s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_LShift, occurrence: 3
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -260,7 +260,7 @@
return True
except Exception as e:
- duration = time.time() - start_time
+ duration = time.time() << start_time
github_api_request_duration_seconds.labels(operation="approve").observe(duration)
github_api_requests_total.labels(operation="approve", status="failure").inc()
....................................................F
=================================== FAILURES ===================================
____________________ TestApprovePR.test_approve_pr_failure _____________________
self = <stampbot.github_client.GitHubAppClient object at 0x7fdfd9262d00>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42
comment = 'Auto-approved by Stampbot'
def approve_pr(
self,
installation_id: int,
repo_full_name: str,
pr_number: int,
comment: str = "Auto-approved by Stampbot",
) -> bool:
"""Approve a pull request.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name (owner/repo)
pr_number: Pull request number
comment: Review comment
Returns:
True if successful, False otherwise
"""
start_time = time.time()
with create_span(
"github.approve_pr",
{
"github.repo": repo_full_name,
"github.pr_number": pr_number,
"github.installation_id": installation_id,
},
) as span:
try:
client = self._get_installation_client(installation_id)
repo = client.get_repo(repo_full_name)
pr = repo.get_pull(pr_number)
# Create approval review
> pr.create_review(
body=comment,
event="APPROVE",
)
stampbot/github_client.py:238:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1175: in __call__
return self._mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1179: in _mock_call
return self._execute_mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <Mock name='Github().get_repo().get_pull().create_review' id='140599397988880'>
args = (), kwargs = {'body': 'Auto-approved by Stampbot', 'event': 'APPROVE'}
effect = Exception('API Error')
def _execute_mock_call(self, /, *args, **kwargs):
# separate from _increment_mock_call so that awaited functions are
# executed separately from their call, also AsyncMock overrides this method
effect = self.side_effect
if effect is not None:
if _is_exception(effect):
> raise effect
E Exception: API Error
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1234: Exception
During handling of the above exception, another exception occurred:
self = <tests.test_github_client.TestApprovePR object at 0x7fdfdd164690>
def test_approve_pr_failure(self):
"""Test PR approval failure."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_pr = Mock()
mock_pr.create_review.side_effect = Exception("API Error")
mock_repo = Mock()
mock_repo.get_pull.return_value = mock_pr
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
> result = client.approve_pr(123456, "owner/repo", 42)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_github_client.py:350:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.github_client.GitHubAppClient object at 0x7fdfd9262d00>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42
comment = 'Auto-approved by Stampbot'
def approve_pr(
self,
installation_id: int,
repo_full_name: str,
pr_number: int,
comment: str = "Auto-approved by Stampbot",
) -> bool:
"""Approve a pull request.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name (owner/repo)
pr_number: Pull request number
comment: Review comment
Returns:
True if successful, False otherwise
"""
start_time = time.time()
with create_span(
"github.approve_pr",
{
"github.repo": repo_full_name,
"github.pr_number": pr_number,
"github.installation_id": installation_id,
},
) as span:
try:
client = self._get_installation_client(installation_id)
repo = client.get_repo(repo_full_name)
pr = repo.get_pull(pr_number)
# Create approval review
pr.create_review(
body=comment,
event="APPROVE",
)
duration = time.time() - start_time
github_api_request_duration_seconds.labels(operation="approve").observe(duration)
github_api_requests_total.labels(operation="approve", status="success").inc()
self._update_rate_limit_metrics(client, installation_id)
add_span_attributes(span, {"github.result": "approved"})
set_span_ok(span)
logger.info(
f"Approved PR #{pr_number} in {repo_full_name}",
extra={
"repo": repo_full_name,
"pr_number": pr_number,
"installation_id": installation_id,
},
)
return True
except Exception as e:
> duration = time.time() << start_time
^^^^^^^^^^^^^^^^^^^^^^^^^
E TypeError: unsupported operand type(s) for <<: 'float' and 'float'
stampbot/github_client.py:263: TypeError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestApprovePR::test_approve_pr_failure - TypeError: unsupported operand type(s) for <<: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 52 passed, 3 deselected in 0.85s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_LShift, occurrence: 4
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -317,7 +317,7 @@
review = pr.get_review(review_id)
review.dismiss(message)
- duration = time.time() - start_time
+ duration = time.time() << start_time
github_api_request_duration_seconds.labels(operation="dismiss").observe(duration)
github_api_requests_total.labels(operation="dismiss", status="success").inc()
.....................................................F
=================================== FAILURES ===================================
______________ TestDismissApproval.test_dismiss_approval_success _______________
self = <tests.test_github_client.TestDismissApproval object at 0x7f4ff1c147d0>
def test_dismiss_approval_success(self):
"""Test successful approval dismissal."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_review = Mock()
mock_pr = Mock()
mock_pr.get_review.return_value = mock_review
mock_repo = Mock()
mock_repo.get_pull.return_value = mock_pr
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
result = client.dismiss_approval(123456, "owner/repo", 42, 999)
> assert result is True
E assert False is True
tests/test_github_client.py:396: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-05-16 19:37:50 [error ] Failed to dismiss approval for PR #42 in owner/repo: unsupported operand type(s) for <<: 'float' and 'float' extra={'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': "unsupported operand type(s) for <<: 'float' and 'float'"}
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestDismissApproval::test_dismiss_approval_success - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 53 passed, 3 deselected in 0.74s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_LShift, occurrence: 5
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -338,7 +338,7 @@
return True
except Exception as e:
- duration = time.time() - start_time
+ duration = time.time() << start_time
github_api_request_duration_seconds.labels(operation="dismiss").observe(duration)
github_api_requests_total.labels(operation="dismiss", status="failure").inc()
......................................................F
=================================== FAILURES ===================================
______________ TestDismissApproval.test_dismiss_approval_failure _______________
self = <stampbot.github_client.GitHubAppClient object at 0x7f33f95f28f0>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42
review_id = 999, message = 'Approval dismissed by Stampbot'
def dismiss_approval(
self,
installation_id: int,
repo_full_name: str,
pr_number: int,
review_id: int,
message: str = "Approval dismissed by Stampbot",
) -> bool:
"""Dismiss a pull request approval.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name (owner/repo)
pr_number: Pull request number
review_id: Review ID to dismiss
message: Dismissal message
Returns:
True if successful, False otherwise
"""
start_time = time.time()
with create_span(
"github.dismiss_approval",
{
"github.repo": repo_full_name,
"github.pr_number": pr_number,
"github.review_id": review_id,
"github.installation_id": installation_id,
},
) as span:
try:
client = self._get_installation_client(installation_id)
repo = client.get_repo(repo_full_name)
pr = repo.get_pull(pr_number)
# Get the review and dismiss it
review = pr.get_review(review_id)
> review.dismiss(message)
stampbot/github_client.py:318:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1175: in __call__
return self._mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1179: in _mock_call
return self._execute_mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <Mock name='Github().get_repo().get_pull().get_review().dismiss' id='139861202695376'>
args = ('Approval dismissed by Stampbot',), kwargs = {}
effect = Exception('API Error')
def _execute_mock_call(self, /, *args, **kwargs):
# separate from _increment_mock_call so that awaited functions are
# executed separately from their call, also AsyncMock overrides this method
effect = self.side_effect
if effect is not None:
if _is_exception(effect):
> raise effect
E Exception: API Error
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1234: Exception
During handling of the above exception, another exception occurred:
self = <tests.test_github_client.TestDismissApproval object at 0x7f33fd434910>
def test_dismiss_approval_failure(self):
"""Test approval dismissal failure."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_review = Mock()
mock_review.dismiss.side_effect = Exception("API Error")
mock_pr = Mock()
mock_pr.get_review.return_value = mock_review
mock_repo = Mock()
mock_repo.get_pull.return_value = mock_pr
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
> result = client.dismiss_approval(123456, "owner/repo", 42, 999)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_github_client.py:435:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.github_client.GitHubAppClient object at 0x7f33f95f28f0>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42
review_id = 999, message = 'Approval dismissed by Stampbot'
def dismiss_approval(
self,
installation_id: int,
repo_full_name: str,
pr_number: int,
review_id: int,
message: str = "Approval dismissed by Stampbot",
) -> bool:
"""Dismiss a pull request approval.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name (owner/repo)
pr_number: Pull request number
review_id: Review ID to dismiss
message: Dismissal message
Returns:
True if successful, False otherwise
"""
start_time = time.time()
with create_span(
"github.dismiss_approval",
{
"github.repo": repo_full_name,
"github.pr_number": pr_number,
"github.review_id": review_id,
"github.installation_id": installation_id,
},
) as span:
try:
client = self._get_installation_client(installation_id)
repo = client.get_repo(repo_full_name)
pr = repo.get_pull(pr_number)
# Get the review and dismiss it
review = pr.get_review(review_id)
review.dismiss(message)
duration = time.time() - start_time
github_api_request_duration_seconds.labels(operation="dismiss").observe(duration)
github_api_requests_total.labels(operation="dismiss", status="success").inc()
self._update_rate_limit_metrics(client, installation_id)
add_span_attributes(span, {"github.result": "dismissed"})
set_span_ok(span)
logger.info(
f"Dismissed approval for PR #{pr_number} in {repo_full_name}",
extra={
"repo": repo_full_name,
"pr_number": pr_number,
"installation_id": installation_id,
"review_id": review_id,
},
)
return True
except Exception as e:
> duration = time.time() << start_time
^^^^^^^^^^^^^^^^^^^^^^^^^
E TypeError: unsupported operand type(s) for <<: 'float' and 'float'
stampbot/github_client.py:341: TypeError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestDismissApproval::test_dismiss_approval_failure - TypeError: unsupported operand type(s) for <<: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 54 passed, 3 deselected in 0.87s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_LShift, occurrence: 6
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -393,7 +393,7 @@
content = repo.get_contents(file_path, ref=ref) # type: ignore[arg-type]
if isinstance(content, list):
- duration = time.time() - start_time
+ duration = time.time() << start_time
github_api_request_duration_seconds.labels(operation="get_file").observe(
duration
)........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.67s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_LShift, occurrence: 7
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -402,7 +402,7 @@
set_span_ok(span)
return None
- duration = time.time() - start_time
+ duration = time.time() << start_time
github_api_request_duration_seconds.labels(operation="get_file").observe(duration)
github_api_requests_total.labels(operation="get_file", status="success").inc()
.......................................................F
=================================== FAILURES ===================================
__________________ TestGetRepoFile.test_get_repo_file_success __________________
self = <tests.test_github_client.TestGetRepoFile object at 0x7f4daf98ca50>
def test_get_repo_file_success(self):
"""Test successful file retrieval."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_content = Mock()
mock_content.decoded_content = b"file content"
mock_repo = Mock()
mock_repo.get_contents.return_value = mock_content
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
result = client.get_repo_file(123456, "owner/repo", "stampbot.toml")
> assert result == "file content"
E AssertionError: assert None == 'file content'
tests/test_github_client.py:480: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-05-16 20:05:52 [debug ] Could not fetch stampbot.toml from owner/repo: unsupported operand type(s) for <<: 'float' and 'float' extra={'repo': 'owner/repo', 'file_path': 'stampbot.toml', 'installation_id': 123456}
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetRepoFile::test_get_repo_file_success - AssertionError: assert None == 'file content'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 55 passed, 3 deselected in 0.76s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_LShift, occurrence: 8
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -414,7 +414,7 @@
return content.decoded_content.decode("utf-8")
except Exception as e:
- duration = time.time() - start_time
+ duration = time.time() << start_time
github_api_request_duration_seconds.labels(operation="get_file").observe(duration)
github_api_requests_total.labels(operation="get_file", status="not_found").inc()
.........................................................F
=================================== FAILURES ===================================
___________ TestGetRepoFile.test_get_repo_file_returns_none_on_error ___________
self = <stampbot.github_client.GitHubAppClient object at 0x7f3480d58cd0>
installation_id = 123456, repo_full_name = 'owner/repo'
file_path = 'nonexistent.toml', ref = None
def get_repo_file(
self,
installation_id: int,
repo_full_name: str,
file_path: str,
ref: str | None = None,
) -> str | None:
"""Get file content from repository.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name (owner/repo)
file_path: Path to file in repository
ref: Git reference (branch, tag, commit). Defaults to default branch
Returns:
File content as string, or None if not found
"""
start_time = time.time()
with create_span(
"github.get_file",
{
"github.repo": repo_full_name,
"github.file_path": file_path,
"github.ref": ref or "default",
"github.installation_id": installation_id,
},
) as span:
try:
client = self._get_installation_client(installation_id)
repo = client.get_repo(repo_full_name)
> content = repo.get_contents(file_path, ref=ref) # type: ignore[arg-type]
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/github_client.py:394:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1175: in __call__
return self._mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1179: in _mock_call
return self._execute_mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <Mock name='Github().get_repo().get_contents' id='139863476815584'>
args = ('nonexistent.toml',), kwargs = {'ref': None}
effect = Exception('File not found')
def _execute_mock_call(self, /, *args, **kwargs):
# separate from _increment_mock_call so that awaited functions are
# executed separately from their call, also AsyncMock overrides this method
effect = self.side_effect
if effect is not None:
if _is_exception(effect):
> raise effect
E Exception: File not found
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1234: Exception
During handling of the above exception, another exception occurred:
self = <tests.test_github_client.TestGetRepoFile object at 0x7f3485121940>
def test_get_repo_file_returns_none_on_error(self):
"""Test that get_repo_file returns None when file not found."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_repo = Mock()
mock_repo.get_contents.side_effect = Exception("File not found")
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
> result = client.get_repo_file(123456, "owner/repo", "nonexistent.toml")
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_github_client.py:551:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.github_client.GitHubAppClient object at 0x7f3480d58cd0>
installation_id = 123456, repo_full_name = 'owner/repo'
file_path = 'nonexistent.toml', ref = None
def get_repo_file(
self,
installation_id: int,
repo_full_name: str,
file_path: str,
ref: str | None = None,
) -> str | None:
"""Get file content from repository.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name (owner/repo)
file_path: Path to file in repository
ref: Git reference (branch, tag, commit). Defaults to default branch
Returns:
File content as string, or None if not found
"""
start_time = time.time()
with create_span(
"github.get_file",
{
"github.repo": repo_full_name,
"github.file_path": file_path,
"github.ref": ref or "default",
"github.installation_id": installation_id,
},
) as span:
try:
client = self._get_installation_client(installation_id)
repo = client.get_repo(repo_full_name)
content = repo.get_contents(file_path, ref=ref) # type: ignore[arg-type]
if isinstance(content, list):
duration = time.time() - start_time
github_api_request_duration_seconds.labels(operation="get_file").observe(
duration
)
github_api_requests_total.labels(operation="get_file", status="not_found").inc()
add_span_attributes(span, {"github.result": "not_found"})
set_span_ok(span)
return None
duration = time.time() - start_time
github_api_request_duration_seconds.labels(operation="get_file").observe(duration)
github_api_requests_total.labels(operation="get_file", status="success").inc()
self._update_rate_limit_metrics(client, installation_id)
add_span_attributes(span, {"github.result": "found"})
set_span_ok(span)
return content.decoded_content.decode("utf-8")
except Exception as e:
> duration = time.time() << start_time
^^^^^^^^^^^^^^^^^^^^^^^^^
E TypeError: unsupported operand type(s) for <<: 'float' and 'float'
stampbot/github_client.py:417: TypeError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetRepoFile::test_get_repo_file_returns_none_on_error - TypeError: unsupported operand type(s) for <<: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 57 passed, 3 deselected in 0.86s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_LShift, occurrence: 9
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -473,7 +473,7 @@
if review.user.login == bot_user and review.state == "APPROVED":
bot_review_ids.append(review.id)
- duration = time.time() - start_time
+ duration = time.time() << start_time
github_api_request_duration_seconds.labels(operation="find_reviews").observe(
duration
)..........................................................F
=================================== FAILURES ===================================
_______________ TestFindBotReviews.test_find_bot_reviews_success _______________
self = <tests.test_github_client.TestFindBotReviews object at 0x7f075f194cd0>
def test_find_bot_reviews_success(self):
"""Test successful bot review finding."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration.get_app.return_value = Mock(slug="stampbot")
mock_integration_cls.return_value = mock_integration
# Create mock reviews
bot_review = Mock()
bot_review.user.login = "stampbot[bot]"
bot_review.state = "APPROVED"
bot_review.id = 123
other_review = Mock()
other_review.user.login = "other-user"
other_review.state = "APPROVED"
other_review.id = 456
mock_pr = Mock()
mock_pr.get_reviews.return_value = [bot_review, other_review]
mock_repo = Mock()
mock_repo.get_pull.return_value = mock_pr
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
result = client.find_bot_reviews(123456, "owner/repo", 42)
> assert result == [123]
E assert [] == [123]
E
E Right contains one more item: 123
E
E Full diff:
E + []
E - [
E - 123,
E - ]
tests/test_github_client.py:608: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-05-16 19:56:31 [error ] Failed to find bot reviews for PR #42 in owner/repo: unsupported operand type(s) for <<: 'float' and 'float' extra={'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': "unsupported operand type(s) for <<: 'float' and 'float'"}
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestFindBotReviews::test_find_bot_reviews_success - assert [] == [123]
Right contains one more item: 123
Full diff:
+ []
- [
- 123,
- ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 58 passed, 3 deselected in 0.78s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_LShift, occurrence: 10
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -489,7 +489,7 @@
return bot_review_ids
except Exception as e:
- duration = time.time() - start_time
+ duration = time.time() << start_time
github_api_request_duration_seconds.labels(operation="find_reviews").observe(
duration
)...........................................................F
=================================== FAILURES ===================================
_______ TestFindBotReviews.test_find_bot_reviews_returns_empty_on_error ________
self = <stampbot.github_client.GitHubAppClient object at 0x7f9a0a850f30>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42
def find_bot_reviews(
self,
installation_id: int,
repo_full_name: str,
pr_number: int,
) -> list[int]:
"""Find all reviews created by this bot on a PR.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name (owner/repo)
pr_number: Pull request number
Returns:
List of review IDs created by the bot
"""
start_time = time.time()
with create_span(
"github.find_bot_reviews",
{
"github.repo": repo_full_name,
"github.pr_number": pr_number,
"github.installation_id": installation_id,
},
) as span:
try:
client = self._get_installation_client(installation_id)
> repo = client.get_repo(repo_full_name)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/github_client.py:462:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1175: in __call__
return self._mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1179: in _mock_call
return self._execute_mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <Mock name='Github().get_repo' id='140299578671856'>
args = ('owner/repo',), kwargs = {}, effect = Exception('API Error')
def _execute_mock_call(self, /, *args, **kwargs):
# separate from _increment_mock_call so that awaited functions are
# executed separately from their call, also AsyncMock overrides this method
effect = self.side_effect
if effect is not None:
if _is_exception(effect):
> raise effect
E Exception: API Error
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1234: Exception
During handling of the above exception, another exception occurred:
self = <tests.test_github_client.TestFindBotReviews object at 0x7f9a0ed08e10>
def test_find_bot_reviews_returns_empty_on_error(self):
"""Test that find_bot_reviews returns empty list on error."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_github = Mock()
mock_github.get_repo.side_effect = Exception("API Error")
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
> result = client.find_bot_reviews(123456, "owner/repo", 42)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_github_client.py:640:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.github_client.GitHubAppClient object at 0x7f9a0a850f30>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42
def find_bot_reviews(
self,
installation_id: int,
repo_full_name: str,
pr_number: int,
) -> list[int]:
"""Find all reviews created by this bot on a PR.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name (owner/repo)
pr_number: Pull request number
Returns:
List of review IDs created by the bot
"""
start_time = time.time()
with create_span(
"github.find_bot_reviews",
{
"github.repo": repo_full_name,
"github.pr_number": pr_number,
"github.installation_id": installation_id,
},
) as span:
try:
client = self._get_installation_client(installation_id)
repo = client.get_repo(repo_full_name)
pr = repo.get_pull(pr_number)
# Get bot user via JWT-authenticated integration (installation tokens
# cannot call GET /user, which is restricted by GitHub Apps integration)
app_info = self.integration.get_app()
bot_user = f"{app_info.slug}[bot]"
# Find all reviews by bot that are approvals
bot_review_ids = []
for review in pr.get_reviews():
if review.user.login == bot_user and review.state == "APPROVED":
bot_review_ids.append(review.id)
duration = time.time() - start_time
github_api_request_duration_seconds.labels(operation="find_reviews").observe(
duration
)
github_api_requests_total.labels(operation="find_reviews", status="success").inc()
self._update_rate_limit_metrics(client, installation_id)
add_span_attributes(
span, {"github.reviews_found": len(bot_review_ids), "github.bot_user": bot_user}
)
set_span_ok(span)
return bot_review_ids
except Exception as e:
> duration = time.time() << start_time
^^^^^^^^^^^^^^^^^^^^^^^^^
E TypeError: unsupported operand type(s) for <<: 'float' and 'float'
stampbot/github_client.py:492: TypeError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestFindBotReviews::test_find_bot_reviews_returns_empty_on_error - TypeError: unsupported operand type(s) for <<: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 59 passed, 3 deselected in 0.87s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_LShift, occurrence: 11
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -546,7 +546,7 @@
pr.create_review(body=message, event="COMMENT")
- duration = time.time() - start_time
+ duration = time.time() << start_time
github_api_request_duration_seconds.labels(operation="comment").observe(duration)
github_api_requests_total.labels(operation="comment", status="success").inc()
........................................................................ [ 34%]
.F
=================================== FAILURES ===================================
_______ TestCreatePrReviewComment.test_create_pr_review_comment_success ________
self = <tests.test_github_client.TestCreatePrReviewComment object at 0x7f7475e9d6d0>
def test_create_pr_review_comment_success(self):
"""Test successful review comment creation."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_pr = Mock()
mock_repo = Mock()
mock_repo.get_pull.return_value = mock_pr
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=1, limit=2))
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
result = client.create_pr_review_comment(
123456,
"owner/repo",
42,
"Config error",
)
> assert result is True
E assert False is True
tests/test_github_client.py:1213: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-05-16 19:28:35 [warning ] Failed to post config error review for PR #42 in owner/repo: unsupported operand type(s) for <<: 'float' and 'float' extra={'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': "unsupported operand type(s) for <<: 'float' and 'float'"}
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestCreatePrReviewComment::test_create_pr_review_comment_success - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 73 passed, 3 deselected in 0.79s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_LShift, occurrence: 12
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -568,7 +568,7 @@
return True
except Exception as e:
- duration = time.time() - start_time
+ duration = time.time() << start_time
github_api_request_duration_seconds.labels(operation="comment").observe(duration)
github_api_requests_total.labels(operation="comment", status="failure").inc()
........................................................................ [ 34%]
..F
=================================== FAILURES ===================================
_______ TestCreatePrReviewComment.test_create_pr_review_comment_failure ________
self = <stampbot.github_client.GitHubAppClient object at 0x7fce76eb73b0>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42
message = 'Config error'
def create_pr_review_comment(
self,
installation_id: int,
repo_full_name: str,
pr_number: int,
message: str,
) -> bool:
"""Create a comment review on a pull request.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name (owner/repo)
pr_number: Pull request number
message: Review comment body
Returns:
True if successful, False otherwise
"""
start_time = time.time()
with create_span(
"github.create_review_comment",
{
"github.repo": repo_full_name,
"github.pr_number": pr_number,
"github.installation_id": installation_id,
},
) as span:
try:
client = self._get_installation_client(installation_id)
repo = client.get_repo(repo_full_name)
> pr = repo.get_pull(pr_number)
^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/github_client.py:545:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1175: in __call__
return self._mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1179: in _mock_call
return self._execute_mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <Mock name='Github().get_repo().get_pull' id='140524735056928'>
args = (42,), kwargs = {}, effect = Exception('API Error')
def _execute_mock_call(self, /, *args, **kwargs):
# separate from _increment_mock_call so that awaited functions are
# executed separately from their call, also AsyncMock overrides this method
effect = self.side_effect
if effect is not None:
if _is_exception(effect):
> raise effect
E Exception: API Error
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1234: Exception
During handling of the above exception, another exception occurred:
self = <tests.test_github_client.TestCreatePrReviewComment object at 0x7fce7b0dd810>
def test_create_pr_review_comment_failure(self):
"""Test review comment creation handles errors."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_repo = Mock()
mock_repo.get_pull.side_effect = Exception("API Error")
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
> result = client.create_pr_review_comment(
123456,
"owner/repo",
42,
"Config error",
)
tests/test_github_client.py:1248:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.github_client.GitHubAppClient object at 0x7fce76eb73b0>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42
message = 'Config error'
def create_pr_review_comment(
self,
installation_id: int,
repo_full_name: str,
pr_number: int,
message: str,
) -> bool:
"""Create a comment review on a pull request.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name (owner/repo)
pr_number: Pull request number
message: Review comment body
Returns:
True if successful, False otherwise
"""
start_time = time.time()
with create_span(
"github.create_review_comment",
{
"github.repo": repo_full_name,
"github.pr_number": pr_number,
"github.installation_id": installation_id,
},
) as span:
try:
client = self._get_installation_client(installation_id)
repo = client.get_repo(repo_full_name)
pr = repo.get_pull(pr_number)
pr.create_review(body=message, event="COMMENT")
duration = time.time() - start_time
github_api_request_duration_seconds.labels(operation="comment").observe(duration)
github_api_requests_total.labels(operation="comment", status="success").inc()
self._update_rate_limit_metrics(client, installation_id)
add_span_attributes(span, {"github.result": "commented"})
set_span_ok(span)
logger.info(
"Posted config error review on PR #%d in %s",
pr_number,
repo_full_name,
extra={
"repo": repo_full_name,
"pr_number": pr_number,
"installation_id": installation_id,
},
)
return True
except Exception as e:
> duration = time.time() << start_time
^^^^^^^^^^^^^^^^^^^^^^^^^
E TypeError: unsupported operand type(s) for <<: 'float' and 'float'
stampbot/github_client.py:571: TypeError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestCreatePrReviewComment::test_create_pr_review_comment_failure - TypeError: unsupported operand type(s) for <<: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 74 passed, 3 deselected in 0.90s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_LShift, occurrence: 13
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -619,7 +619,7 @@
repo = client.get_repo(repo_full_name)
repo.get_label(label_name)
- duration = time.time() - start_time
+ duration = time.time() << start_time
github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
github_api_requests_total.labels(operation="get_label", status="success").inc()
.....................................................................F
=================================== FAILURES ===================================
__________________ TestRepoHasLabel.test_repo_has_label_true ___________________
self = <tests.test_github_client.TestRepoHasLabel object at 0x7f965d735450>
def test_repo_has_label_true(self):
"""Test repo_has_label returns True when label exists."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_repo = Mock()
mock_repo.get_label.return_value = Mock()
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=1, limit=2))
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
result = client.repo_has_label(123456, "owner/repo", "autoapprove")
> assert result is True
E assert None is True
tests/test_github_client.py:1054: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-05-16 19:23:36 [debug ] Could not verify label autoapprove in owner/repo: unsupported operand type(s) for <<: 'float' and 'float' extra={'repo': 'owner/repo', 'label': 'autoapprove', 'installation_id': 123456, 'error': "unsupported operand type(s) for <<: 'float' and 'float'"}
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestRepoHasLabel::test_repo_has_label_true - assert None is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 69 passed, 3 deselected in 0.77s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_LShift, occurrence: 14
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -630,7 +630,7 @@
return True
except GithubException as e:
- duration = time.time() - start_time
+ duration = time.time() << start_time
github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
github_api_requests_total.labels(operation="get_label", status="failure").inc()
......................................................................F
=================================== FAILURES ===================================
________________ TestRepoHasLabel.test_repo_has_label_not_found ________________
self = <stampbot.github_client.GitHubAppClient object at 0x7f0073545e50>
installation_id = 123456, repo_full_name = 'owner/repo', label_name = 'missing'
def repo_has_label(
self,
installation_id: int,
repo_full_name: str,
label_name: str,
) -> bool | None:
"""Check whether a repository has a label.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name (owner/repo)
label_name: Label name to look up
Returns:
True if label exists, False if not found, None if unknown due to error
"""
start_time = time.time()
with create_span(
"github.get_label",
{
"github.repo": repo_full_name,
"github.label": label_name,
"github.installation_id": installation_id,
},
) as span:
try:
client = self._get_installation_client(installation_id)
repo = client.get_repo(repo_full_name)
> repo.get_label(label_name)
stampbot/github_client.py:620:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1175: in __call__
return self._mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1179: in _mock_call
return self._execute_mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <Mock name='Github().get_repo().get_label' id='139639911459072'>
args = ('missing',), kwargs = {}
effect = GithubException(404 {"message": "Not Found"})
def _execute_mock_call(self, /, *args, **kwargs):
# separate from _increment_mock_call so that awaited functions are
# executed separately from their call, also AsyncMock overrides this method
effect = self.side_effect
if effect is not None:
if _is_exception(effect):
> raise effect
E github.GithubException.GithubException: 404 {"message": "Not Found"}
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1234: GithubException
During handling of the above exception, another exception occurred:
self = <tests.test_github_client.TestRepoHasLabel object at 0x7f007780d590>
def test_repo_has_label_not_found(self):
"""Test repo_has_label returns False when label is missing."""
from github.GithubException import GithubException
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_repo = Mock()
mock_repo.get_label.side_effect = GithubException(404, {"message": "Not Found"}, None)
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
> result = client.repo_has_label(123456, "owner/repo", "missing")
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_github_client.py:1090:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.github_client.GitHubAppClient object at 0x7f0073545e50>
installation_id = 123456, repo_full_name = 'owner/repo', label_name = 'missing'
def repo_has_label(
self,
installation_id: int,
repo_full_name: str,
label_name: str,
) -> bool | None:
"""Check whether a repository has a label.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name (owner/repo)
label_name: Label name to look up
Returns:
True if label exists, False if not found, None if unknown due to error
"""
start_time = time.time()
with create_span(
"github.get_label",
{
"github.repo": repo_full_name,
"github.label": label_name,
"github.installation_id": installation_id,
},
) as span:
try:
client = self._get_installation_client(installation_id)
repo = client.get_repo(repo_full_name)
repo.get_label(label_name)
duration = time.time() - start_time
github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
github_api_requests_total.labels(operation="get_label", status="success").inc()
self._update_rate_limit_metrics(client, installation_id)
add_span_attributes(span, {"github.label_found": True})
set_span_ok(span)
return True
except GithubException as e:
> duration = time.time() << start_time
^^^^^^^^^^^^^^^^^^^^^^^^^
E TypeError: unsupported operand type(s) for <<: 'float' and 'float'
stampbot/github_client.py:633: TypeError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestRepoHasLabel::test_repo_has_label_not_found - TypeError: unsupported operand type(s) for <<: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 70 passed, 3 deselected in 0.91s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_LShift, occurrence: 15
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -655,7 +655,7 @@
return None
except Exception as e:
- duration = time.time() - start_time
+ duration = time.time() << start_time
github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
github_api_requests_total.labels(operation="get_label", status="failure").inc()
........................................................................ [ 34%]
F
=================================== FAILURES ===================================
__________________ TestRepoHasLabel.test_repo_has_label_error __________________
self = <stampbot.github_client.GitHubAppClient object at 0x7f511d1878f0>
installation_id = 123456, repo_full_name = 'owner/repo'
label_name = 'autoapprove'
def repo_has_label(
self,
installation_id: int,
repo_full_name: str,
label_name: str,
) -> bool | None:
"""Check whether a repository has a label.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name (owner/repo)
label_name: Label name to look up
Returns:
True if label exists, False if not found, None if unknown due to error
"""
start_time = time.time()
with create_span(
"github.get_label",
{
"github.repo": repo_full_name,
"github.label": label_name,
"github.installation_id": installation_id,
},
) as span:
try:
client = self._get_installation_client(installation_id)
repo = client.get_repo(repo_full_name)
> repo.get_label(label_name)
stampbot/github_client.py:620:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1175: in __call__
return self._mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1179: in _mock_call
return self._execute_mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <Mock name='Github().get_repo().get_label' id='139986357114528'>
args = ('autoapprove',), kwargs = {}, effect = Exception('API Error')
def _execute_mock_call(self, /, *args, **kwargs):
# separate from _increment_mock_call so that awaited functions are
# executed separately from their call, also AsyncMock overrides this method
effect = self.side_effect
if effect is not None:
if _is_exception(effect):
> raise effect
E Exception: API Error
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1234: Exception
During handling of the above exception, another exception occurred:
self = <tests.test_github_client.TestRepoHasLabel object at 0x7f512171e060>
def test_repo_has_label_error(self):
"""Test repo_has_label returns None on error."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_repo = Mock()
mock_repo.get_label.side_effect = Exception("API Error")
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
> result = client.repo_has_label(123456, "owner/repo", "autoapprove")
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_github_client.py:1164:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.github_client.GitHubAppClient object at 0x7f511d1878f0>
installation_id = 123456, repo_full_name = 'owner/repo'
label_name = 'autoapprove'
def repo_has_label(
self,
installation_id: int,
repo_full_name: str,
label_name: str,
) -> bool | None:
"""Check whether a repository has a label.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name (owner/repo)
label_name: Label name to look up
Returns:
True if label exists, False if not found, None if unknown due to error
"""
start_time = time.time()
with create_span(
"github.get_label",
{
"github.repo": repo_full_name,
"github.label": label_name,
"github.installation_id": installation_id,
},
) as span:
try:
client = self._get_installation_client(installation_id)
repo = client.get_repo(repo_full_name)
repo.get_label(label_name)
duration = time.time() - start_time
github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
github_api_requests_total.labels(operation="get_label", status="success").inc()
self._update_rate_limit_metrics(client, installation_id)
add_span_attributes(span, {"github.label_found": True})
set_span_ok(span)
return True
except GithubException as e:
duration = time.time() - start_time
github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
github_api_requests_total.labels(operation="get_label", status="failure").inc()
if e.status == 404:
add_span_attributes(span, {"github.label_found": False})
set_span_ok(span)
return False
set_span_error(span, e)
logger.debug(
"Could not verify label %s in %s: %s",
label_name,
repo_full_name,
_sanitize_error(e),
extra={
"repo": repo_full_name,
"label": label_name,
"installation_id": installation_id,
"error": _sanitize_error(e),
},
)
return None
except Exception as e:
> duration = time.time() << start_time
^^^^^^^^^^^^^^^^^^^^^^^^^
E TypeError: unsupported operand type(s) for <<: 'float' and 'float'
stampbot/github_client.py:658: TypeError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestRepoHasLabel::test_repo_has_label_error - TypeError: unsupported operand type(s) for <<: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 72 passed, 3 deselected in 0.92s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_LShift, occurrence: 16
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -718,7 +718,7 @@
has_permission = permission_index >= required_index
- duration = time.time() - start_time
+ duration = time.time() << start_time
github_api_request_duration_seconds.labels(operation="get_permission").observe(
duration
)............................................................F
=================================== FAILURES ===================================
_____________ TestUserHasPermission.test_user_has_permission_true ______________
self = <tests.test_github_client.TestUserHasPermission object at 0x7fd5f6f6cf50>
def test_user_has_permission_true(self):
"""Test required permission satisfied returns True."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_repo = Mock()
mock_repo.get_collaborator_permission.return_value = "write"
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=1, limit=2))
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
result = client.user_has_permission(123456, "owner/repo", "alice", "write")
> assert result is True
E assert False is True
tests/test_github_client.py:683: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-05-16 19:41:56 [warning ] Failed to check collaborator permission for alice in owner/repo: unsupported operand type(s) for <<: 'float' and 'float' extra={'repo': 'owner/repo', 'username': 'alice', 'installation_id': 123456, 'error': "unsupported operand type(s) for <<: 'float' and 'float'"}
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestUserHasPermission::test_user_has_permission_true - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 60 passed, 3 deselected in 0.76s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_LShift, occurrence: 17
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -738,7 +738,7 @@
return has_permission
except Exception as e:
- duration = time.time() - start_time
+ duration = time.time() << start_time
github_api_request_duration_seconds.labels(operation="get_permission").observe(
duration
)..............................................................F
=================================== FAILURES ===================================
_____________ TestUserHasPermission.test_user_has_permission_error _____________
self = <stampbot.github_client.GitHubAppClient object at 0x7f704f958110>
installation_id = 123456, repo_full_name = 'owner/repo', username = 'carol'
required_permission = 'maintain'
def user_has_permission(
self,
installation_id: int,
repo_full_name: str,
username: str,
required_permission: str,
) -> bool:
"""Check whether a user has required access to a repository.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name (owner/repo)
username: GitHub username to check
required_permission: Minimum required repository permission
Returns:
True if user meets or exceeds required permission, False otherwise
"""
start_time = time.time()
with create_span(
"github.get_collaborator_permission",
{
"github.repo": repo_full_name,
"github.username": username,
"github.required_permission": required_permission,
"github.installation_id": installation_id,
},
) as span:
try:
client = self._get_installation_client(installation_id)
repo = client.get_repo(repo_full_name)
> permission = repo.get_collaborator_permission(username)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/github_client.py:710:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1175: in __call__
return self._mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1179: in _mock_call
return self._execute_mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <Mock name='Github().get_repo().get_collaborator_permission' id='140120348216352'>
args = ('carol',), kwargs = {}, effect = Exception('API Error')
def _execute_mock_call(self, /, *args, **kwargs):
# separate from _increment_mock_call so that awaited functions are
# executed separately from their call, also AsyncMock overrides this method
effect = self.side_effect
if effect is not None:
if _is_exception(effect):
> raise effect
E Exception: API Error
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1234: Exception
During handling of the above exception, another exception occurred:
self = <tests.test_github_client.TestUserHasPermission object at 0x7f7053c39a70>
def test_user_has_permission_error(self):
"""Test permission check returns False on error."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_repo = Mock()
mock_repo.get_collaborator_permission.side_effect = Exception("API Error")
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
> result = client.user_has_permission(123456, "owner/repo", "carol", "maintain")
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_github_client.py:754:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.github_client.GitHubAppClient object at 0x7f704f958110>
installation_id = 123456, repo_full_name = 'owner/repo', username = 'carol'
required_permission = 'maintain'
def user_has_permission(
self,
installation_id: int,
repo_full_name: str,
username: str,
required_permission: str,
) -> bool:
"""Check whether a user has required access to a repository.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name (owner/repo)
username: GitHub username to check
required_permission: Minimum required repository permission
Returns:
True if user meets or exceeds required permission, False otherwise
"""
start_time = time.time()
with create_span(
"github.get_collaborator_permission",
{
"github.repo": repo_full_name,
"github.username": username,
"github.required_permission": required_permission,
"github.installation_id": installation_id,
},
) as span:
try:
client = self._get_installation_client(installation_id)
repo = client.get_repo(repo_full_name)
permission = repo.get_collaborator_permission(username)
permission_order = ["none", "read", "triage", "write", "maintain", "admin"]
try:
permission_index = permission_order.index(permission)
required_index = permission_order.index(required_permission)
except ValueError:
permission_index = -1
required_index = 99
has_permission = permission_index >= required_index
duration = time.time() - start_time
github_api_request_duration_seconds.labels(operation="get_permission").observe(
duration
)
github_api_requests_total.labels(operation="get_permission", status="success").inc()
self._update_rate_limit_metrics(client, installation_id)
add_span_attributes(
span,
{
"github.permission": permission,
"github.has_permission": has_permission,
},
)
set_span_ok(span)
return has_permission
except Exception as e:
> duration = time.time() << start_time
^^^^^^^^^^^^^^^^^^^^^^^^^
E TypeError: unsupported operand type(s) for <<: 'float' and 'float'
stampbot/github_client.py:741: TypeError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestUserHasPermission::test_user_has_permission_error - TypeError: unsupported operand type(s) for <<: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 62 passed, 3 deselected in 0.87s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_LShift, occurrence: 18
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -829,7 +829,7 @@
)
continue
- duration = time.time() - start_time
+ duration = time.time() << start_time
github_api_request_duration_seconds.labels(operation="get_user_teams").observe(
duration
)................................................................F
=================================== FAILURES ===================================
____________ TestGetUserTeamSlugs.test_get_user_team_slugs_success _____________
self = <tests.test_github_client.TestGetUserTeamSlugs object at 0x7f6eed4d91d0>
def test_get_user_team_slugs_success(self):
"""Test successful team membership check."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
# Mock user
mock_user = Mock()
mock_user.login = "alice"
# Mock team that has alice as member
mock_team = Mock()
mock_team.has_in_members.return_value = True
# Mock org
mock_org = Mock()
mock_org.get_team_by_slug.return_value = mock_team
mock_github = Mock()
mock_github.get_organization.return_value = mock_org
mock_github.get_user.return_value = mock_user
mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
result = client.get_user_team_slugs(
123456, "acme", "alice", ["release-team", "deploy-team"]
)
> assert result == ["release-team", "deploy-team"]
E AssertionError: assert [] == ['release-tea...'deploy-team']
E
E Right contains 2 more items, first extra item: 'release-team'
E
E Full diff:
E + []
E - [
E - 'release-team',
E - 'deploy-team',
E - ]
tests/test_github_client.py:848: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-05-16 19:42:20 [debug ] User alice is a member of team release-team extra={'org': 'acme', 'username': 'alice', 'team': 'release-team'}
2026-05-16 19:42:20 [debug ] User alice is a member of team deploy-team extra={'org': 'acme', 'username': 'alice', 'team': 'deploy-team'}
2026-05-16 19:42:20 [warning ] Failed to check team memberships for alice in acme: unsupported operand type(s) for <<: 'float' and 'float' extra={'org': 'acme', 'username': 'alice', 'installation_id': 123456, 'error': "unsupported operand type(s) for <<: 'float' and 'float'"}
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetUserTeamSlugs::test_get_user_team_slugs_success - AssertionError: assert [] == ['release-tea...'deploy-team']
Right contains 2 more items, first extra item: 'release-team'
Full diff:
+ []
- [
- 'release-team',
- 'deploy-team',
- ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 64 passed, 3 deselected in 0.78s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_LShift, occurrence: 19
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -849,7 +849,7 @@
return member_teams
except Exception as e:
- duration = time.time() - start_time
+ duration = time.time() << start_time
github_api_request_duration_seconds.labels(operation="get_user_teams").observe(
duration
)....................................................................F
=================================== FAILURES ===================================
_______ TestGetUserTeamSlugs.test_get_user_team_slugs_general_exception ________
self = <stampbot.github_client.GitHubAppClient object at 0x7f1c701219d0>
installation_id = 123456, org_name = 'acme', username = 'alice'
allowed_teams = ['release-team']
def get_user_team_slugs(
self,
installation_id: int,
org_name: str,
username: str,
allowed_teams: list[str],
) -> list[str]:
"""Get team slugs the user is a member of from the allowed teams list.
Args:
installation_id: GitHub App installation ID
org_name: Organization name
username: GitHub username to check
allowed_teams: List of team slugs to check (can be "org/team" or just "team")
Returns:
List of team slugs the user is a member of
"""
start_time = time.time()
with create_span(
"github.get_user_team_slugs",
{
"github.org": org_name,
"github.username": username,
"github.installation_id": installation_id,
"github.teams_to_check": len(allowed_teams),
},
) as span:
member_teams: list[str] = []
try:
client = self._get_installation_client(installation_id)
> org = client.get_organization(org_name)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/github_client.py:796:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1175: in __call__
return self._mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1179: in _mock_call
return self._execute_mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <Mock name='Github().get_organization' id='139760117889216'>
args = ('acme',), kwargs = {}, effect = Exception('Network error')
def _execute_mock_call(self, /, *args, **kwargs):
# separate from _increment_mock_call so that awaited functions are
# executed separately from their call, also AsyncMock overrides this method
effect = self.side_effect
if effect is not None:
if _is_exception(effect):
> raise effect
E Exception: Network error
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1234: Exception
During handling of the above exception, another exception occurred:
self = <tests.test_github_client.TestGetUserTeamSlugs object at 0x7f1c747105f0>
def test_get_user_team_slugs_general_exception(self):
"""Test team membership check with general exception."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_github = Mock()
mock_github.get_organization.side_effect = Exception("Network error")
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
> result = client.get_user_team_slugs(123456, "acme", "alice", ["release-team"])
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_github_client.py:1010:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.github_client.GitHubAppClient object at 0x7f1c701219d0>
installation_id = 123456, org_name = 'acme', username = 'alice'
allowed_teams = ['release-team']
def get_user_team_slugs(
self,
installation_id: int,
org_name: str,
username: str,
allowed_teams: list[str],
) -> list[str]:
"""Get team slugs the user is a member of from the allowed teams list.
Args:
installation_id: GitHub App installation ID
org_name: Organization name
username: GitHub username to check
allowed_teams: List of team slugs to check (can be "org/team" or just "team")
Returns:
List of team slugs the user is a member of
"""
start_time = time.time()
with create_span(
"github.get_user_team_slugs",
{
"github.org": org_name,
"github.username": username,
"github.installation_id": installation_id,
"github.teams_to_check": len(allowed_teams),
},
) as span:
member_teams: list[str] = []
try:
client = self._get_installation_client(installation_id)
org = client.get_organization(org_name)
for team_ref in allowed_teams:
# Extract team slug (handle both "org/team" and "team" formats)
team_slug = team_ref.split("/")[-1] if "/" in team_ref else team_ref
try:
team = org.get_team_by_slug(team_slug)
# Check if user is a member
user = client.get_user(username)
if team.has_in_members(user): # type: ignore[arg-type]
member_teams.append(team_slug)
logger.debug(
"User %s is a member of team %s",
username,
team_slug,
extra={
"org": org_name,
"username": username,
"team": team_slug,
},
)
except GithubException as e:
# Team not found or no access - skip silently
logger.debug(
"Could not check team %s membership: %s",
team_slug,
e.data.get("message", str(e)) if hasattr(e, "data") else str(e),
extra={
"org": org_name,
"team": team_slug,
"username": username,
},
)
continue
duration = time.time() - start_time
github_api_request_duration_seconds.labels(operation="get_user_teams").observe(
duration
)
github_api_requests_total.labels(operation="get_user_teams", status="success").inc()
self._update_rate_limit_metrics(client, installation_id)
add_span_attributes(
span,
{
"github.member_teams": len(member_teams),
"github.teams_checked": len(allowed_teams),
},
)
set_span_ok(span)
return member_teams
except Exception as e:
> duration = time.time() << start_time
^^^^^^^^^^^^^^^^^^^^^^^^^
E TypeError: unsupported operand type(s) for <<: 'float' and 'float'
stampbot/github_client.py:852: TypeError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetUserTeamSlugs::test_get_user_team_slugs_general_exception - TypeError: unsupported operand type(s) for <<: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 68 passed, 3 deselected in 0.90s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_BitOr, occurrence: 0
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -168,7 +168,7 @@
retry=retry,
)
- duration = time.time() - start_time
+ duration = time.time() | start_time
github_api_request_duration_seconds.labels(operation="get_token").observe(duration)
github_api_requests_total.labels(operation="get_token", status="success").inc()
...............................................F
=================================== FAILURES ===================================
________ TestGetInstallationClient.test_get_installation_client_success ________
self = <tests.test_github_client.TestGetInstallationClient object at 0x7fdb33194050>
def test_get_installation_client_success(self):
"""Test successful installation client creation."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
> result = client._get_installation_client(123456)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_github_client.py:211:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.github_client.GitHubAppClient object at 0x7fdb2f2a0750>
installation_id = 123456
def _get_installation_client(self, installation_id: int) -> Github:
"""Get authenticated GitHub client for an installation.
Args:
installation_id: GitHub App installation ID.
Returns:
Authenticated Github client instance with timeout and retry configured.
Raises:
github.GithubException: If token exchange fails.
"""
start_time = time.time()
with create_span(
"github.get_installation_token",
{"github.installation_id": installation_id},
) as span:
try:
auth = self.integration.get_access_token(installation_id)
# Configure retry with exponential backoff
retry = Retry(
total=GITHUB_API_RETRY_TOTAL,
backoff_factor=GITHUB_API_RETRY_BACKOFF,
status_forcelist=[500, 502, 503, 504],
allowed_methods=["GET", "POST", "PUT", "DELETE", "PATCH"],
)
client = Github(
auth=Auth.Token(auth.token),
timeout=GITHUB_API_TIMEOUT,
retry=retry,
)
> duration = time.time() | start_time
^^^^^^^^^^^^^^^^^^^^^^^^
E TypeError: unsupported operand type(s) for |: 'float' and 'float'
stampbot/github_client.py:171: TypeError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetInstallationClient::test_get_installation_client_success - TypeError: unsupported operand type(s) for |: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 47 passed, 3 deselected in 0.75s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_BitOr, occurrence: 1
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -176,7 +176,7 @@
return client
except Exception as e:
- duration = time.time() - start_time
+ duration = time.time() | start_time
github_api_request_duration_seconds.labels(operation="get_token").observe(duration)
github_api_requests_total.labels(operation="get_token", status="failure").inc()
set_span_error(span, e)................................................F
=================================== FAILURES ===================================
________ TestGetInstallationClient.test_get_installation_client_failure ________
self = <stampbot.github_client.GitHubAppClient object at 0x7fe4a39c5160>
installation_id = 123456
def _get_installation_client(self, installation_id: int) -> Github:
"""Get authenticated GitHub client for an installation.
Args:
installation_id: GitHub App installation ID.
Returns:
Authenticated Github client instance with timeout and retry configured.
Raises:
github.GithubException: If token exchange fails.
"""
start_time = time.time()
with create_span(
"github.get_installation_token",
{"github.installation_id": installation_id},
) as span:
try:
> auth = self.integration.get_access_token(installation_id)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/github_client.py:155:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1175: in __call__
return self._mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1179: in _mock_call
return self._execute_mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <Mock name='GithubIntegration().get_access_token' id='140619955273808'>
args = (123456,), kwargs = {}, effect = Exception('Token exchange failed')
def _execute_mock_call(self, /, *args, **kwargs):
# separate from _increment_mock_call so that awaited functions are
# executed separately from their call, also AsyncMock overrides this method
effect = self.side_effect
if effect is not None:
if _is_exception(effect):
> raise effect
E Exception: Token exchange failed
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1234: Exception
During handling of the above exception, another exception occurred:
self = <tests.test_github_client.TestGetInstallationClient object at 0x7fe4a65cc190>
def test_get_installation_client_failure(self):
"""Test installation client creation failure."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_integration.get_access_token.side_effect = Exception("Token exchange failed")
mock_integration_cls.return_value = mock_integration
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
with pytest.raises(Exception, match="Token exchange failed"):
> client._get_installation_client(123456)
tests/test_github_client.py:240:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.github_client.GitHubAppClient object at 0x7fe4a39c5160>
installation_id = 123456
def _get_installation_client(self, installation_id: int) -> Github:
"""Get authenticated GitHub client for an installation.
Args:
installation_id: GitHub App installation ID.
Returns:
Authenticated Github client instance with timeout and retry configured.
Raises:
github.GithubException: If token exchange fails.
"""
start_time = time.time()
with create_span(
"github.get_installation_token",
{"github.installation_id": installation_id},
) as span:
try:
auth = self.integration.get_access_token(installation_id)
# Configure retry with exponential backoff
retry = Retry(
total=GITHUB_API_RETRY_TOTAL,
backoff_factor=GITHUB_API_RETRY_BACKOFF,
status_forcelist=[500, 502, 503, 504],
allowed_methods=["GET", "POST", "PUT", "DELETE", "PATCH"],
)
client = Github(
auth=Auth.Token(auth.token),
timeout=GITHUB_API_TIMEOUT,
retry=retry,
)
duration = time.time() - start_time
github_api_request_duration_seconds.labels(operation="get_token").observe(duration)
github_api_requests_total.labels(operation="get_token", status="success").inc()
set_span_ok(span)
return client
except Exception as e:
> duration = time.time() | start_time
^^^^^^^^^^^^^^^^^^^^^^^^
E TypeError: unsupported operand type(s) for |: 'float' and 'float'
stampbot/github_client.py:179: TypeError
During handling of the above exception, another exception occurred:
self = <tests.test_github_client.TestGetInstallationClient object at 0x7fe4a65cc190>
def test_get_installation_client_failure(self):
"""Test installation client creation failure."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_integration.get_access_token.side_effect = Exception("Token exchange failed")
mock_integration_cls.return_value = mock_integration
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
> with pytest.raises(Exception, match="Token exchange failed"):
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E AssertionError: Regex pattern did not match.
E Expected regex: 'Token exchange failed'
E Actual message: "unsupported operand type(s) for |: 'float' and 'float'"
tests/test_github_client.py:239: AssertionError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetInstallationClient::test_get_installation_client_failure - AssertionError: Regex pattern did not match.
Expected regex: 'Token exchange failed'
Actual message: "unsupported operand type(s) for |: 'float' and 'float'"
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 48 passed, 3 deselected in 0.86s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_BitOr, occurrence: 2
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -240,7 +240,7 @@
event="APPROVE",
)
- duration = time.time() - start_time
+ duration = time.time() | start_time
github_api_request_duration_seconds.labels(operation="approve").observe(duration)
github_api_requests_total.labels(operation="approve", status="success").inc()
...................................................F
=================================== FAILURES ===================================
____________________ TestApprovePR.test_approve_pr_success _____________________
self = <tests.test_github_client.TestApprovePR object at 0x7f9334b7c550>
def test_approve_pr_success(self):
"""Test successful PR approval."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_pr = Mock()
mock_repo = Mock()
mock_repo.get_pull.return_value = mock_pr
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
result = client.approve_pr(123456, "owner/repo", 42)
> assert result is True
E assert False is True
tests/test_github_client.py:313: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-05-16 20:08:23 [error ] Failed to approve PR #42 in owner/repo: unsupported operand type(s) for |: 'float' and 'float' extra={'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': "unsupported operand type(s) for |: 'float' and 'float'"}
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestApprovePR::test_approve_pr_success - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 51 passed, 3 deselected in 0.78s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_BitOr, occurrence: 3
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -260,7 +260,7 @@
return True
except Exception as e:
- duration = time.time() - start_time
+ duration = time.time() | start_time
github_api_request_duration_seconds.labels(operation="approve").observe(duration)
github_api_requests_total.labels(operation="approve", status="failure").inc()
....................................................F
=================================== FAILURES ===================================
____________________ TestApprovePR.test_approve_pr_failure _____________________
self = <stampbot.github_client.GitHubAppClient object at 0x7fc75976ed00>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42
comment = 'Auto-approved by Stampbot'
def approve_pr(
self,
installation_id: int,
repo_full_name: str,
pr_number: int,
comment: str = "Auto-approved by Stampbot",
) -> bool:
"""Approve a pull request.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name (owner/repo)
pr_number: Pull request number
comment: Review comment
Returns:
True if successful, False otherwise
"""
start_time = time.time()
with create_span(
"github.approve_pr",
{
"github.repo": repo_full_name,
"github.pr_number": pr_number,
"github.installation_id": installation_id,
},
) as span:
try:
client = self._get_installation_client(installation_id)
repo = client.get_repo(repo_full_name)
pr = repo.get_pull(pr_number)
# Create approval review
> pr.create_review(
body=comment,
event="APPROVE",
)
stampbot/github_client.py:238:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1175: in __call__
return self._mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1179: in _mock_call
return self._execute_mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <Mock name='Github().get_repo().get_pull().create_review' id='140494176582160'>
args = (), kwargs = {'body': 'Auto-approved by Stampbot', 'event': 'APPROVE'}
effect = Exception('API Error')
def _execute_mock_call(self, /, *args, **kwargs):
# separate from _increment_mock_call so that awaited functions are
# executed separately from their call, also AsyncMock overrides this method
effect = self.side_effect
if effect is not None:
if _is_exception(effect):
> raise effect
E Exception: API Error
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1234: Exception
During handling of the above exception, another exception occurred:
self = <tests.test_github_client.TestApprovePR object at 0x7fc75d65c690>
def test_approve_pr_failure(self):
"""Test PR approval failure."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_pr = Mock()
mock_pr.create_review.side_effect = Exception("API Error")
mock_repo = Mock()
mock_repo.get_pull.return_value = mock_pr
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
> result = client.approve_pr(123456, "owner/repo", 42)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_github_client.py:350:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.github_client.GitHubAppClient object at 0x7fc75976ed00>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42
comment = 'Auto-approved by Stampbot'
def approve_pr(
self,
installation_id: int,
repo_full_name: str,
pr_number: int,
comment: str = "Auto-approved by Stampbot",
) -> bool:
"""Approve a pull request.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name (owner/repo)
pr_number: Pull request number
comment: Review comment
Returns:
True if successful, False otherwise
"""
start_time = time.time()
with create_span(
"github.approve_pr",
{
"github.repo": repo_full_name,
"github.pr_number": pr_number,
"github.installation_id": installation_id,
},
) as span:
try:
client = self._get_installation_client(installation_id)
repo = client.get_repo(repo_full_name)
pr = repo.get_pull(pr_number)
# Create approval review
pr.create_review(
body=comment,
event="APPROVE",
)
duration = time.time() - start_time
github_api_request_duration_seconds.labels(operation="approve").observe(duration)
github_api_requests_total.labels(operation="approve", status="success").inc()
self._update_rate_limit_metrics(client, installation_id)
add_span_attributes(span, {"github.result": "approved"})
set_span_ok(span)
logger.info(
f"Approved PR #{pr_number} in {repo_full_name}",
extra={
"repo": repo_full_name,
"pr_number": pr_number,
"installation_id": installation_id,
},
)
return True
except Exception as e:
> duration = time.time() | start_time
^^^^^^^^^^^^^^^^^^^^^^^^
E TypeError: unsupported operand type(s) for |: 'float' and 'float'
stampbot/github_client.py:263: TypeError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestApprovePR::test_approve_pr_failure - TypeError: unsupported operand type(s) for |: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 52 passed, 3 deselected in 0.86s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_BitOr, occurrence: 4
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -317,7 +317,7 @@
review = pr.get_review(review_id)
review.dismiss(message)
- duration = time.time() - start_time
+ duration = time.time() | start_time
github_api_request_duration_seconds.labels(operation="dismiss").observe(duration)
github_api_requests_total.labels(operation="dismiss", status="success").inc()
.....................................................F
=================================== FAILURES ===================================
______________ TestDismissApproval.test_dismiss_approval_success _______________
self = <tests.test_github_client.TestDismissApproval object at 0x7f6e4269c7d0>
def test_dismiss_approval_success(self):
"""Test successful approval dismissal."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_review = Mock()
mock_pr = Mock()
mock_pr.get_review.return_value = mock_review
mock_repo = Mock()
mock_repo.get_pull.return_value = mock_pr
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
result = client.dismiss_approval(123456, "owner/repo", 42, 999)
> assert result is True
E assert False is True
tests/test_github_client.py:396: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-05-16 19:33:51 [error ] Failed to dismiss approval for PR #42 in owner/repo: unsupported operand type(s) for |: 'float' and 'float' extra={'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': "unsupported operand type(s) for |: 'float' and 'float'"}
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestDismissApproval::test_dismiss_approval_success - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 53 passed, 3 deselected in 0.74s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_BitOr, occurrence: 5
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -338,7 +338,7 @@
return True
except Exception as e:
- duration = time.time() - start_time
+ duration = time.time() | start_time
github_api_request_duration_seconds.labels(operation="dismiss").observe(duration)
github_api_requests_total.labels(operation="dismiss", status="failure").inc()
......................................................F
=================================== FAILURES ===================================
______________ TestDismissApproval.test_dismiss_approval_failure _______________
self = <stampbot.github_client.GitHubAppClient object at 0x7fd551f228f0>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42
review_id = 999, message = 'Approval dismissed by Stampbot'
def dismiss_approval(
self,
installation_id: int,
repo_full_name: str,
pr_number: int,
review_id: int,
message: str = "Approval dismissed by Stampbot",
) -> bool:
"""Dismiss a pull request approval.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name (owner/repo)
pr_number: Pull request number
review_id: Review ID to dismiss
message: Dismissal message
Returns:
True if successful, False otherwise
"""
start_time = time.time()
with create_span(
"github.dismiss_approval",
{
"github.repo": repo_full_name,
"github.pr_number": pr_number,
"github.review_id": review_id,
"github.installation_id": installation_id,
},
) as span:
try:
client = self._get_installation_client(installation_id)
repo = client.get_repo(repo_full_name)
pr = repo.get_pull(pr_number)
# Get the review and dismiss it
review = pr.get_review(review_id)
> review.dismiss(message)
stampbot/github_client.py:318:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1175: in __call__
return self._mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1179: in _mock_call
return self._execute_mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <Mock name='Github().get_repo().get_pull().get_review().dismiss' id='140554178442448'>
args = ('Approval dismissed by Stampbot',), kwargs = {}
effect = Exception('API Error')
def _execute_mock_call(self, /, *args, **kwargs):
# separate from _increment_mock_call so that awaited functions are
# executed separately from their call, also AsyncMock overrides this method
effect = self.side_effect
if effect is not None:
if _is_exception(effect):
> raise effect
E Exception: API Error
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1234: Exception
During handling of the above exception, another exception occurred:
self = <tests.test_github_client.TestDismissApproval object at 0x7fd556364910>
def test_dismiss_approval_failure(self):
"""Test approval dismissal failure."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_review = Mock()
mock_review.dismiss.side_effect = Exception("API Error")
mock_pr = Mock()
mock_pr.get_review.return_value = mock_review
mock_repo = Mock()
mock_repo.get_pull.return_value = mock_pr
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
> result = client.dismiss_approval(123456, "owner/repo", 42, 999)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_github_client.py:435:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.github_client.GitHubAppClient object at 0x7fd551f228f0>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42
review_id = 999, message = 'Approval dismissed by Stampbot'
def dismiss_approval(
self,
installation_id: int,
repo_full_name: str,
pr_number: int,
review_id: int,
message: str = "Approval dismissed by Stampbot",
) -> bool:
"""Dismiss a pull request approval.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name (owner/repo)
pr_number: Pull request number
review_id: Review ID to dismiss
message: Dismissal message
Returns:
True if successful, False otherwise
"""
start_time = time.time()
with create_span(
"github.dismiss_approval",
{
"github.repo": repo_full_name,
"github.pr_number": pr_number,
"github.review_id": review_id,
"github.installation_id": installation_id,
},
) as span:
try:
client = self._get_installation_client(installation_id)
repo = client.get_repo(repo_full_name)
pr = repo.get_pull(pr_number)
# Get the review and dismiss it
review = pr.get_review(review_id)
review.dismiss(message)
duration = time.time() - start_time
github_api_request_duration_seconds.labels(operation="dismiss").observe(duration)
github_api_requests_total.labels(operation="dismiss", status="success").inc()
self._update_rate_limit_metrics(client, installation_id)
add_span_attributes(span, {"github.result": "dismissed"})
set_span_ok(span)
logger.info(
f"Dismissed approval for PR #{pr_number} in {repo_full_name}",
extra={
"repo": repo_full_name,
"pr_number": pr_number,
"installation_id": installation_id,
"review_id": review_id,
},
)
return True
except Exception as e:
> duration = time.time() | start_time
^^^^^^^^^^^^^^^^^^^^^^^^
E TypeError: unsupported operand type(s) for |: 'float' and 'float'
stampbot/github_client.py:341: TypeError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestDismissApproval::test_dismiss_approval_failure - TypeError: unsupported operand type(s) for |: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 54 passed, 3 deselected in 0.86s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_BitOr, occurrence: 6
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -393,7 +393,7 @@
content = repo.get_contents(file_path, ref=ref) # type: ignore[arg-type]
if isinstance(content, list):
- duration = time.time() - start_time
+ duration = time.time() | start_time
github_api_request_duration_seconds.labels(operation="get_file").observe(
duration
)........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.65s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_BitOr, occurrence: 7
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -402,7 +402,7 @@
set_span_ok(span)
return None
- duration = time.time() - start_time
+ duration = time.time() | start_time
github_api_request_duration_seconds.labels(operation="get_file").observe(duration)
github_api_requests_total.labels(operation="get_file", status="success").inc()
.......................................................F
=================================== FAILURES ===================================
__________________ TestGetRepoFile.test_get_repo_file_success __________________
self = <tests.test_github_client.TestGetRepoFile object at 0x7f1f9632ca50>
def test_get_repo_file_success(self):
"""Test successful file retrieval."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_content = Mock()
mock_content.decoded_content = b"file content"
mock_repo = Mock()
mock_repo.get_contents.return_value = mock_content
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
result = client.get_repo_file(123456, "owner/repo", "stampbot.toml")
> assert result == "file content"
E AssertionError: assert None == 'file content'
tests/test_github_client.py:480: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-05-16 19:50:35 [debug ] Could not fetch stampbot.toml from owner/repo: unsupported operand type(s) for |: 'float' and 'float' extra={'repo': 'owner/repo', 'file_path': 'stampbot.toml', 'installation_id': 123456}
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetRepoFile::test_get_repo_file_success - AssertionError: assert None == 'file content'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 55 passed, 3 deselected in 0.75s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_BitOr, occurrence: 8
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -414,7 +414,7 @@
return content.decoded_content.decode("utf-8")
except Exception as e:
- duration = time.time() - start_time
+ duration = time.time() | start_time
github_api_request_duration_seconds.labels(operation="get_file").observe(duration)
github_api_requests_total.labels(operation="get_file", status="not_found").inc()
.........................................................F
=================================== FAILURES ===================================
___________ TestGetRepoFile.test_get_repo_file_returns_none_on_error ___________
self = <stampbot.github_client.GitHubAppClient object at 0x7f3a72c2ccd0>
installation_id = 123456, repo_full_name = 'owner/repo'
file_path = 'nonexistent.toml', ref = None
def get_repo_file(
self,
installation_id: int,
repo_full_name: str,
file_path: str,
ref: str | None = None,
) -> str | None:
"""Get file content from repository.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name (owner/repo)
file_path: Path to file in repository
ref: Git reference (branch, tag, commit). Defaults to default branch
Returns:
File content as string, or None if not found
"""
start_time = time.time()
with create_span(
"github.get_file",
{
"github.repo": repo_full_name,
"github.file_path": file_path,
"github.ref": ref or "default",
"github.installation_id": installation_id,
},
) as span:
try:
client = self._get_installation_client(installation_id)
repo = client.get_repo(repo_full_name)
> content = repo.get_contents(file_path, ref=ref) # type: ignore[arg-type]
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/github_client.py:394:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1175: in __call__
return self._mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1179: in _mock_call
return self._execute_mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <Mock name='Github().get_repo().get_contents' id='139889010525920'>
args = ('nonexistent.toml',), kwargs = {'ref': None}
effect = Exception('File not found')
def _execute_mock_call(self, /, *args, **kwargs):
# separate from _increment_mock_call so that awaited functions are
# executed separately from their call, also AsyncMock overrides this method
effect = self.side_effect
if effect is not None:
if _is_exception(effect):
> raise effect
E Exception: File not found
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1234: Exception
During handling of the above exception, another exception occurred:
self = <tests.test_github_client.TestGetRepoFile object at 0x7f3a76f8d940>
def test_get_repo_file_returns_none_on_error(self):
"""Test that get_repo_file returns None when file not found."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_repo = Mock()
mock_repo.get_contents.side_effect = Exception("File not found")
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
> result = client.get_repo_file(123456, "owner/repo", "nonexistent.toml")
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_github_client.py:551:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.github_client.GitHubAppClient object at 0x7f3a72c2ccd0>
installation_id = 123456, repo_full_name = 'owner/repo'
file_path = 'nonexistent.toml', ref = None
def get_repo_file(
self,
installation_id: int,
repo_full_name: str,
file_path: str,
ref: str | None = None,
) -> str | None:
"""Get file content from repository.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name (owner/repo)
file_path: Path to file in repository
ref: Git reference (branch, tag, commit). Defaults to default branch
Returns:
File content as string, or None if not found
"""
start_time = time.time()
with create_span(
"github.get_file",
{
"github.repo": repo_full_name,
"github.file_path": file_path,
"github.ref": ref or "default",
"github.installation_id": installation_id,
},
) as span:
try:
client = self._get_installation_client(installation_id)
repo = client.get_repo(repo_full_name)
content = repo.get_contents(file_path, ref=ref) # type: ignore[arg-type]
if isinstance(content, list):
duration = time.time() - start_time
github_api_request_duration_seconds.labels(operation="get_file").observe(
duration
)
github_api_requests_total.labels(operation="get_file", status="not_found").inc()
add_span_attributes(span, {"github.result": "not_found"})
set_span_ok(span)
return None
duration = time.time() - start_time
github_api_request_duration_seconds.labels(operation="get_file").observe(duration)
github_api_requests_total.labels(operation="get_file", status="success").inc()
self._update_rate_limit_metrics(client, installation_id)
add_span_attributes(span, {"github.result": "found"})
set_span_ok(span)
return content.decoded_content.decode("utf-8")
except Exception as e:
> duration = time.time() | start_time
^^^^^^^^^^^^^^^^^^^^^^^^
E TypeError: unsupported operand type(s) for |: 'float' and 'float'
stampbot/github_client.py:417: TypeError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetRepoFile::test_get_repo_file_returns_none_on_error - TypeError: unsupported operand type(s) for |: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 57 passed, 3 deselected in 0.87s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_BitOr, occurrence: 9
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -473,7 +473,7 @@
if review.user.login == bot_user and review.state == "APPROVED":
bot_review_ids.append(review.id)
- duration = time.time() - start_time
+ duration = time.time() | start_time
github_api_request_duration_seconds.labels(operation="find_reviews").observe(
duration
)..........................................................F
=================================== FAILURES ===================================
_______________ TestFindBotReviews.test_find_bot_reviews_success _______________
self = <tests.test_github_client.TestFindBotReviews object at 0x7fdaa74e8cd0>
def test_find_bot_reviews_success(self):
"""Test successful bot review finding."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration.get_app.return_value = Mock(slug="stampbot")
mock_integration_cls.return_value = mock_integration
# Create mock reviews
bot_review = Mock()
bot_review.user.login = "stampbot[bot]"
bot_review.state = "APPROVED"
bot_review.id = 123
other_review = Mock()
other_review.user.login = "other-user"
other_review.state = "APPROVED"
other_review.id = 456
mock_pr = Mock()
mock_pr.get_reviews.return_value = [bot_review, other_review]
mock_repo = Mock()
mock_repo.get_pull.return_value = mock_pr
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
result = client.find_bot_reviews(123456, "owner/repo", 42)
> assert result == [123]
E assert [] == [123]
E
E Right contains one more item: 123
E
E Full diff:
E + []
E - [
E - 123,
E - ]
tests/test_github_client.py:608: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-05-16 19:36:26 [error ] Failed to find bot reviews for PR #42 in owner/repo: unsupported operand type(s) for |: 'float' and 'float' extra={'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': "unsupported operand type(s) for |: 'float' and 'float'"}
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestFindBotReviews::test_find_bot_reviews_success - assert [] == [123]
Right contains one more item: 123
Full diff:
+ []
- [
- 123,
- ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 58 passed, 3 deselected in 0.76s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_BitOr, occurrence: 10
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -489,7 +489,7 @@
return bot_review_ids
except Exception as e:
- duration = time.time() - start_time
+ duration = time.time() | start_time
github_api_request_duration_seconds.labels(operation="find_reviews").observe(
duration
)...........................................................F
=================================== FAILURES ===================================
_______ TestFindBotReviews.test_find_bot_reviews_returns_empty_on_error ________
self = <stampbot.github_client.GitHubAppClient object at 0x7fc2a256cf30>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42
def find_bot_reviews(
self,
installation_id: int,
repo_full_name: str,
pr_number: int,
) -> list[int]:
"""Find all reviews created by this bot on a PR.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name (owner/repo)
pr_number: Pull request number
Returns:
List of review IDs created by the bot
"""
start_time = time.time()
with create_span(
"github.find_bot_reviews",
{
"github.repo": repo_full_name,
"github.pr_number": pr_number,
"github.installation_id": installation_id,
},
) as span:
try:
client = self._get_installation_client(installation_id)
> repo = client.get_repo(repo_full_name)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/github_client.py:462:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1175: in __call__
return self._mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1179: in _mock_call
return self._execute_mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <Mock name='Github().get_repo' id='140473924453104'>
args = ('owner/repo',), kwargs = {}, effect = Exception('API Error')
def _execute_mock_call(self, /, *args, **kwargs):
# separate from _increment_mock_call so that awaited functions are
# executed separately from their call, also AsyncMock overrides this method
effect = self.side_effect
if effect is not None:
if _is_exception(effect):
> raise effect
E Exception: API Error
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1234: Exception
During handling of the above exception, another exception occurred:
self = <tests.test_github_client.TestFindBotReviews object at 0x7fc2a6590e10>
def test_find_bot_reviews_returns_empty_on_error(self):
"""Test that find_bot_reviews returns empty list on error."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_github = Mock()
mock_github.get_repo.side_effect = Exception("API Error")
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
> result = client.find_bot_reviews(123456, "owner/repo", 42)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_github_client.py:640:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.github_client.GitHubAppClient object at 0x7fc2a256cf30>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42
def find_bot_reviews(
self,
installation_id: int,
repo_full_name: str,
pr_number: int,
) -> list[int]:
"""Find all reviews created by this bot on a PR.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name (owner/repo)
pr_number: Pull request number
Returns:
List of review IDs created by the bot
"""
start_time = time.time()
with create_span(
"github.find_bot_reviews",
{
"github.repo": repo_full_name,
"github.pr_number": pr_number,
"github.installation_id": installation_id,
},
) as span:
try:
client = self._get_installation_client(installation_id)
repo = client.get_repo(repo_full_name)
pr = repo.get_pull(pr_number)
# Get bot user via JWT-authenticated integration (installation tokens
# cannot call GET /user, which is restricted by GitHub Apps integration)
app_info = self.integration.get_app()
bot_user = f"{app_info.slug}[bot]"
# Find all reviews by bot that are approvals
bot_review_ids = []
for review in pr.get_reviews():
if review.user.login == bot_user and review.state == "APPROVED":
bot_review_ids.append(review.id)
duration = time.time() - start_time
github_api_request_duration_seconds.labels(operation="find_reviews").observe(
duration
)
github_api_requests_total.labels(operation="find_reviews", status="success").inc()
self._update_rate_limit_metrics(client, installation_id)
add_span_attributes(
span, {"github.reviews_found": len(bot_review_ids), "github.bot_user": bot_user}
)
set_span_ok(span)
return bot_review_ids
except Exception as e:
> duration = time.time() | start_time
^^^^^^^^^^^^^^^^^^^^^^^^
E TypeError: unsupported operand type(s) for |: 'float' and 'float'
stampbot/github_client.py:492: TypeError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestFindBotReviews::test_find_bot_reviews_returns_empty_on_error - TypeError: unsupported operand type(s) for |: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 59 passed, 3 deselected in 0.87s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_BitOr, occurrence: 11
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -546,7 +546,7 @@
pr.create_review(body=message, event="COMMENT")
- duration = time.time() - start_time
+ duration = time.time() | start_time
github_api_request_duration_seconds.labels(operation="comment").observe(duration)
github_api_requests_total.labels(operation="comment", status="success").inc()
........................................................................ [ 34%]
.F
=================================== FAILURES ===================================
_______ TestCreatePrReviewComment.test_create_pr_review_comment_success ________
self = <tests.test_github_client.TestCreatePrReviewComment object at 0x7f4d141696d0>
def test_create_pr_review_comment_success(self):
"""Test successful review comment creation."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_pr = Mock()
mock_repo = Mock()
mock_repo.get_pull.return_value = mock_pr
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=1, limit=2))
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
result = client.create_pr_review_comment(
123456,
"owner/repo",
42,
"Config error",
)
> assert result is True
E assert False is True
tests/test_github_client.py:1213: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-05-16 19:54:09 [warning ] Failed to post config error review for PR #42 in owner/repo: unsupported operand type(s) for |: 'float' and 'float' extra={'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': "unsupported operand type(s) for |: 'float' and 'float'"}
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestCreatePrReviewComment::test_create_pr_review_comment_success - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 73 passed, 3 deselected in 0.83s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_BitOr, occurrence: 12
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -568,7 +568,7 @@
return True
except Exception as e:
- duration = time.time() - start_time
+ duration = time.time() | start_time
github_api_request_duration_seconds.labels(operation="comment").observe(duration)
github_api_requests_total.labels(operation="comment", status="failure").inc()
........................................................................ [ 34%]
..F
=================================== FAILURES ===================================
_______ TestCreatePrReviewComment.test_create_pr_review_comment_failure ________
self = <stampbot.github_client.GitHubAppClient object at 0x7f9145fc65d0>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42
message = 'Config error'
def create_pr_review_comment(
self,
installation_id: int,
repo_full_name: str,
pr_number: int,
message: str,
) -> bool:
"""Create a comment review on a pull request.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name (owner/repo)
pr_number: Pull request number
message: Review comment body
Returns:
True if successful, False otherwise
"""
start_time = time.time()
with create_span(
"github.create_review_comment",
{
"github.repo": repo_full_name,
"github.pr_number": pr_number,
"github.installation_id": installation_id,
},
) as span:
try:
client = self._get_installation_client(installation_id)
repo = client.get_repo(repo_full_name)
> pr = repo.get_pull(pr_number)
^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/github_client.py:545:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1175: in __call__
return self._mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1179: in _mock_call
return self._execute_mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <Mock name='Github().get_repo().get_pull' id='140261921082400'>
args = (42,), kwargs = {}, effect = Exception('API Error')
def _execute_mock_call(self, /, *args, **kwargs):
# separate from _increment_mock_call so that awaited functions are
# executed separately from their call, also AsyncMock overrides this method
effect = self.side_effect
if effect is not None:
if _is_exception(effect):
> raise effect
E Exception: API Error
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1234: Exception
During handling of the above exception, another exception occurred:
self = <tests.test_github_client.TestCreatePrReviewComment object at 0x7f914a189810>
def test_create_pr_review_comment_failure(self):
"""Test review comment creation handles errors."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_repo = Mock()
mock_repo.get_pull.side_effect = Exception("API Error")
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
> result = client.create_pr_review_comment(
123456,
"owner/repo",
42,
"Config error",
)
tests/test_github_client.py:1248:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.github_client.GitHubAppClient object at 0x7f9145fc65d0>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42
message = 'Config error'
def create_pr_review_comment(
self,
installation_id: int,
repo_full_name: str,
pr_number: int,
message: str,
) -> bool:
"""Create a comment review on a pull request.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name (owner/repo)
pr_number: Pull request number
message: Review comment body
Returns:
True if successful, False otherwise
"""
start_time = time.time()
with create_span(
"github.create_review_comment",
{
"github.repo": repo_full_name,
"github.pr_number": pr_number,
"github.installation_id": installation_id,
},
) as span:
try:
client = self._get_installation_client(installation_id)
repo = client.get_repo(repo_full_name)
pr = repo.get_pull(pr_number)
pr.create_review(body=message, event="COMMENT")
duration = time.time() - start_time
github_api_request_duration_seconds.labels(operation="comment").observe(duration)
github_api_requests_total.labels(operation="comment", status="success").inc()
self._update_rate_limit_metrics(client, installation_id)
add_span_attributes(span, {"github.result": "commented"})
set_span_ok(span)
logger.info(
"Posted config error review on PR #%d in %s",
pr_number,
repo_full_name,
extra={
"repo": repo_full_name,
"pr_number": pr_number,
"installation_id": installation_id,
},
)
return True
except Exception as e:
> duration = time.time() | start_time
^^^^^^^^^^^^^^^^^^^^^^^^
E TypeError: unsupported operand type(s) for |: 'float' and 'float'
stampbot/github_client.py:571: TypeError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestCreatePrReviewComment::test_create_pr_review_comment_failure - TypeError: unsupported operand type(s) for |: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 74 passed, 3 deselected in 0.91s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_BitOr, occurrence: 13
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -619,7 +619,7 @@
repo = client.get_repo(repo_full_name)
repo.get_label(label_name)
- duration = time.time() - start_time
+ duration = time.time() | start_time
github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
github_api_requests_total.labels(operation="get_label", status="success").inc()
.....................................................................F
=================================== FAILURES ===================================
__________________ TestRepoHasLabel.test_repo_has_label_true ___________________
self = <tests.test_github_client.TestRepoHasLabel object at 0x7f0abf16d450>
def test_repo_has_label_true(self):
"""Test repo_has_label returns True when label exists."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_repo = Mock()
mock_repo.get_label.return_value = Mock()
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=1, limit=2))
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
result = client.repo_has_label(123456, "owner/repo", "autoapprove")
> assert result is True
E assert None is True
tests/test_github_client.py:1054: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-05-16 19:31:49 [debug ] Could not verify label autoapprove in owner/repo: unsupported operand type(s) for |: 'float' and 'float' extra={'repo': 'owner/repo', 'label': 'autoapprove', 'installation_id': 123456, 'error': "unsupported operand type(s) for |: 'float' and 'float'"}
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestRepoHasLabel::test_repo_has_label_true - assert None is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 69 passed, 3 deselected in 0.77s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_BitOr, occurrence: 14
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -630,7 +630,7 @@
return True
except GithubException as e:
- duration = time.time() - start_time
+ duration = time.time() | start_time
github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
github_api_requests_total.labels(operation="get_label", status="failure").inc()
......................................................................F
=================================== FAILURES ===================================
________________ TestRepoHasLabel.test_repo_has_label_not_found ________________
self = <stampbot.github_client.GitHubAppClient object at 0x7fcb0f049730>
installation_id = 123456, repo_full_name = 'owner/repo', label_name = 'missing'
def repo_has_label(
self,
installation_id: int,
repo_full_name: str,
label_name: str,
) -> bool | None:
"""Check whether a repository has a label.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name (owner/repo)
label_name: Label name to look up
Returns:
True if label exists, False if not found, None if unknown due to error
"""
start_time = time.time()
with create_span(
"github.get_label",
{
"github.repo": repo_full_name,
"github.label": label_name,
"github.installation_id": installation_id,
},
) as span:
try:
client = self._get_installation_client(installation_id)
repo = client.get_repo(repo_full_name)
> repo.get_label(label_name)
stampbot/github_client.py:620:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1175: in __call__
return self._mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1179: in _mock_call
return self._execute_mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <Mock name='Github().get_repo().get_label' id='140510106872064'>
args = ('missing',), kwargs = {}
effect = GithubException(404 {"message": "Not Found"})
def _execute_mock_call(self, /, *args, **kwargs):
# separate from _increment_mock_call so that awaited functions are
# executed separately from their call, also AsyncMock overrides this method
effect = self.side_effect
if effect is not None:
if _is_exception(effect):
> raise effect
E github.GithubException.GithubException: 404 {"message": "Not Found"}
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1234: GithubException
During handling of the above exception, another exception occurred:
self = <tests.test_github_client.TestRepoHasLabel object at 0x7fcb13761590>
def test_repo_has_label_not_found(self):
"""Test repo_has_label returns False when label is missing."""
from github.GithubException import GithubException
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_repo = Mock()
mock_repo.get_label.side_effect = GithubException(404, {"message": "Not Found"}, None)
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
> result = client.repo_has_label(123456, "owner/repo", "missing")
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_github_client.py:1090:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.github_client.GitHubAppClient object at 0x7fcb0f049730>
installation_id = 123456, repo_full_name = 'owner/repo', label_name = 'missing'
def repo_has_label(
self,
installation_id: int,
repo_full_name: str,
label_name: str,
) -> bool | None:
"""Check whether a repository has a label.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name (owner/repo)
label_name: Label name to look up
Returns:
True if label exists, False if not found, None if unknown due to error
"""
start_time = time.time()
with create_span(
"github.get_label",
{
"github.repo": repo_full_name,
"github.label": label_name,
"github.installation_id": installation_id,
},
) as span:
try:
client = self._get_installation_client(installation_id)
repo = client.get_repo(repo_full_name)
repo.get_label(label_name)
duration = time.time() - start_time
github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
github_api_requests_total.labels(operation="get_label", status="success").inc()
self._update_rate_limit_metrics(client, installation_id)
add_span_attributes(span, {"github.label_found": True})
set_span_ok(span)
return True
except GithubException as e:
> duration = time.time() | start_time
^^^^^^^^^^^^^^^^^^^^^^^^
E TypeError: unsupported operand type(s) for |: 'float' and 'float'
stampbot/github_client.py:633: TypeError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestRepoHasLabel::test_repo_has_label_not_found - TypeError: unsupported operand type(s) for |: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 70 passed, 3 deselected in 0.91s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_BitOr, occurrence: 15
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -655,7 +655,7 @@
return None
except Exception as e:
- duration = time.time() - start_time
+ duration = time.time() | start_time
github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
github_api_requests_total.labels(operation="get_label", status="failure").inc()
........................................................................ [ 34%]
F
=================================== FAILURES ===================================
__________________ TestRepoHasLabel.test_repo_has_label_error __________________
self = <stampbot.github_client.GitHubAppClient object at 0x7f67e7486810>
installation_id = 123456, repo_full_name = 'owner/repo'
label_name = 'autoapprove'
def repo_has_label(
self,
installation_id: int,
repo_full_name: str,
label_name: str,
) -> bool | None:
"""Check whether a repository has a label.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name (owner/repo)
label_name: Label name to look up
Returns:
True if label exists, False if not found, None if unknown due to error
"""
start_time = time.time()
with create_span(
"github.get_label",
{
"github.repo": repo_full_name,
"github.label": label_name,
"github.installation_id": installation_id,
},
) as span:
try:
client = self._get_installation_client(installation_id)
repo = client.get_repo(repo_full_name)
> repo.get_label(label_name)
stampbot/github_client.py:620:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1175: in __call__
return self._mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1179: in _mock_call
return self._execute_mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <Mock name='Github().get_repo().get_label' id='140084238538400'>
args = ('autoapprove',), kwargs = {}, effect = Exception('API Error')
def _execute_mock_call(self, /, *args, **kwargs):
# separate from _increment_mock_call so that awaited functions are
# executed separately from their call, also AsyncMock overrides this method
effect = self.side_effect
if effect is not None:
if _is_exception(effect):
> raise effect
E Exception: API Error
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1234: Exception
During handling of the above exception, another exception occurred:
self = <tests.test_github_client.TestRepoHasLabel object at 0x7f67eba02060>
def test_repo_has_label_error(self):
"""Test repo_has_label returns None on error."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_repo = Mock()
mock_repo.get_label.side_effect = Exception("API Error")
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
> result = client.repo_has_label(123456, "owner/repo", "autoapprove")
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_github_client.py:1164:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.github_client.GitHubAppClient object at 0x7f67e7486810>
installation_id = 123456, repo_full_name = 'owner/repo'
label_name = 'autoapprove'
def repo_has_label(
self,
installation_id: int,
repo_full_name: str,
label_name: str,
) -> bool | None:
"""Check whether a repository has a label.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name (owner/repo)
label_name: Label name to look up
Returns:
True if label exists, False if not found, None if unknown due to error
"""
start_time = time.time()
with create_span(
"github.get_label",
{
"github.repo": repo_full_name,
"github.label": label_name,
"github.installation_id": installation_id,
},
) as span:
try:
client = self._get_installation_client(installation_id)
repo = client.get_repo(repo_full_name)
repo.get_label(label_name)
duration = time.time() - start_time
github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
github_api_requests_total.labels(operation="get_label", status="success").inc()
self._update_rate_limit_metrics(client, installation_id)
add_span_attributes(span, {"github.label_found": True})
set_span_ok(span)
return True
except GithubException as e:
duration = time.time() - start_time
github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
github_api_requests_total.labels(operation="get_label", status="failure").inc()
if e.status == 404:
add_span_attributes(span, {"github.label_found": False})
set_span_ok(span)
return False
set_span_error(span, e)
logger.debug(
"Could not verify label %s in %s: %s",
label_name,
repo_full_name,
_sanitize_error(e),
extra={
"repo": repo_full_name,
"label": label_name,
"installation_id": installation_id,
"error": _sanitize_error(e),
},
)
return None
except Exception as e:
> duration = time.time() | start_time
^^^^^^^^^^^^^^^^^^^^^^^^
E TypeError: unsupported operand type(s) for |: 'float' and 'float'
stampbot/github_client.py:658: TypeError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestRepoHasLabel::test_repo_has_label_error - TypeError: unsupported operand type(s) for |: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 72 passed, 3 deselected in 0.90s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_BitOr, occurrence: 16
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -718,7 +718,7 @@
has_permission = permission_index >= required_index
- duration = time.time() - start_time
+ duration = time.time() | start_time
github_api_request_duration_seconds.labels(operation="get_permission").observe(
duration
)............................................................F
=================================== FAILURES ===================================
_____________ TestUserHasPermission.test_user_has_permission_true ______________
self = <tests.test_github_client.TestUserHasPermission object at 0x7f53b4cf0f50>
def test_user_has_permission_true(self):
"""Test required permission satisfied returns True."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_repo = Mock()
mock_repo.get_collaborator_permission.return_value = "write"
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=1, limit=2))
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
result = client.user_has_permission(123456, "owner/repo", "alice", "write")
> assert result is True
E assert False is True
tests/test_github_client.py:683: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-05-16 20:04:25 [warning ] Failed to check collaborator permission for alice in owner/repo: unsupported operand type(s) for |: 'float' and 'float' extra={'repo': 'owner/repo', 'username': 'alice', 'installation_id': 123456, 'error': "unsupported operand type(s) for |: 'float' and 'float'"}
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestUserHasPermission::test_user_has_permission_true - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 60 passed, 3 deselected in 0.75s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_BitOr, occurrence: 17
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -738,7 +738,7 @@
return has_permission
except Exception as e:
- duration = time.time() - start_time
+ duration = time.time() | start_time
github_api_request_duration_seconds.labels(operation="get_permission").observe(
duration
)..............................................................F
=================================== FAILURES ===================================
_____________ TestUserHasPermission.test_user_has_permission_error _____________
self = <stampbot.github_client.GitHubAppClient object at 0x7fcbb6b47e30>
installation_id = 123456, repo_full_name = 'owner/repo', username = 'carol'
required_permission = 'maintain'
def user_has_permission(
self,
installation_id: int,
repo_full_name: str,
username: str,
required_permission: str,
) -> bool:
"""Check whether a user has required access to a repository.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name (owner/repo)
username: GitHub username to check
required_permission: Minimum required repository permission
Returns:
True if user meets or exceeds required permission, False otherwise
"""
start_time = time.time()
with create_span(
"github.get_collaborator_permission",
{
"github.repo": repo_full_name,
"github.username": username,
"github.required_permission": required_permission,
"github.installation_id": installation_id,
},
) as span:
try:
client = self._get_installation_client(installation_id)
repo = client.get_repo(repo_full_name)
> permission = repo.get_collaborator_permission(username)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/github_client.py:710:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1175: in __call__
return self._mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1179: in _mock_call
return self._execute_mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <Mock name='Github().get_repo().get_collaborator_permission' id='140512920325152'>
args = ('carol',), kwargs = {}, effect = Exception('API Error')
def _execute_mock_call(self, /, *args, **kwargs):
# separate from _increment_mock_call so that awaited functions are
# executed separately from their call, also AsyncMock overrides this method
effect = self.side_effect
if effect is not None:
if _is_exception(effect):
> raise effect
E Exception: API Error
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1234: Exception
During handling of the above exception, another exception occurred:
self = <tests.test_github_client.TestUserHasPermission object at 0x7fcbbb019a70>
def test_user_has_permission_error(self):
"""Test permission check returns False on error."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_repo = Mock()
mock_repo.get_collaborator_permission.side_effect = Exception("API Error")
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
> result = client.user_has_permission(123456, "owner/repo", "carol", "maintain")
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_github_client.py:754:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.github_client.GitHubAppClient object at 0x7fcbb6b47e30>
installation_id = 123456, repo_full_name = 'owner/repo', username = 'carol'
required_permission = 'maintain'
def user_has_permission(
self,
installation_id: int,
repo_full_name: str,
username: str,
required_permission: str,
) -> bool:
"""Check whether a user has required access to a repository.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name (owner/repo)
username: GitHub username to check
required_permission: Minimum required repository permission
Returns:
True if user meets or exceeds required permission, False otherwise
"""
start_time = time.time()
with create_span(
"github.get_collaborator_permission",
{
"github.repo": repo_full_name,
"github.username": username,
"github.required_permission": required_permission,
"github.installation_id": installation_id,
},
) as span:
try:
client = self._get_installation_client(installation_id)
repo = client.get_repo(repo_full_name)
permission = repo.get_collaborator_permission(username)
permission_order = ["none", "read", "triage", "write", "maintain", "admin"]
try:
permission_index = permission_order.index(permission)
required_index = permission_order.index(required_permission)
except ValueError:
permission_index = -1
required_index = 99
has_permission = permission_index >= required_index
duration = time.time() - start_time
github_api_request_duration_seconds.labels(operation="get_permission").observe(
duration
)
github_api_requests_total.labels(operation="get_permission", status="success").inc()
self._update_rate_limit_metrics(client, installation_id)
add_span_attributes(
span,
{
"github.permission": permission,
"github.has_permission": has_permission,
},
)
set_span_ok(span)
return has_permission
except Exception as e:
> duration = time.time() | start_time
^^^^^^^^^^^^^^^^^^^^^^^^
E TypeError: unsupported operand type(s) for |: 'float' and 'float'
stampbot/github_client.py:741: TypeError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestUserHasPermission::test_user_has_permission_error - TypeError: unsupported operand type(s) for |: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 62 passed, 3 deselected in 0.91s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_BitOr, occurrence: 18
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -829,7 +829,7 @@
)
continue
- duration = time.time() - start_time
+ duration = time.time() | start_time
github_api_request_duration_seconds.labels(operation="get_user_teams").observe(
duration
)................................................................F
=================================== FAILURES ===================================
____________ TestGetUserTeamSlugs.test_get_user_team_slugs_success _____________
self = <tests.test_github_client.TestGetUserTeamSlugs object at 0x7f930d28d1d0>
def test_get_user_team_slugs_success(self):
"""Test successful team membership check."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
# Mock user
mock_user = Mock()
mock_user.login = "alice"
# Mock team that has alice as member
mock_team = Mock()
mock_team.has_in_members.return_value = True
# Mock org
mock_org = Mock()
mock_org.get_team_by_slug.return_value = mock_team
mock_github = Mock()
mock_github.get_organization.return_value = mock_org
mock_github.get_user.return_value = mock_user
mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
result = client.get_user_team_slugs(
123456, "acme", "alice", ["release-team", "deploy-team"]
)
> assert result == ["release-team", "deploy-team"]
E AssertionError: assert [] == ['release-tea...'deploy-team']
E
E Right contains 2 more items, first extra item: 'release-team'
E
E Full diff:
E + []
E - [
E - 'release-team',
E - 'deploy-team',
E - ]
tests/test_github_client.py:848: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-05-16 20:01:03 [debug ] User alice is a member of team release-team extra={'org': 'acme', 'username': 'alice', 'team': 'release-team'}
2026-05-16 20:01:03 [debug ] User alice is a member of team deploy-team extra={'org': 'acme', 'username': 'alice', 'team': 'deploy-team'}
2026-05-16 20:01:03 [warning ] Failed to check team memberships for alice in acme: unsupported operand type(s) for |: 'float' and 'float' extra={'org': 'acme', 'username': 'alice', 'installation_id': 123456, 'error': "unsupported operand type(s) for |: 'float' and 'float'"}
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetUserTeamSlugs::test_get_user_team_slugs_success - AssertionError: assert [] == ['release-tea...'deploy-team']
Right contains 2 more items, first extra item: 'release-team'
Full diff:
+ []
- [
- 'release-team',
- 'deploy-team',
- ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 64 passed, 3 deselected in 0.76s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_BitOr, occurrence: 19
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -849,7 +849,7 @@
return member_teams
except Exception as e:
- duration = time.time() - start_time
+ duration = time.time() | start_time
github_api_request_duration_seconds.labels(operation="get_user_teams").observe(
duration
)....................................................................F
=================================== FAILURES ===================================
_______ TestGetUserTeamSlugs.test_get_user_team_slugs_general_exception ________
self = <stampbot.github_client.GitHubAppClient object at 0x7fa10df15b50>
installation_id = 123456, org_name = 'acme', username = 'alice'
allowed_teams = ['release-team']
def get_user_team_slugs(
self,
installation_id: int,
org_name: str,
username: str,
allowed_teams: list[str],
) -> list[str]:
"""Get team slugs the user is a member of from the allowed teams list.
Args:
installation_id: GitHub App installation ID
org_name: Organization name
username: GitHub username to check
allowed_teams: List of team slugs to check (can be "org/team" or just "team")
Returns:
List of team slugs the user is a member of
"""
start_time = time.time()
with create_span(
"github.get_user_team_slugs",
{
"github.org": org_name,
"github.username": username,
"github.installation_id": installation_id,
"github.teams_to_check": len(allowed_teams),
},
) as span:
member_teams: list[str] = []
try:
client = self._get_installation_client(installation_id)
> org = client.get_organization(org_name)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/github_client.py:796:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1175: in __call__
return self._mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1179: in _mock_call
return self._execute_mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <Mock name='Github().get_organization' id='140329702209728'>
args = ('acme',), kwargs = {}, effect = Exception('Network error')
def _execute_mock_call(self, /, *args, **kwargs):
# separate from _increment_mock_call so that awaited functions are
# executed separately from their call, also AsyncMock overrides this method
effect = self.side_effect
if effect is not None:
if _is_exception(effect):
> raise effect
E Exception: Network error
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1234: Exception
During handling of the above exception, another exception occurred:
self = <tests.test_github_client.TestGetUserTeamSlugs object at 0x7fa1123445f0>
def test_get_user_team_slugs_general_exception(self):
"""Test team membership check with general exception."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_github = Mock()
mock_github.get_organization.side_effect = Exception("Network error")
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
> result = client.get_user_team_slugs(123456, "acme", "alice", ["release-team"])
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_github_client.py:1010:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.github_client.GitHubAppClient object at 0x7fa10df15b50>
installation_id = 123456, org_name = 'acme', username = 'alice'
allowed_teams = ['release-team']
def get_user_team_slugs(
self,
installation_id: int,
org_name: str,
username: str,
allowed_teams: list[str],
) -> list[str]:
"""Get team slugs the user is a member of from the allowed teams list.
Args:
installation_id: GitHub App installation ID
org_name: Organization name
username: GitHub username to check
allowed_teams: List of team slugs to check (can be "org/team" or just "team")
Returns:
List of team slugs the user is a member of
"""
start_time = time.time()
with create_span(
"github.get_user_team_slugs",
{
"github.org": org_name,
"github.username": username,
"github.installation_id": installation_id,
"github.teams_to_check": len(allowed_teams),
},
) as span:
member_teams: list[str] = []
try:
client = self._get_installation_client(installation_id)
org = client.get_organization(org_name)
for team_ref in allowed_teams:
# Extract team slug (handle both "org/team" and "team" formats)
team_slug = team_ref.split("/")[-1] if "/" in team_ref else team_ref
try:
team = org.get_team_by_slug(team_slug)
# Check if user is a member
user = client.get_user(username)
if team.has_in_members(user): # type: ignore[arg-type]
member_teams.append(team_slug)
logger.debug(
"User %s is a member of team %s",
username,
team_slug,
extra={
"org": org_name,
"username": username,
"team": team_slug,
},
)
except GithubException as e:
# Team not found or no access - skip silently
logger.debug(
"Could not check team %s membership: %s",
team_slug,
e.data.get("message", str(e)) if hasattr(e, "data") else str(e),
extra={
"org": org_name,
"team": team_slug,
"username": username,
},
)
continue
duration = time.time() - start_time
github_api_request_duration_seconds.labels(operation="get_user_teams").observe(
duration
)
github_api_requests_total.labels(operation="get_user_teams", status="success").inc()
self._update_rate_limit_metrics(client, installation_id)
add_span_attributes(
span,
{
"github.member_teams": len(member_teams),
"github.teams_checked": len(allowed_teams),
},
)
set_span_ok(span)
return member_teams
except Exception as e:
> duration = time.time() | start_time
^^^^^^^^^^^^^^^^^^^^^^^^
E TypeError: unsupported operand type(s) for |: 'float' and 'float'
stampbot/github_client.py:852: TypeError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetUserTeamSlugs::test_get_user_team_slugs_general_exception - TypeError: unsupported operand type(s) for |: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 68 passed, 3 deselected in 0.90s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_BitAnd, occurrence: 0
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -168,7 +168,7 @@
retry=retry,
)
- duration = time.time() - start_time
+ duration = time.time() & start_time
github_api_request_duration_seconds.labels(operation="get_token").observe(duration)
github_api_requests_total.labels(operation="get_token", status="success").inc()
...............................................F
=================================== FAILURES ===================================
________ TestGetInstallationClient.test_get_installation_client_success ________
self = <tests.test_github_client.TestGetInstallationClient object at 0x7ff8c4114050>
def test_get_installation_client_success(self):
"""Test successful installation client creation."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
> result = client._get_installation_client(123456)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_github_client.py:211:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.github_client.GitHubAppClient object at 0x7ff8c0188750>
installation_id = 123456
def _get_installation_client(self, installation_id: int) -> Github:
"""Get authenticated GitHub client for an installation.
Args:
installation_id: GitHub App installation ID.
Returns:
Authenticated Github client instance with timeout and retry configured.
Raises:
github.GithubException: If token exchange fails.
"""
start_time = time.time()
with create_span(
"github.get_installation_token",
{"github.installation_id": installation_id},
) as span:
try:
auth = self.integration.get_access_token(installation_id)
# Configure retry with exponential backoff
retry = Retry(
total=GITHUB_API_RETRY_TOTAL,
backoff_factor=GITHUB_API_RETRY_BACKOFF,
status_forcelist=[500, 502, 503, 504],
allowed_methods=["GET", "POST", "PUT", "DELETE", "PATCH"],
)
client = Github(
auth=Auth.Token(auth.token),
timeout=GITHUB_API_TIMEOUT,
retry=retry,
)
> duration = time.time() & start_time
^^^^^^^^^^^^^^^^^^^^^^^^
E TypeError: unsupported operand type(s) for &: 'float' and 'float'
stampbot/github_client.py:171: TypeError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetInstallationClient::test_get_installation_client_success - TypeError: unsupported operand type(s) for &: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 47 passed, 3 deselected in 0.78s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_BitAnd, occurrence: 1
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -176,7 +176,7 @@
return client
except Exception as e:
- duration = time.time() - start_time
+ duration = time.time() & start_time
github_api_request_duration_seconds.labels(operation="get_token").observe(duration)
github_api_requests_total.labels(operation="get_token", status="failure").inc()
set_span_error(span, e)................................................F
=================================== FAILURES ===================================
________ TestGetInstallationClient.test_get_installation_client_failure ________
self = <stampbot.github_client.GitHubAppClient object at 0x7f62348d5160>
installation_id = 123456
def _get_installation_client(self, installation_id: int) -> Github:
"""Get authenticated GitHub client for an installation.
Args:
installation_id: GitHub App installation ID.
Returns:
Authenticated Github client instance with timeout and retry configured.
Raises:
github.GithubException: If token exchange fails.
"""
start_time = time.time()
with create_span(
"github.get_installation_token",
{"github.installation_id": installation_id},
) as span:
try:
> auth = self.integration.get_access_token(installation_id)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/github_client.py:155:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1175: in __call__
return self._mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1179: in _mock_call
return self._execute_mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <Mock name='GithubIntegration().get_access_token' id='140059746238544'>
args = (123456,), kwargs = {}, effect = Exception('Token exchange failed')
def _execute_mock_call(self, /, *args, **kwargs):
# separate from _increment_mock_call so that awaited functions are
# executed separately from their call, also AsyncMock overrides this method
effect = self.side_effect
if effect is not None:
if _is_exception(effect):
> raise effect
E Exception: Token exchange failed
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1234: Exception
During handling of the above exception, another exception occurred:
self = <tests.test_github_client.TestGetInstallationClient object at 0x7f6237554190>
def test_get_installation_client_failure(self):
"""Test installation client creation failure."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_integration.get_access_token.side_effect = Exception("Token exchange failed")
mock_integration_cls.return_value = mock_integration
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
with pytest.raises(Exception, match="Token exchange failed"):
> client._get_installation_client(123456)
tests/test_github_client.py:240:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.github_client.GitHubAppClient object at 0x7f62348d5160>
installation_id = 123456
def _get_installation_client(self, installation_id: int) -> Github:
"""Get authenticated GitHub client for an installation.
Args:
installation_id: GitHub App installation ID.
Returns:
Authenticated Github client instance with timeout and retry configured.
Raises:
github.GithubException: If token exchange fails.
"""
start_time = time.time()
with create_span(
"github.get_installation_token",
{"github.installation_id": installation_id},
) as span:
try:
auth = self.integration.get_access_token(installation_id)
# Configure retry with exponential backoff
retry = Retry(
total=GITHUB_API_RETRY_TOTAL,
backoff_factor=GITHUB_API_RETRY_BACKOFF,
status_forcelist=[500, 502, 503, 504],
allowed_methods=["GET", "POST", "PUT", "DELETE", "PATCH"],
)
client = Github(
auth=Auth.Token(auth.token),
timeout=GITHUB_API_TIMEOUT,
retry=retry,
)
duration = time.time() - start_time
github_api_request_duration_seconds.labels(operation="get_token").observe(duration)
github_api_requests_total.labels(operation="get_token", status="success").inc()
set_span_ok(span)
return client
except Exception as e:
> duration = time.time() & start_time
^^^^^^^^^^^^^^^^^^^^^^^^
E TypeError: unsupported operand type(s) for &: 'float' and 'float'
stampbot/github_client.py:179: TypeError
During handling of the above exception, another exception occurred:
self = <tests.test_github_client.TestGetInstallationClient object at 0x7f6237554190>
def test_get_installation_client_failure(self):
"""Test installation client creation failure."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_integration.get_access_token.side_effect = Exception("Token exchange failed")
mock_integration_cls.return_value = mock_integration
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
> with pytest.raises(Exception, match="Token exchange failed"):
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E AssertionError: Regex pattern did not match.
E Expected regex: 'Token exchange failed'
E Actual message: "unsupported operand type(s) for &: 'float' and 'float'"
tests/test_github_client.py:239: AssertionError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetInstallationClient::test_get_installation_client_failure - AssertionError: Regex pattern did not match.
Expected regex: 'Token exchange failed'
Actual message: "unsupported operand type(s) for &: 'float' and 'float'"
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 48 passed, 3 deselected in 0.86s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_BitAnd, occurrence: 2
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -240,7 +240,7 @@
event="APPROVE",
)
- duration = time.time() - start_time
+ duration = time.time() & start_time
github_api_request_duration_seconds.labels(operation="approve").observe(duration)
github_api_requests_total.labels(operation="approve", status="success").inc()
...................................................F
=================================== FAILURES ===================================
____________________ TestApprovePR.test_approve_pr_success _____________________
self = <tests.test_github_client.TestApprovePR object at 0x7f28c42d4550>
def test_approve_pr_success(self):
"""Test successful PR approval."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_pr = Mock()
mock_repo = Mock()
mock_repo.get_pull.return_value = mock_pr
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
result = client.approve_pr(123456, "owner/repo", 42)
> assert result is True
E assert False is True
tests/test_github_client.py:313: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-05-16 20:08:19 [error ] Failed to approve PR #42 in owner/repo: unsupported operand type(s) for &: 'float' and 'float' extra={'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': "unsupported operand type(s) for &: 'float' and 'float'"}
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestApprovePR::test_approve_pr_success - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 51 passed, 3 deselected in 0.78s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_BitAnd, occurrence: 3
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -260,7 +260,7 @@
return True
except Exception as e:
- duration = time.time() - start_time
+ duration = time.time() & start_time
github_api_request_duration_seconds.labels(operation="approve").observe(duration)
github_api_requests_total.labels(operation="approve", status="failure").inc()
....................................................F
=================================== FAILURES ===================================
____________________ TestApprovePR.test_approve_pr_failure _____________________
self = <stampbot.github_client.GitHubAppClient object at 0x7efbfb466d00>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42
comment = 'Auto-approved by Stampbot'
def approve_pr(
self,
installation_id: int,
repo_full_name: str,
pr_number: int,
comment: str = "Auto-approved by Stampbot",
) -> bool:
"""Approve a pull request.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name (owner/repo)
pr_number: Pull request number
comment: Review comment
Returns:
True if successful, False otherwise
"""
start_time = time.time()
with create_span(
"github.approve_pr",
{
"github.repo": repo_full_name,
"github.pr_number": pr_number,
"github.installation_id": installation_id,
},
) as span:
try:
client = self._get_installation_client(installation_id)
repo = client.get_repo(repo_full_name)
pr = repo.get_pull(pr_number)
# Create approval review
> pr.create_review(
body=comment,
event="APPROVE",
)
stampbot/github_client.py:238:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1175: in __call__
return self._mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1179: in _mock_call
return self._execute_mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <Mock name='Github().get_repo().get_pull().create_review' id='139620718000656'>
args = (), kwargs = {'body': 'Auto-approved by Stampbot', 'event': 'APPROVE'}
effect = Exception('API Error')
def _execute_mock_call(self, /, *args, **kwargs):
# separate from _increment_mock_call so that awaited functions are
# executed separately from their call, also AsyncMock overrides this method
effect = self.side_effect
if effect is not None:
if _is_exception(effect):
> raise effect
E Exception: API Error
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1234: Exception
During handling of the above exception, another exception occurred:
self = <tests.test_github_client.TestApprovePR object at 0x7efbff33c690>
def test_approve_pr_failure(self):
"""Test PR approval failure."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_pr = Mock()
mock_pr.create_review.side_effect = Exception("API Error")
mock_repo = Mock()
mock_repo.get_pull.return_value = mock_pr
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
> result = client.approve_pr(123456, "owner/repo", 42)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_github_client.py:350:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.github_client.GitHubAppClient object at 0x7efbfb466d00>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42
comment = 'Auto-approved by Stampbot'
def approve_pr(
self,
installation_id: int,
repo_full_name: str,
pr_number: int,
comment: str = "Auto-approved by Stampbot",
) -> bool:
"""Approve a pull request.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name (owner/repo)
pr_number: Pull request number
comment: Review comment
Returns:
True if successful, False otherwise
"""
start_time = time.time()
with create_span(
"github.approve_pr",
{
"github.repo": repo_full_name,
"github.pr_number": pr_number,
"github.installation_id": installation_id,
},
) as span:
try:
client = self._get_installation_client(installation_id)
repo = client.get_repo(repo_full_name)
pr = repo.get_pull(pr_number)
# Create approval review
pr.create_review(
body=comment,
event="APPROVE",
)
duration = time.time() - start_time
github_api_request_duration_seconds.labels(operation="approve").observe(duration)
github_api_requests_total.labels(operation="approve", status="success").inc()
self._update_rate_limit_metrics(client, installation_id)
add_span_attributes(span, {"github.result": "approved"})
set_span_ok(span)
logger.info(
f"Approved PR #{pr_number} in {repo_full_name}",
extra={
"repo": repo_full_name,
"pr_number": pr_number,
"installation_id": installation_id,
},
)
return True
except Exception as e:
> duration = time.time() & start_time
^^^^^^^^^^^^^^^^^^^^^^^^
E TypeError: unsupported operand type(s) for &: 'float' and 'float'
stampbot/github_client.py:263: TypeError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestApprovePR::test_approve_pr_failure - TypeError: unsupported operand type(s) for &: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 52 passed, 3 deselected in 0.85s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_BitAnd, occurrence: 4
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -317,7 +317,7 @@
review = pr.get_review(review_id)
review.dismiss(message)
- duration = time.time() - start_time
+ duration = time.time() & start_time
github_api_request_duration_seconds.labels(operation="dismiss").observe(duration)
github_api_requests_total.labels(operation="dismiss", status="success").inc()
.....................................................F
=================================== FAILURES ===================================
______________ TestDismissApproval.test_dismiss_approval_success _______________
self = <tests.test_github_client.TestDismissApproval object at 0x7f46940747d0>
def test_dismiss_approval_success(self):
"""Test successful approval dismissal."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_review = Mock()
mock_pr = Mock()
mock_pr.get_review.return_value = mock_review
mock_repo = Mock()
mock_repo.get_pull.return_value = mock_pr
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
result = client.dismiss_approval(123456, "owner/repo", 42, 999)
> assert result is True
E assert False is True
tests/test_github_client.py:396: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-05-16 19:33:11 [error ] Failed to dismiss approval for PR #42 in owner/repo: unsupported operand type(s) for &: 'float' and 'float' extra={'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': "unsupported operand type(s) for &: 'float' and 'float'"}
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestDismissApproval::test_dismiss_approval_success - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 53 passed, 3 deselected in 0.74s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_BitAnd, occurrence: 5
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -338,7 +338,7 @@
return True
except Exception as e:
- duration = time.time() - start_time
+ duration = time.time() & start_time
github_api_request_duration_seconds.labels(operation="dismiss").observe(duration)
github_api_requests_total.labels(operation="dismiss", status="failure").inc()
......................................................F
=================================== FAILURES ===================================
______________ TestDismissApproval.test_dismiss_approval_failure _______________
self = <stampbot.github_client.GitHubAppClient object at 0x7f899dc168f0>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42
review_id = 999, message = 'Approval dismissed by Stampbot'
def dismiss_approval(
self,
installation_id: int,
repo_full_name: str,
pr_number: int,
review_id: int,
message: str = "Approval dismissed by Stampbot",
) -> bool:
"""Dismiss a pull request approval.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name (owner/repo)
pr_number: Pull request number
review_id: Review ID to dismiss
message: Dismissal message
Returns:
True if successful, False otherwise
"""
start_time = time.time()
with create_span(
"github.dismiss_approval",
{
"github.repo": repo_full_name,
"github.pr_number": pr_number,
"github.review_id": review_id,
"github.installation_id": installation_id,
},
) as span:
try:
client = self._get_installation_client(installation_id)
repo = client.get_repo(repo_full_name)
pr = repo.get_pull(pr_number)
# Get the review and dismiss it
review = pr.get_review(review_id)
> review.dismiss(message)
stampbot/github_client.py:318:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1175: in __call__
return self._mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1179: in _mock_call
return self._execute_mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <Mock name='Github().get_repo().get_pull().get_review().dismiss' id='140229032817872'>
args = ('Approval dismissed by Stampbot',), kwargs = {}
effect = Exception('API Error')
def _execute_mock_call(self, /, *args, **kwargs):
# separate from _increment_mock_call so that awaited functions are
# executed separately from their call, also AsyncMock overrides this method
effect = self.side_effect
if effect is not None:
if _is_exception(effect):
> raise effect
E Exception: API Error
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1234: Exception
During handling of the above exception, another exception occurred:
self = <tests.test_github_client.TestDismissApproval object at 0x7f89a1cc4910>
def test_dismiss_approval_failure(self):
"""Test approval dismissal failure."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_review = Mock()
mock_review.dismiss.side_effect = Exception("API Error")
mock_pr = Mock()
mock_pr.get_review.return_value = mock_review
mock_repo = Mock()
mock_repo.get_pull.return_value = mock_pr
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
> result = client.dismiss_approval(123456, "owner/repo", 42, 999)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_github_client.py:435:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.github_client.GitHubAppClient object at 0x7f899dc168f0>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42
review_id = 999, message = 'Approval dismissed by Stampbot'
def dismiss_approval(
self,
installation_id: int,
repo_full_name: str,
pr_number: int,
review_id: int,
message: str = "Approval dismissed by Stampbot",
) -> bool:
"""Dismiss a pull request approval.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name (owner/repo)
pr_number: Pull request number
review_id: Review ID to dismiss
message: Dismissal message
Returns:
True if successful, False otherwise
"""
start_time = time.time()
with create_span(
"github.dismiss_approval",
{
"github.repo": repo_full_name,
"github.pr_number": pr_number,
"github.review_id": review_id,
"github.installation_id": installation_id,
},
) as span:
try:
client = self._get_installation_client(installation_id)
repo = client.get_repo(repo_full_name)
pr = repo.get_pull(pr_number)
# Get the review and dismiss it
review = pr.get_review(review_id)
review.dismiss(message)
duration = time.time() - start_time
github_api_request_duration_seconds.labels(operation="dismiss").observe(duration)
github_api_requests_total.labels(operation="dismiss", status="success").inc()
self._update_rate_limit_metrics(client, installation_id)
add_span_attributes(span, {"github.result": "dismissed"})
set_span_ok(span)
logger.info(
f"Dismissed approval for PR #{pr_number} in {repo_full_name}",
extra={
"repo": repo_full_name,
"pr_number": pr_number,
"installation_id": installation_id,
"review_id": review_id,
},
)
return True
except Exception as e:
> duration = time.time() & start_time
^^^^^^^^^^^^^^^^^^^^^^^^
E TypeError: unsupported operand type(s) for &: 'float' and 'float'
stampbot/github_client.py:341: TypeError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestDismissApproval::test_dismiss_approval_failure - TypeError: unsupported operand type(s) for &: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 54 passed, 3 deselected in 0.87s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_BitAnd, occurrence: 6
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -393,7 +393,7 @@
content = repo.get_contents(file_path, ref=ref) # type: ignore[arg-type]
if isinstance(content, list):
- duration = time.time() - start_time
+ duration = time.time() & start_time
github_api_request_duration_seconds.labels(operation="get_file").observe(
duration
)........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.63s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_BitAnd, occurrence: 7
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -402,7 +402,7 @@
set_span_ok(span)
return None
- duration = time.time() - start_time
+ duration = time.time() & start_time
github_api_request_duration_seconds.labels(operation="get_file").observe(duration)
github_api_requests_total.labels(operation="get_file", status="success").inc()
.......................................................F
=================================== FAILURES ===================================
__________________ TestGetRepoFile.test_get_repo_file_success __________________
self = <tests.test_github_client.TestGetRepoFile object at 0x7f8a65824a50>
def test_get_repo_file_success(self):
"""Test successful file retrieval."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_content = Mock()
mock_content.decoded_content = b"file content"
mock_repo = Mock()
mock_repo.get_contents.return_value = mock_content
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
result = client.get_repo_file(123456, "owner/repo", "stampbot.toml")
> assert result == "file content"
E AssertionError: assert None == 'file content'
tests/test_github_client.py:480: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-05-16 19:58:43 [debug ] Could not fetch stampbot.toml from owner/repo: unsupported operand type(s) for &: 'float' and 'float' extra={'repo': 'owner/repo', 'file_path': 'stampbot.toml', 'installation_id': 123456}
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetRepoFile::test_get_repo_file_success - AssertionError: assert None == 'file content'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 55 passed, 3 deselected in 0.75s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_BitAnd, occurrence: 8
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -414,7 +414,7 @@
return content.decoded_content.decode("utf-8")
except Exception as e:
- duration = time.time() - start_time
+ duration = time.time() & start_time
github_api_request_duration_seconds.labels(operation="get_file").observe(duration)
github_api_requests_total.labels(operation="get_file", status="not_found").inc()
.........................................................F
=================================== FAILURES ===================================
___________ TestGetRepoFile.test_get_repo_file_returns_none_on_error ___________
self = <stampbot.github_client.GitHubAppClient object at 0x7f3952c68cd0>
installation_id = 123456, repo_full_name = 'owner/repo'
file_path = 'nonexistent.toml', ref = None
def get_repo_file(
self,
installation_id: int,
repo_full_name: str,
file_path: str,
ref: str | None = None,
) -> str | None:
"""Get file content from repository.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name (owner/repo)
file_path: Path to file in repository
ref: Git reference (branch, tag, commit). Defaults to default branch
Returns:
File content as string, or None if not found
"""
start_time = time.time()
with create_span(
"github.get_file",
{
"github.repo": repo_full_name,
"github.file_path": file_path,
"github.ref": ref or "default",
"github.installation_id": installation_id,
},
) as span:
try:
client = self._get_installation_client(installation_id)
repo = client.get_repo(repo_full_name)
> content = repo.get_contents(file_path, ref=ref) # type: ignore[arg-type]
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/github_client.py:394:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1175: in __call__
return self._mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1179: in _mock_call
return self._execute_mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <Mock name='Github().get_repo().get_contents' id='139884178917088'>
args = ('nonexistent.toml',), kwargs = {'ref': None}
effect = Exception('File not found')
def _execute_mock_call(self, /, *args, **kwargs):
# separate from _increment_mock_call so that awaited functions are
# executed separately from their call, also AsyncMock overrides this method
effect = self.side_effect
if effect is not None:
if _is_exception(effect):
> raise effect
E Exception: File not found
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1234: Exception
During handling of the above exception, another exception occurred:
self = <tests.test_github_client.TestGetRepoFile object at 0x7f3956fe9940>
def test_get_repo_file_returns_none_on_error(self):
"""Test that get_repo_file returns None when file not found."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_repo = Mock()
mock_repo.get_contents.side_effect = Exception("File not found")
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
> result = client.get_repo_file(123456, "owner/repo", "nonexistent.toml")
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_github_client.py:551:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.github_client.GitHubAppClient object at 0x7f3952c68cd0>
installation_id = 123456, repo_full_name = 'owner/repo'
file_path = 'nonexistent.toml', ref = None
def get_repo_file(
self,
installation_id: int,
repo_full_name: str,
file_path: str,
ref: str | None = None,
) -> str | None:
"""Get file content from repository.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name (owner/repo)
file_path: Path to file in repository
ref: Git reference (branch, tag, commit). Defaults to default branch
Returns:
File content as string, or None if not found
"""
start_time = time.time()
with create_span(
"github.get_file",
{
"github.repo": repo_full_name,
"github.file_path": file_path,
"github.ref": ref or "default",
"github.installation_id": installation_id,
},
) as span:
try:
client = self._get_installation_client(installation_id)
repo = client.get_repo(repo_full_name)
content = repo.get_contents(file_path, ref=ref) # type: ignore[arg-type]
if isinstance(content, list):
duration = time.time() - start_time
github_api_request_duration_seconds.labels(operation="get_file").observe(
duration
)
github_api_requests_total.labels(operation="get_file", status="not_found").inc()
add_span_attributes(span, {"github.result": "not_found"})
set_span_ok(span)
return None
duration = time.time() - start_time
github_api_request_duration_seconds.labels(operation="get_file").observe(duration)
github_api_requests_total.labels(operation="get_file", status="success").inc()
self._update_rate_limit_metrics(client, installation_id)
add_span_attributes(span, {"github.result": "found"})
set_span_ok(span)
return content.decoded_content.decode("utf-8")
except Exception as e:
> duration = time.time() & start_time
^^^^^^^^^^^^^^^^^^^^^^^^
E TypeError: unsupported operand type(s) for &: 'float' and 'float'
stampbot/github_client.py:417: TypeError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetRepoFile::test_get_repo_file_returns_none_on_error - TypeError: unsupported operand type(s) for &: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 57 passed, 3 deselected in 0.86s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_BitAnd, occurrence: 9
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -473,7 +473,7 @@
if review.user.login == bot_user and review.state == "APPROVED":
bot_review_ids.append(review.id)
- duration = time.time() - start_time
+ duration = time.time() & start_time
github_api_request_duration_seconds.labels(operation="find_reviews").observe(
duration
)..........................................................F
=================================== FAILURES ===================================
_______________ TestFindBotReviews.test_find_bot_reviews_success _______________
self = <tests.test_github_client.TestFindBotReviews object at 0x7fae1dd14cd0>
def test_find_bot_reviews_success(self):
"""Test successful bot review finding."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration.get_app.return_value = Mock(slug="stampbot")
mock_integration_cls.return_value = mock_integration
# Create mock reviews
bot_review = Mock()
bot_review.user.login = "stampbot[bot]"
bot_review.state = "APPROVED"
bot_review.id = 123
other_review = Mock()
other_review.user.login = "other-user"
other_review.state = "APPROVED"
other_review.id = 456
mock_pr = Mock()
mock_pr.get_reviews.return_value = [bot_review, other_review]
mock_repo = Mock()
mock_repo.get_pull.return_value = mock_pr
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
result = client.find_bot_reviews(123456, "owner/repo", 42)
> assert result == [123]
E assert [] == [123]
E
E Right contains one more item: 123
E
E Full diff:
E + []
E - [
E - 123,
E - ]
tests/test_github_client.py:608: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-05-16 19:57:38 [error ] Failed to find bot reviews for PR #42 in owner/repo: unsupported operand type(s) for &: 'float' and 'float' extra={'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': "unsupported operand type(s) for &: 'float' and 'float'"}
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestFindBotReviews::test_find_bot_reviews_success - assert [] == [123]
Right contains one more item: 123
Full diff:
+ []
- [
- 123,
- ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 58 passed, 3 deselected in 0.75s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_BitAnd, occurrence: 10
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -489,7 +489,7 @@
return bot_review_ids
except Exception as e:
- duration = time.time() - start_time
+ duration = time.time() & start_time
github_api_request_duration_seconds.labels(operation="find_reviews").observe(
duration
)...........................................................F
=================================== FAILURES ===================================
_______ TestFindBotReviews.test_find_bot_reviews_returns_empty_on_error ________
self = <stampbot.github_client.GitHubAppClient object at 0x7f827dd64f30>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42
def find_bot_reviews(
self,
installation_id: int,
repo_full_name: str,
pr_number: int,
) -> list[int]:
"""Find all reviews created by this bot on a PR.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name (owner/repo)
pr_number: Pull request number
Returns:
List of review IDs created by the bot
"""
start_time = time.time()
with create_span(
"github.find_bot_reviews",
{
"github.repo": repo_full_name,
"github.pr_number": pr_number,
"github.installation_id": installation_id,
},
) as span:
try:
client = self._get_installation_client(installation_id)
> repo = client.get_repo(repo_full_name)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/github_client.py:462:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1175: in __call__
return self._mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1179: in _mock_call
return self._execute_mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <Mock name='Github().get_repo' id='140198434161392'>
args = ('owner/repo',), kwargs = {}, effect = Exception('API Error')
def _execute_mock_call(self, /, *args, **kwargs):
# separate from _increment_mock_call so that awaited functions are
# executed separately from their call, also AsyncMock overrides this method
effect = self.side_effect
if effect is not None:
if _is_exception(effect):
> raise effect
E Exception: API Error
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1234: Exception
During handling of the above exception, another exception occurred:
self = <tests.test_github_client.TestFindBotReviews object at 0x7f8281d8ce10>
def test_find_bot_reviews_returns_empty_on_error(self):
"""Test that find_bot_reviews returns empty list on error."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_github = Mock()
mock_github.get_repo.side_effect = Exception("API Error")
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
> result = client.find_bot_reviews(123456, "owner/repo", 42)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_github_client.py:640:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.github_client.GitHubAppClient object at 0x7f827dd64f30>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42
def find_bot_reviews(
self,
installation_id: int,
repo_full_name: str,
pr_number: int,
) -> list[int]:
"""Find all reviews created by this bot on a PR.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name (owner/repo)
pr_number: Pull request number
Returns:
List of review IDs created by the bot
"""
start_time = time.time()
with create_span(
"github.find_bot_reviews",
{
"github.repo": repo_full_name,
"github.pr_number": pr_number,
"github.installation_id": installation_id,
},
) as span:
try:
client = self._get_installation_client(installation_id)
repo = client.get_repo(repo_full_name)
pr = repo.get_pull(pr_number)
# Get bot user via JWT-authenticated integration (installation tokens
# cannot call GET /user, which is restricted by GitHub Apps integration)
app_info = self.integration.get_app()
bot_user = f"{app_info.slug}[bot]"
# Find all reviews by bot that are approvals
bot_review_ids = []
for review in pr.get_reviews():
if review.user.login == bot_user and review.state == "APPROVED":
bot_review_ids.append(review.id)
duration = time.time() - start_time
github_api_request_duration_seconds.labels(operation="find_reviews").observe(
duration
)
github_api_requests_total.labels(operation="find_reviews", status="success").inc()
self._update_rate_limit_metrics(client, installation_id)
add_span_attributes(
span, {"github.reviews_found": len(bot_review_ids), "github.bot_user": bot_user}
)
set_span_ok(span)
return bot_review_ids
except Exception as e:
> duration = time.time() & start_time
^^^^^^^^^^^^^^^^^^^^^^^^
E TypeError: unsupported operand type(s) for &: 'float' and 'float'
stampbot/github_client.py:492: TypeError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestFindBotReviews::test_find_bot_reviews_returns_empty_on_error - TypeError: unsupported operand type(s) for &: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 59 passed, 3 deselected in 0.87s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_BitAnd, occurrence: 11
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -546,7 +546,7 @@
pr.create_review(body=message, event="COMMENT")
- duration = time.time() - start_time
+ duration = time.time() & start_time
github_api_request_duration_seconds.labels(operation="comment").observe(duration)
github_api_requests_total.labels(operation="comment", status="success").inc()
........................................................................ [ 34%]
.F
=================================== FAILURES ===================================
_______ TestCreatePrReviewComment.test_create_pr_review_comment_success ________
self = <tests.test_github_client.TestCreatePrReviewComment object at 0x7f6db92d56d0>
def test_create_pr_review_comment_success(self):
"""Test successful review comment creation."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_pr = Mock()
mock_repo = Mock()
mock_repo.get_pull.return_value = mock_pr
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=1, limit=2))
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
result = client.create_pr_review_comment(
123456,
"owner/repo",
42,
"Config error",
)
> assert result is True
E assert False is True
tests/test_github_client.py:1213: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-05-16 19:46:44 [warning ] Failed to post config error review for PR #42 in owner/repo: unsupported operand type(s) for &: 'float' and 'float' extra={'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': "unsupported operand type(s) for &: 'float' and 'float'"}
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestCreatePrReviewComment::test_create_pr_review_comment_success - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 73 passed, 3 deselected in 0.81s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_BitAnd, occurrence: 12
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -568,7 +568,7 @@
return True
except Exception as e:
- duration = time.time() - start_time
+ duration = time.time() & start_time
github_api_request_duration_seconds.labels(operation="comment").observe(duration)
github_api_requests_total.labels(operation="comment", status="failure").inc()
........................................................................ [ 34%]
..F
=================================== FAILURES ===================================
_______ TestCreatePrReviewComment.test_create_pr_review_comment_failure ________
self = <stampbot.github_client.GitHubAppClient object at 0x7fb28bebdd90>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42
message = 'Config error'
def create_pr_review_comment(
self,
installation_id: int,
repo_full_name: str,
pr_number: int,
message: str,
) -> bool:
"""Create a comment review on a pull request.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name (owner/repo)
pr_number: Pull request number
message: Review comment body
Returns:
True if successful, False otherwise
"""
start_time = time.time()
with create_span(
"github.create_review_comment",
{
"github.repo": repo_full_name,
"github.pr_number": pr_number,
"github.installation_id": installation_id,
},
) as span:
try:
client = self._get_installation_client(installation_id)
repo = client.get_repo(repo_full_name)
> pr = repo.get_pull(pr_number)
^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/github_client.py:545:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1175: in __call__
return self._mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1179: in _mock_call
return self._execute_mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <Mock name='Github().get_repo().get_pull' id='140404828326944'>
args = (42,), kwargs = {}, effect = Exception('API Error')
def _execute_mock_call(self, /, *args, **kwargs):
# separate from _increment_mock_call so that awaited functions are
# executed separately from their call, also AsyncMock overrides this method
effect = self.side_effect
if effect is not None:
if _is_exception(effect):
> raise effect
E Exception: API Error
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1234: Exception
During handling of the above exception, another exception occurred:
self = <tests.test_github_client.TestCreatePrReviewComment object at 0x7fb290425810>
def test_create_pr_review_comment_failure(self):
"""Test review comment creation handles errors."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_repo = Mock()
mock_repo.get_pull.side_effect = Exception("API Error")
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
> result = client.create_pr_review_comment(
123456,
"owner/repo",
42,
"Config error",
)
tests/test_github_client.py:1248:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.github_client.GitHubAppClient object at 0x7fb28bebdd90>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42
message = 'Config error'
def create_pr_review_comment(
self,
installation_id: int,
repo_full_name: str,
pr_number: int,
message: str,
) -> bool:
"""Create a comment review on a pull request.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name (owner/repo)
pr_number: Pull request number
message: Review comment body
Returns:
True if successful, False otherwise
"""
start_time = time.time()
with create_span(
"github.create_review_comment",
{
"github.repo": repo_full_name,
"github.pr_number": pr_number,
"github.installation_id": installation_id,
},
) as span:
try:
client = self._get_installation_client(installation_id)
repo = client.get_repo(repo_full_name)
pr = repo.get_pull(pr_number)
pr.create_review(body=message, event="COMMENT")
duration = time.time() - start_time
github_api_request_duration_seconds.labels(operation="comment").observe(duration)
github_api_requests_total.labels(operation="comment", status="success").inc()
self._update_rate_limit_metrics(client, installation_id)
add_span_attributes(span, {"github.result": "commented"})
set_span_ok(span)
logger.info(
"Posted config error review on PR #%d in %s",
pr_number,
repo_full_name,
extra={
"repo": repo_full_name,
"pr_number": pr_number,
"installation_id": installation_id,
},
)
return True
except Exception as e:
> duration = time.time() & start_time
^^^^^^^^^^^^^^^^^^^^^^^^
E TypeError: unsupported operand type(s) for &: 'float' and 'float'
stampbot/github_client.py:571: TypeError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestCreatePrReviewComment::test_create_pr_review_comment_failure - TypeError: unsupported operand type(s) for &: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 74 passed, 3 deselected in 0.91s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_BitAnd, occurrence: 13
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -619,7 +619,7 @@
repo = client.get_repo(repo_full_name)
repo.get_label(label_name)
- duration = time.time() - start_time
+ duration = time.time() & start_time
github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
github_api_requests_total.labels(operation="get_label", status="success").inc()
.....................................................................F
=================================== FAILURES ===================================
__________________ TestRepoHasLabel.test_repo_has_label_true ___________________
self = <tests.test_github_client.TestRepoHasLabel object at 0x7faff02cd450>
def test_repo_has_label_true(self):
"""Test repo_has_label returns True when label exists."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_repo = Mock()
mock_repo.get_label.return_value = Mock()
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=1, limit=2))
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
result = client.repo_has_label(123456, "owner/repo", "autoapprove")
> assert result is True
E assert None is True
tests/test_github_client.py:1054: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-05-16 19:30:45 [debug ] Could not verify label autoapprove in owner/repo: unsupported operand type(s) for &: 'float' and 'float' extra={'repo': 'owner/repo', 'label': 'autoapprove', 'installation_id': 123456, 'error': "unsupported operand type(s) for &: 'float' and 'float'"}
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestRepoHasLabel::test_repo_has_label_true - assert None is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 69 passed, 3 deselected in 0.76s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_BitAnd, occurrence: 14
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -630,7 +630,7 @@
return True
except GithubException as e:
- duration = time.time() - start_time
+ duration = time.time() & start_time
github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
github_api_requests_total.labels(operation="get_label", status="failure").inc()
......................................................................F
=================================== FAILURES ===================================
________________ TestRepoHasLabel.test_repo_has_label_not_found ________________
self = <stampbot.github_client.GitHubAppClient object at 0x7fcd30444e90>
installation_id = 123456, repo_full_name = 'owner/repo', label_name = 'missing'
def repo_has_label(
self,
installation_id: int,
repo_full_name: str,
label_name: str,
) -> bool | None:
"""Check whether a repository has a label.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name (owner/repo)
label_name: Label name to look up
Returns:
True if label exists, False if not found, None if unknown due to error
"""
start_time = time.time()
with create_span(
"github.get_label",
{
"github.repo": repo_full_name,
"github.label": label_name,
"github.installation_id": installation_id,
},
) as span:
try:
client = self._get_installation_client(installation_id)
repo = client.get_repo(repo_full_name)
> repo.get_label(label_name)
stampbot/github_client.py:620:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1175: in __call__
return self._mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1179: in _mock_call
return self._execute_mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <Mock name='Github().get_repo().get_label' id='140519254632704'>
args = ('missing',), kwargs = {}
effect = GithubException(404 {"message": "Not Found"})
def _execute_mock_call(self, /, *args, **kwargs):
# separate from _increment_mock_call so that awaited functions are
# executed separately from their call, also AsyncMock overrides this method
effect = self.side_effect
if effect is not None:
if _is_exception(effect):
> raise effect
E github.GithubException.GithubException: 404 {"message": "Not Found"}
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1234: GithubException
During handling of the above exception, another exception occurred:
self = <tests.test_github_client.TestRepoHasLabel object at 0x7fcd3461d590>
def test_repo_has_label_not_found(self):
"""Test repo_has_label returns False when label is missing."""
from github.GithubException import GithubException
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_repo = Mock()
mock_repo.get_label.side_effect = GithubException(404, {"message": "Not Found"}, None)
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
> result = client.repo_has_label(123456, "owner/repo", "missing")
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_github_client.py:1090:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.github_client.GitHubAppClient object at 0x7fcd30444e90>
installation_id = 123456, repo_full_name = 'owner/repo', label_name = 'missing'
def repo_has_label(
self,
installation_id: int,
repo_full_name: str,
label_name: str,
) -> bool | None:
"""Check whether a repository has a label.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name (owner/repo)
label_name: Label name to look up
Returns:
True if label exists, False if not found, None if unknown due to error
"""
start_time = time.time()
with create_span(
"github.get_label",
{
"github.repo": repo_full_name,
"github.label": label_name,
"github.installation_id": installation_id,
},
) as span:
try:
client = self._get_installation_client(installation_id)
repo = client.get_repo(repo_full_name)
repo.get_label(label_name)
duration = time.time() - start_time
github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
github_api_requests_total.labels(operation="get_label", status="success").inc()
self._update_rate_limit_metrics(client, installation_id)
add_span_attributes(span, {"github.label_found": True})
set_span_ok(span)
return True
except GithubException as e:
> duration = time.time() & start_time
^^^^^^^^^^^^^^^^^^^^^^^^
E TypeError: unsupported operand type(s) for &: 'float' and 'float'
stampbot/github_client.py:633: TypeError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestRepoHasLabel::test_repo_has_label_not_found - TypeError: unsupported operand type(s) for &: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 70 passed, 3 deselected in 0.93s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_BitAnd, occurrence: 15
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -655,7 +655,7 @@
return None
except Exception as e:
- duration = time.time() - start_time
+ duration = time.time() & start_time
github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
github_api_requests_total.labels(operation="get_label", status="failure").inc()
........................................................................ [ 34%]
F
=================================== FAILURES ===================================
__________________ TestRepoHasLabel.test_repo_has_label_error __________________
self = <stampbot.github_client.GitHubAppClient object at 0x7f11db18ef90>
installation_id = 123456, repo_full_name = 'owner/repo'
label_name = 'autoapprove'
def repo_has_label(
self,
installation_id: int,
repo_full_name: str,
label_name: str,
) -> bool | None:
"""Check whether a repository has a label.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name (owner/repo)
label_name: Label name to look up
Returns:
True if label exists, False if not found, None if unknown due to error
"""
start_time = time.time()
with create_span(
"github.get_label",
{
"github.repo": repo_full_name,
"github.label": label_name,
"github.installation_id": installation_id,
},
) as span:
try:
client = self._get_installation_client(installation_id)
repo = client.get_repo(repo_full_name)
> repo.get_label(label_name)
stampbot/github_client.py:620:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1175: in __call__
return self._mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1179: in _mock_call
return self._execute_mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <Mock name='Github().get_repo().get_label' id='139714666911392'>
args = ('autoapprove',), kwargs = {}, effect = Exception('API Error')
def _execute_mock_call(self, /, *args, **kwargs):
# separate from _increment_mock_call so that awaited functions are
# executed separately from their call, also AsyncMock overrides this method
effect = self.side_effect
if effect is not None:
if _is_exception(effect):
> raise effect
E Exception: API Error
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1234: Exception
During handling of the above exception, another exception occurred:
self = <tests.test_github_client.TestRepoHasLabel object at 0x7f11df502060>
def test_repo_has_label_error(self):
"""Test repo_has_label returns None on error."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_repo = Mock()
mock_repo.get_label.side_effect = Exception("API Error")
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
> result = client.repo_has_label(123456, "owner/repo", "autoapprove")
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_github_client.py:1164:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.github_client.GitHubAppClient object at 0x7f11db18ef90>
installation_id = 123456, repo_full_name = 'owner/repo'
label_name = 'autoapprove'
def repo_has_label(
self,
installation_id: int,
repo_full_name: str,
label_name: str,
) -> bool | None:
"""Check whether a repository has a label.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name (owner/repo)
label_name: Label name to look up
Returns:
True if label exists, False if not found, None if unknown due to error
"""
start_time = time.time()
with create_span(
"github.get_label",
{
"github.repo": repo_full_name,
"github.label": label_name,
"github.installation_id": installation_id,
},
) as span:
try:
client = self._get_installation_client(installation_id)
repo = client.get_repo(repo_full_name)
repo.get_label(label_name)
duration = time.time() - start_time
github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
github_api_requests_total.labels(operation="get_label", status="success").inc()
self._update_rate_limit_metrics(client, installation_id)
add_span_attributes(span, {"github.label_found": True})
set_span_ok(span)
return True
except GithubException as e:
duration = time.time() - start_time
github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
github_api_requests_total.labels(operation="get_label", status="failure").inc()
if e.status == 404:
add_span_attributes(span, {"github.label_found": False})
set_span_ok(span)
return False
set_span_error(span, e)
logger.debug(
"Could not verify label %s in %s: %s",
label_name,
repo_full_name,
_sanitize_error(e),
extra={
"repo": repo_full_name,
"label": label_name,
"installation_id": installation_id,
"error": _sanitize_error(e),
},
)
return None
except Exception as e:
> duration = time.time() & start_time
^^^^^^^^^^^^^^^^^^^^^^^^
E TypeError: unsupported operand type(s) for &: 'float' and 'float'
stampbot/github_client.py:658: TypeError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestRepoHasLabel::test_repo_has_label_error - TypeError: unsupported operand type(s) for &: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 72 passed, 3 deselected in 0.90s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_BitAnd, occurrence: 16
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -718,7 +718,7 @@
has_permission = permission_index >= required_index
- duration = time.time() - start_time
+ duration = time.time() & start_time
github_api_request_duration_seconds.labels(operation="get_permission").observe(
duration
)............................................................F
=================================== FAILURES ===================================
_____________ TestUserHasPermission.test_user_has_permission_true ______________
self = <tests.test_github_client.TestUserHasPermission object at 0x7f9ee44dcf50>
def test_user_has_permission_true(self):
"""Test required permission satisfied returns True."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_repo = Mock()
mock_repo.get_collaborator_permission.return_value = "write"
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=1, limit=2))
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
result = client.user_has_permission(123456, "owner/repo", "alice", "write")
> assert result is True
E assert False is True
tests/test_github_client.py:683: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-05-16 20:10:13 [warning ] Failed to check collaborator permission for alice in owner/repo: unsupported operand type(s) for &: 'float' and 'float' extra={'repo': 'owner/repo', 'username': 'alice', 'installation_id': 123456, 'error': "unsupported operand type(s) for &: 'float' and 'float'"}
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestUserHasPermission::test_user_has_permission_true - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 60 passed, 3 deselected in 0.80s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_BitAnd, occurrence: 17
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -738,7 +738,7 @@
return has_permission
except Exception as e:
- duration = time.time() - start_time
+ duration = time.time() & start_time
github_api_request_duration_seconds.labels(operation="get_permission").observe(
duration
)..............................................................F
=================================== FAILURES ===================================
_____________ TestUserHasPermission.test_user_has_permission_error _____________
self = <stampbot.github_client.GitHubAppClient object at 0x7ff96cf43fb0>
installation_id = 123456, repo_full_name = 'owner/repo', username = 'carol'
required_permission = 'maintain'
def user_has_permission(
self,
installation_id: int,
repo_full_name: str,
username: str,
required_permission: str,
) -> bool:
"""Check whether a user has required access to a repository.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name (owner/repo)
username: GitHub username to check
required_permission: Minimum required repository permission
Returns:
True if user meets or exceeds required permission, False otherwise
"""
start_time = time.time()
with create_span(
"github.get_collaborator_permission",
{
"github.repo": repo_full_name,
"github.username": username,
"github.required_permission": required_permission,
"github.installation_id": installation_id,
},
) as span:
try:
client = self._get_installation_client(installation_id)
repo = client.get_repo(repo_full_name)
> permission = repo.get_collaborator_permission(username)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/github_client.py:710:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1175: in __call__
return self._mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1179: in _mock_call
return self._execute_mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <Mock name='Github().get_repo().get_collaborator_permission' id='140709251484704'>
args = ('carol',), kwargs = {}, effect = Exception('API Error')
def _execute_mock_call(self, /, *args, **kwargs):
# separate from _increment_mock_call so that awaited functions are
# executed separately from their call, also AsyncMock overrides this method
effect = self.side_effect
if effect is not None:
if _is_exception(effect):
> raise effect
E Exception: API Error
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1234: Exception
During handling of the above exception, another exception occurred:
self = <tests.test_github_client.TestUserHasPermission object at 0x7ff9713c9a70>
def test_user_has_permission_error(self):
"""Test permission check returns False on error."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_repo = Mock()
mock_repo.get_collaborator_permission.side_effect = Exception("API Error")
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
> result = client.user_has_permission(123456, "owner/repo", "carol", "maintain")
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_github_client.py:754:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.github_client.GitHubAppClient object at 0x7ff96cf43fb0>
installation_id = 123456, repo_full_name = 'owner/repo', username = 'carol'
required_permission = 'maintain'
def user_has_permission(
self,
installation_id: int,
repo_full_name: str,
username: str,
required_permission: str,
) -> bool:
"""Check whether a user has required access to a repository.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name (owner/repo)
username: GitHub username to check
required_permission: Minimum required repository permission
Returns:
True if user meets or exceeds required permission, False otherwise
"""
start_time = time.time()
with create_span(
"github.get_collaborator_permission",
{
"github.repo": repo_full_name,
"github.username": username,
"github.required_permission": required_permission,
"github.installation_id": installation_id,
},
) as span:
try:
client = self._get_installation_client(installation_id)
repo = client.get_repo(repo_full_name)
permission = repo.get_collaborator_permission(username)
permission_order = ["none", "read", "triage", "write", "maintain", "admin"]
try:
permission_index = permission_order.index(permission)
required_index = permission_order.index(required_permission)
except ValueError:
permission_index = -1
required_index = 99
has_permission = permission_index >= required_index
duration = time.time() - start_time
github_api_request_duration_seconds.labels(operation="get_permission").observe(
duration
)
github_api_requests_total.labels(operation="get_permission", status="success").inc()
self._update_rate_limit_metrics(client, installation_id)
add_span_attributes(
span,
{
"github.permission": permission,
"github.has_permission": has_permission,
},
)
set_span_ok(span)
return has_permission
except Exception as e:
> duration = time.time() & start_time
^^^^^^^^^^^^^^^^^^^^^^^^
E TypeError: unsupported operand type(s) for &: 'float' and 'float'
stampbot/github_client.py:741: TypeError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestUserHasPermission::test_user_has_permission_error - TypeError: unsupported operand type(s) for &: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 62 passed, 3 deselected in 0.93s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_BitAnd, occurrence: 18
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -829,7 +829,7 @@
)
continue
- duration = time.time() - start_time
+ duration = time.time() & start_time
github_api_request_duration_seconds.labels(operation="get_user_teams").observe(
duration
)................................................................F
=================================== FAILURES ===================================
____________ TestGetUserTeamSlugs.test_get_user_team_slugs_success _____________
self = <tests.test_github_client.TestGetUserTeamSlugs object at 0x7f2e2d61d1d0>
def test_get_user_team_slugs_success(self):
"""Test successful team membership check."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
# Mock user
mock_user = Mock()
mock_user.login = "alice"
# Mock team that has alice as member
mock_team = Mock()
mock_team.has_in_members.return_value = True
# Mock org
mock_org = Mock()
mock_org.get_team_by_slug.return_value = mock_team
mock_github = Mock()
mock_github.get_organization.return_value = mock_org
mock_github.get_user.return_value = mock_user
mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
result = client.get_user_team_slugs(
123456, "acme", "alice", ["release-team", "deploy-team"]
)
> assert result == ["release-team", "deploy-team"]
E AssertionError: assert [] == ['release-tea...'deploy-team']
E
E Right contains 2 more items, first extra item: 'release-team'
E
E Full diff:
E + []
E - [
E - 'release-team',
E - 'deploy-team',
E - ]
tests/test_github_client.py:848: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-05-16 19:55:43 [debug ] User alice is a member of team release-team extra={'org': 'acme', 'username': 'alice', 'team': 'release-team'}
2026-05-16 19:55:43 [debug ] User alice is a member of team deploy-team extra={'org': 'acme', 'username': 'alice', 'team': 'deploy-team'}
2026-05-16 19:55:43 [warning ] Failed to check team memberships for alice in acme: unsupported operand type(s) for &: 'float' and 'float' extra={'org': 'acme', 'username': 'alice', 'installation_id': 123456, 'error': "unsupported operand type(s) for &: 'float' and 'float'"}
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetUserTeamSlugs::test_get_user_team_slugs_success - AssertionError: assert [] == ['release-tea...'deploy-team']
Right contains 2 more items, first extra item: 'release-team'
Full diff:
+ []
- [
- 'release-team',
- 'deploy-team',
- ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 64 passed, 3 deselected in 0.77s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_BitAnd, occurrence: 19
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -849,7 +849,7 @@
return member_teams
except Exception as e:
- duration = time.time() - start_time
+ duration = time.time() & start_time
github_api_request_duration_seconds.labels(operation="get_user_teams").observe(
duration
)....................................................................F
=================================== FAILURES ===================================
_______ TestGetUserTeamSlugs.test_get_user_team_slugs_general_exception ________
self = <stampbot.github_client.GitHubAppClient object at 0x7f651d007050>
installation_id = 123456, org_name = 'acme', username = 'alice'
allowed_teams = ['release-team']
def get_user_team_slugs(
self,
installation_id: int,
org_name: str,
username: str,
allowed_teams: list[str],
) -> list[str]:
"""Get team slugs the user is a member of from the allowed teams list.
Args:
installation_id: GitHub App installation ID
org_name: Organization name
username: GitHub username to check
allowed_teams: List of team slugs to check (can be "org/team" or just "team")
Returns:
List of team slugs the user is a member of
"""
start_time = time.time()
with create_span(
"github.get_user_team_slugs",
{
"github.org": org_name,
"github.username": username,
"github.installation_id": installation_id,
"github.teams_to_check": len(allowed_teams),
},
) as span:
member_teams: list[str] = []
try:
client = self._get_installation_client(installation_id)
> org = client.get_organization(org_name)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/github_client.py:796:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1175: in __call__
return self._mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1179: in _mock_call
return self._execute_mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <Mock name='Github().get_organization' id='140072256813248'>
args = ('acme',), kwargs = {}, effect = Exception('Network error')
def _execute_mock_call(self, /, *args, **kwargs):
# separate from _increment_mock_call so that awaited functions are
# executed separately from their call, also AsyncMock overrides this method
effect = self.side_effect
if effect is not None:
if _is_exception(effect):
> raise effect
E Exception: Network error
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1234: Exception
During handling of the above exception, another exception occurred:
self = <tests.test_github_client.TestGetUserTeamSlugs object at 0x7f65217605f0>
def test_get_user_team_slugs_general_exception(self):
"""Test team membership check with general exception."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_github = Mock()
mock_github.get_organization.side_effect = Exception("Network error")
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
> result = client.get_user_team_slugs(123456, "acme", "alice", ["release-team"])
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_github_client.py:1010:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.github_client.GitHubAppClient object at 0x7f651d007050>
installation_id = 123456, org_name = 'acme', username = 'alice'
allowed_teams = ['release-team']
def get_user_team_slugs(
self,
installation_id: int,
org_name: str,
username: str,
allowed_teams: list[str],
) -> list[str]:
"""Get team slugs the user is a member of from the allowed teams list.
Args:
installation_id: GitHub App installation ID
org_name: Organization name
username: GitHub username to check
allowed_teams: List of team slugs to check (can be "org/team" or just "team")
Returns:
List of team slugs the user is a member of
"""
start_time = time.time()
with create_span(
"github.get_user_team_slugs",
{
"github.org": org_name,
"github.username": username,
"github.installation_id": installation_id,
"github.teams_to_check": len(allowed_teams),
},
) as span:
member_teams: list[str] = []
try:
client = self._get_installation_client(installation_id)
org = client.get_organization(org_name)
for team_ref in allowed_teams:
# Extract team slug (handle both "org/team" and "team" formats)
team_slug = team_ref.split("/")[-1] if "/" in team_ref else team_ref
try:
team = org.get_team_by_slug(team_slug)
# Check if user is a member
user = client.get_user(username)
if team.has_in_members(user): # type: ignore[arg-type]
member_teams.append(team_slug)
logger.debug(
"User %s is a member of team %s",
username,
team_slug,
extra={
"org": org_name,
"username": username,
"team": team_slug,
},
)
except GithubException as e:
# Team not found or no access - skip silently
logger.debug(
"Could not check team %s membership: %s",
team_slug,
e.data.get("message", str(e)) if hasattr(e, "data") else str(e),
extra={
"org": org_name,
"team": team_slug,
"username": username,
},
)
continue
duration = time.time() - start_time
github_api_request_duration_seconds.labels(operation="get_user_teams").observe(
duration
)
github_api_requests_total.labels(operation="get_user_teams", status="success").inc()
self._update_rate_limit_metrics(client, installation_id)
add_span_attributes(
span,
{
"github.member_teams": len(member_teams),
"github.teams_checked": len(allowed_teams),
},
)
set_span_ok(span)
return member_teams
except Exception as e:
> duration = time.time() & start_time
^^^^^^^^^^^^^^^^^^^^^^^^
E TypeError: unsupported operand type(s) for &: 'float' and 'float'
stampbot/github_client.py:852: TypeError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetUserTeamSlugs::test_get_user_team_slugs_general_exception - TypeError: unsupported operand type(s) for &: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 68 passed, 3 deselected in 0.89s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_BitXor, occurrence: 0
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -168,7 +168,7 @@
retry=retry,
)
- duration = time.time() - start_time
+ duration = time.time() ^ start_time
github_api_request_duration_seconds.labels(operation="get_token").observe(duration)
github_api_requests_total.labels(operation="get_token", status="success").inc()
...............................................F
=================================== FAILURES ===================================
________ TestGetInstallationClient.test_get_installation_client_success ________
self = <tests.test_github_client.TestGetInstallationClient object at 0x7fe7a58e4050>
def test_get_installation_client_success(self):
"""Test successful installation client creation."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
> result = client._get_installation_client(123456)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_github_client.py:211:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.github_client.GitHubAppClient object at 0x7fe7a1aa0750>
installation_id = 123456
def _get_installation_client(self, installation_id: int) -> Github:
"""Get authenticated GitHub client for an installation.
Args:
installation_id: GitHub App installation ID.
Returns:
Authenticated Github client instance with timeout and retry configured.
Raises:
github.GithubException: If token exchange fails.
"""
start_time = time.time()
with create_span(
"github.get_installation_token",
{"github.installation_id": installation_id},
) as span:
try:
auth = self.integration.get_access_token(installation_id)
# Configure retry with exponential backoff
retry = Retry(
total=GITHUB_API_RETRY_TOTAL,
backoff_factor=GITHUB_API_RETRY_BACKOFF,
status_forcelist=[500, 502, 503, 504],
allowed_methods=["GET", "POST", "PUT", "DELETE", "PATCH"],
)
client = Github(
auth=Auth.Token(auth.token),
timeout=GITHUB_API_TIMEOUT,
retry=retry,
)
> duration = time.time() ^ start_time
^^^^^^^^^^^^^^^^^^^^^^^^
E TypeError: unsupported operand type(s) for ^: 'float' and 'float'
stampbot/github_client.py:171: TypeError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetInstallationClient::test_get_installation_client_success - TypeError: unsupported operand type(s) for ^: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 47 passed, 3 deselected in 0.80s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_BitXor, occurrence: 1
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -176,7 +176,7 @@
return client
except Exception as e:
- duration = time.time() - start_time
+ duration = time.time() ^ start_time
github_api_request_duration_seconds.labels(operation="get_token").observe(duration)
github_api_requests_total.labels(operation="get_token", status="failure").inc()
set_span_error(span, e)................................................F
=================================== FAILURES ===================================
________ TestGetInstallationClient.test_get_installation_client_failure ________
self = <stampbot.github_client.GitHubAppClient object at 0x7facf7cb9160>
installation_id = 123456
def _get_installation_client(self, installation_id: int) -> Github:
"""Get authenticated GitHub client for an installation.
Args:
installation_id: GitHub App installation ID.
Returns:
Authenticated Github client instance with timeout and retry configured.
Raises:
github.GithubException: If token exchange fails.
"""
start_time = time.time()
with create_span(
"github.get_installation_token",
{"github.installation_id": installation_id},
) as span:
try:
> auth = self.integration.get_access_token(installation_id)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/github_client.py:155:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1175: in __call__
return self._mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1179: in _mock_call
return self._execute_mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <Mock name='GithubIntegration().get_access_token' id='140380849487952'>
args = (123456,), kwargs = {}, effect = Exception('Token exchange failed')
def _execute_mock_call(self, /, *args, **kwargs):
# separate from _increment_mock_call so that awaited functions are
# executed separately from their call, also AsyncMock overrides this method
effect = self.side_effect
if effect is not None:
if _is_exception(effect):
> raise effect
E Exception: Token exchange failed
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1234: Exception
During handling of the above exception, another exception occurred:
self = <tests.test_github_client.TestGetInstallationClient object at 0x7facfaa4c190>
def test_get_installation_client_failure(self):
"""Test installation client creation failure."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_integration.get_access_token.side_effect = Exception("Token exchange failed")
mock_integration_cls.return_value = mock_integration
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
with pytest.raises(Exception, match="Token exchange failed"):
> client._get_installation_client(123456)
tests/test_github_client.py:240:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.github_client.GitHubAppClient object at 0x7facf7cb9160>
installation_id = 123456
def _get_installation_client(self, installation_id: int) -> Github:
"""Get authenticated GitHub client for an installation.
Args:
installation_id: GitHub App installation ID.
Returns:
Authenticated Github client instance with timeout and retry configured.
Raises:
github.GithubException: If token exchange fails.
"""
start_time = time.time()
with create_span(
"github.get_installation_token",
{"github.installation_id": installation_id},
) as span:
try:
auth = self.integration.get_access_token(installation_id)
# Configure retry with exponential backoff
retry = Retry(
total=GITHUB_API_RETRY_TOTAL,
backoff_factor=GITHUB_API_RETRY_BACKOFF,
status_forcelist=[500, 502, 503, 504],
allowed_methods=["GET", "POST", "PUT", "DELETE", "PATCH"],
)
client = Github(
auth=Auth.Token(auth.token),
timeout=GITHUB_API_TIMEOUT,
retry=retry,
)
duration = time.time() - start_time
github_api_request_duration_seconds.labels(operation="get_token").observe(duration)
github_api_requests_total.labels(operation="get_token", status="success").inc()
set_span_ok(span)
return client
except Exception as e:
> duration = time.time() ^ start_time
^^^^^^^^^^^^^^^^^^^^^^^^
E TypeError: unsupported operand type(s) for ^: 'float' and 'float'
stampbot/github_client.py:179: TypeError
During handling of the above exception, another exception occurred:
self = <tests.test_github_client.TestGetInstallationClient object at 0x7facfaa4c190>
def test_get_installation_client_failure(self):
"""Test installation client creation failure."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_integration.get_access_token.side_effect = Exception("Token exchange failed")
mock_integration_cls.return_value = mock_integration
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
> with pytest.raises(Exception, match="Token exchange failed"):
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E AssertionError: Regex pattern did not match.
E Expected regex: 'Token exchange failed'
E Actual message: "unsupported operand type(s) for ^: 'float' and 'float'"
tests/test_github_client.py:239: AssertionError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetInstallationClient::test_get_installation_client_failure - AssertionError: Regex pattern did not match.
Expected regex: 'Token exchange failed'
Actual message: "unsupported operand type(s) for ^: 'float' and 'float'"
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 48 passed, 3 deselected in 0.86s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_BitXor, occurrence: 2
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -240,7 +240,7 @@
event="APPROVE",
)
- duration = time.time() - start_time
+ duration = time.time() ^ start_time
github_api_request_duration_seconds.labels(operation="approve").observe(duration)
github_api_requests_total.labels(operation="approve", status="success").inc()
...................................................F
=================================== FAILURES ===================================
____________________ TestApprovePR.test_approve_pr_success _____________________
self = <tests.test_github_client.TestApprovePR object at 0x7fca603ac550>
def test_approve_pr_success(self):
"""Test successful PR approval."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_pr = Mock()
mock_repo = Mock()
mock_repo.get_pull.return_value = mock_pr
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
result = client.approve_pr(123456, "owner/repo", 42)
> assert result is True
E assert False is True
tests/test_github_client.py:313: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-05-16 20:10:15 [error ] Failed to approve PR #42 in owner/repo: unsupported operand type(s) for ^: 'float' and 'float' extra={'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': "unsupported operand type(s) for ^: 'float' and 'float'"}
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestApprovePR::test_approve_pr_success - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 51 passed, 3 deselected in 0.76s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_BitXor, occurrence: 3
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -260,7 +260,7 @@
return True
except Exception as e:
- duration = time.time() - start_time
+ duration = time.time() ^ start_time
github_api_request_duration_seconds.labels(operation="approve").observe(duration)
github_api_requests_total.labels(operation="approve", status="failure").inc()
....................................................F
=================================== FAILURES ===================================
____________________ TestApprovePR.test_approve_pr_failure _____________________
self = <stampbot.github_client.GitHubAppClient object at 0x7f3edc656d00>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42
comment = 'Auto-approved by Stampbot'
def approve_pr(
self,
installation_id: int,
repo_full_name: str,
pr_number: int,
comment: str = "Auto-approved by Stampbot",
) -> bool:
"""Approve a pull request.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name (owner/repo)
pr_number: Pull request number
comment: Review comment
Returns:
True if successful, False otherwise
"""
start_time = time.time()
with create_span(
"github.approve_pr",
{
"github.repo": repo_full_name,
"github.pr_number": pr_number,
"github.installation_id": installation_id,
},
) as span:
try:
client = self._get_installation_client(installation_id)
repo = client.get_repo(repo_full_name)
pr = repo.get_pull(pr_number)
# Create approval review
> pr.create_review(
body=comment,
event="APPROVE",
)
stampbot/github_client.py:238:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1175: in __call__
return self._mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1179: in _mock_call
return self._execute_mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <Mock name='Github().get_repo().get_pull().create_review' id='139907962731024'>
args = (), kwargs = {'body': 'Auto-approved by Stampbot', 'event': 'APPROVE'}
effect = Exception('API Error')
def _execute_mock_call(self, /, *args, **kwargs):
# separate from _increment_mock_call so that awaited functions are
# executed separately from their call, also AsyncMock overrides this method
effect = self.side_effect
if effect is not None:
if _is_exception(effect):
> raise effect
E Exception: API Error
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1234: Exception
During handling of the above exception, another exception occurred:
self = <tests.test_github_client.TestApprovePR object at 0x7f3ee0518690>
def test_approve_pr_failure(self):
"""Test PR approval failure."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_pr = Mock()
mock_pr.create_review.side_effect = Exception("API Error")
mock_repo = Mock()
mock_repo.get_pull.return_value = mock_pr
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
> result = client.approve_pr(123456, "owner/repo", 42)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_github_client.py:350:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.github_client.GitHubAppClient object at 0x7f3edc656d00>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42
comment = 'Auto-approved by Stampbot'
def approve_pr(
self,
installation_id: int,
repo_full_name: str,
pr_number: int,
comment: str = "Auto-approved by Stampbot",
) -> bool:
"""Approve a pull request.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name (owner/repo)
pr_number: Pull request number
comment: Review comment
Returns:
True if successful, False otherwise
"""
start_time = time.time()
with create_span(
"github.approve_pr",
{
"github.repo": repo_full_name,
"github.pr_number": pr_number,
"github.installation_id": installation_id,
},
) as span:
try:
client = self._get_installation_client(installation_id)
repo = client.get_repo(repo_full_name)
pr = repo.get_pull(pr_number)
# Create approval review
pr.create_review(
body=comment,
event="APPROVE",
)
duration = time.time() - start_time
github_api_request_duration_seconds.labels(operation="approve").observe(duration)
github_api_requests_total.labels(operation="approve", status="success").inc()
self._update_rate_limit_metrics(client, installation_id)
add_span_attributes(span, {"github.result": "approved"})
set_span_ok(span)
logger.info(
f"Approved PR #{pr_number} in {repo_full_name}",
extra={
"repo": repo_full_name,
"pr_number": pr_number,
"installation_id": installation_id,
},
)
return True
except Exception as e:
> duration = time.time() ^ start_time
^^^^^^^^^^^^^^^^^^^^^^^^
E TypeError: unsupported operand type(s) for ^: 'float' and 'float'
stampbot/github_client.py:263: TypeError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestApprovePR::test_approve_pr_failure - TypeError: unsupported operand type(s) for ^: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 52 passed, 3 deselected in 0.85s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_BitXor, occurrence: 4
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -317,7 +317,7 @@
review = pr.get_review(review_id)
review.dismiss(message)
- duration = time.time() - start_time
+ duration = time.time() ^ start_time
github_api_request_duration_seconds.labels(operation="dismiss").observe(duration)
github_api_requests_total.labels(operation="dismiss", status="success").inc()
.....................................................F
=================================== FAILURES ===================================
______________ TestDismissApproval.test_dismiss_approval_success _______________
self = <tests.test_github_client.TestDismissApproval object at 0x7fc358e0c7d0>
def test_dismiss_approval_success(self):
"""Test successful approval dismissal."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_review = Mock()
mock_pr = Mock()
mock_pr.get_review.return_value = mock_review
mock_repo = Mock()
mock_repo.get_pull.return_value = mock_pr
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
result = client.dismiss_approval(123456, "owner/repo", 42, 999)
> assert result is True
E assert False is True
tests/test_github_client.py:396: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-05-16 19:49:53 [error ] Failed to dismiss approval for PR #42 in owner/repo: unsupported operand type(s) for ^: 'float' and 'float' extra={'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': "unsupported operand type(s) for ^: 'float' and 'float'"}
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestDismissApproval::test_dismiss_approval_success - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 53 passed, 3 deselected in 0.74s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_BitXor, occurrence: 5
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -338,7 +338,7 @@
return True
except Exception as e:
- duration = time.time() - start_time
+ duration = time.time() ^ start_time
github_api_request_duration_seconds.labels(operation="dismiss").observe(duration)
github_api_requests_total.labels(operation="dismiss", status="failure").inc()
......................................................F
=================================== FAILURES ===================================
______________ TestDismissApproval.test_dismiss_approval_failure _______________
self = <stampbot.github_client.GitHubAppClient object at 0x7f994a6268f0>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42
review_id = 999, message = 'Approval dismissed by Stampbot'
def dismiss_approval(
self,
installation_id: int,
repo_full_name: str,
pr_number: int,
review_id: int,
message: str = "Approval dismissed by Stampbot",
) -> bool:
"""Dismiss a pull request approval.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name (owner/repo)
pr_number: Pull request number
review_id: Review ID to dismiss
message: Dismissal message
Returns:
True if successful, False otherwise
"""
start_time = time.time()
with create_span(
"github.dismiss_approval",
{
"github.repo": repo_full_name,
"github.pr_number": pr_number,
"github.review_id": review_id,
"github.installation_id": installation_id,
},
) as span:
try:
client = self._get_installation_client(installation_id)
repo = client.get_repo(repo_full_name)
pr = repo.get_pull(pr_number)
# Get the review and dismiss it
review = pr.get_review(review_id)
> review.dismiss(message)
stampbot/github_client.py:318:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1175: in __call__
return self._mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1179: in _mock_call
return self._execute_mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <Mock name='Github().get_repo().get_pull().get_review().dismiss' id='140296353559760'>
args = ('Approval dismissed by Stampbot',), kwargs = {}
effect = Exception('API Error')
def _execute_mock_call(self, /, *args, **kwargs):
# separate from _increment_mock_call so that awaited functions are
# executed separately from their call, also AsyncMock overrides this method
effect = self.side_effect
if effect is not None:
if _is_exception(effect):
> raise effect
E Exception: API Error
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1234: Exception
During handling of the above exception, another exception occurred:
self = <tests.test_github_client.TestDismissApproval object at 0x7f994e57c910>
def test_dismiss_approval_failure(self):
"""Test approval dismissal failure."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_review = Mock()
mock_review.dismiss.side_effect = Exception("API Error")
mock_pr = Mock()
mock_pr.get_review.return_value = mock_review
mock_repo = Mock()
mock_repo.get_pull.return_value = mock_pr
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
> result = client.dismiss_approval(123456, "owner/repo", 42, 999)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_github_client.py:435:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.github_client.GitHubAppClient object at 0x7f994a6268f0>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42
review_id = 999, message = 'Approval dismissed by Stampbot'
def dismiss_approval(
self,
installation_id: int,
repo_full_name: str,
pr_number: int,
review_id: int,
message: str = "Approval dismissed by Stampbot",
) -> bool:
"""Dismiss a pull request approval.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name (owner/repo)
pr_number: Pull request number
review_id: Review ID to dismiss
message: Dismissal message
Returns:
True if successful, False otherwise
"""
start_time = time.time()
with create_span(
"github.dismiss_approval",
{
"github.repo": repo_full_name,
"github.pr_number": pr_number,
"github.review_id": review_id,
"github.installation_id": installation_id,
},
) as span:
try:
client = self._get_installation_client(installation_id)
repo = client.get_repo(repo_full_name)
pr = repo.get_pull(pr_number)
# Get the review and dismiss it
review = pr.get_review(review_id)
review.dismiss(message)
duration = time.time() - start_time
github_api_request_duration_seconds.labels(operation="dismiss").observe(duration)
github_api_requests_total.labels(operation="dismiss", status="success").inc()
self._update_rate_limit_metrics(client, installation_id)
add_span_attributes(span, {"github.result": "dismissed"})
set_span_ok(span)
logger.info(
f"Dismissed approval for PR #{pr_number} in {repo_full_name}",
extra={
"repo": repo_full_name,
"pr_number": pr_number,
"installation_id": installation_id,
"review_id": review_id,
},
)
return True
except Exception as e:
> duration = time.time() ^ start_time
^^^^^^^^^^^^^^^^^^^^^^^^
E TypeError: unsupported operand type(s) for ^: 'float' and 'float'
stampbot/github_client.py:341: TypeError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestDismissApproval::test_dismiss_approval_failure - TypeError: unsupported operand type(s) for ^: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 54 passed, 3 deselected in 0.85s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_BitXor, occurrence: 6
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -393,7 +393,7 @@
content = repo.get_contents(file_path, ref=ref) # type: ignore[arg-type]
if isinstance(content, list):
- duration = time.time() - start_time
+ duration = time.time() ^ start_time
github_api_request_duration_seconds.labels(operation="get_file").observe(
duration
)........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.65s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_BitXor, occurrence: 7
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -402,7 +402,7 @@
set_span_ok(span)
return None
- duration = time.time() - start_time
+ duration = time.time() ^ start_time
github_api_request_duration_seconds.labels(operation="get_file").observe(duration)
github_api_requests_total.labels(operation="get_file", status="success").inc()
.......................................................F
=================================== FAILURES ===================================
__________________ TestGetRepoFile.test_get_repo_file_success __________________
self = <tests.test_github_client.TestGetRepoFile object at 0x7f4af6384a50>
def test_get_repo_file_success(self):
"""Test successful file retrieval."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_content = Mock()
mock_content.decoded_content = b"file content"
mock_repo = Mock()
mock_repo.get_contents.return_value = mock_content
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
result = client.get_repo_file(123456, "owner/repo", "stampbot.toml")
> assert result == "file content"
E AssertionError: assert None == 'file content'
tests/test_github_client.py:480: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-05-16 20:07:55 [debug ] Could not fetch stampbot.toml from owner/repo: unsupported operand type(s) for ^: 'float' and 'float' extra={'repo': 'owner/repo', 'file_path': 'stampbot.toml', 'installation_id': 123456}
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetRepoFile::test_get_repo_file_success - AssertionError: assert None == 'file content'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 55 passed, 3 deselected in 0.75s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_BitXor, occurrence: 8
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -414,7 +414,7 @@
return content.decoded_content.decode("utf-8")
except Exception as e:
- duration = time.time() - start_time
+ duration = time.time() ^ start_time
github_api_request_duration_seconds.labels(operation="get_file").observe(duration)
github_api_requests_total.labels(operation="get_file", status="not_found").inc()
.........................................................F
=================================== FAILURES ===================================
___________ TestGetRepoFile.test_get_repo_file_returns_none_on_error ___________
self = <stampbot.github_client.GitHubAppClient object at 0x7f01d4458cd0>
installation_id = 123456, repo_full_name = 'owner/repo'
file_path = 'nonexistent.toml', ref = None
def get_repo_file(
self,
installation_id: int,
repo_full_name: str,
file_path: str,
ref: str | None = None,
) -> str | None:
"""Get file content from repository.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name (owner/repo)
file_path: Path to file in repository
ref: Git reference (branch, tag, commit). Defaults to default branch
Returns:
File content as string, or None if not found
"""
start_time = time.time()
with create_span(
"github.get_file",
{
"github.repo": repo_full_name,
"github.file_path": file_path,
"github.ref": ref or "default",
"github.installation_id": installation_id,
},
) as span:
try:
client = self._get_installation_client(installation_id)
repo = client.get_repo(repo_full_name)
> content = repo.get_contents(file_path, ref=ref) # type: ignore[arg-type]
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/github_client.py:394:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1175: in __call__
return self._mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1179: in _mock_call
return self._execute_mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <Mock name='Github().get_repo().get_contents' id='139645833332448'>
args = ('nonexistent.toml',), kwargs = {'ref': None}
effect = Exception('File not found')
def _execute_mock_call(self, /, *args, **kwargs):
# separate from _increment_mock_call so that awaited functions are
# executed separately from their call, also AsyncMock overrides this method
effect = self.side_effect
if effect is not None:
if _is_exception(effect):
> raise effect
E Exception: File not found
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1234: Exception
During handling of the above exception, another exception occurred:
self = <tests.test_github_client.TestGetRepoFile object at 0x7f01d8811940>
def test_get_repo_file_returns_none_on_error(self):
"""Test that get_repo_file returns None when file not found."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_repo = Mock()
mock_repo.get_contents.side_effect = Exception("File not found")
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
> result = client.get_repo_file(123456, "owner/repo", "nonexistent.toml")
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_github_client.py:551:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.github_client.GitHubAppClient object at 0x7f01d4458cd0>
installation_id = 123456, repo_full_name = 'owner/repo'
file_path = 'nonexistent.toml', ref = None
def get_repo_file(
self,
installation_id: int,
repo_full_name: str,
file_path: str,
ref: str | None = None,
) -> str | None:
"""Get file content from repository.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name (owner/repo)
file_path: Path to file in repository
ref: Git reference (branch, tag, commit). Defaults to default branch
Returns:
File content as string, or None if not found
"""
start_time = time.time()
with create_span(
"github.get_file",
{
"github.repo": repo_full_name,
"github.file_path": file_path,
"github.ref": ref or "default",
"github.installation_id": installation_id,
},
) as span:
try:
client = self._get_installation_client(installation_id)
repo = client.get_repo(repo_full_name)
content = repo.get_contents(file_path, ref=ref) # type: ignore[arg-type]
if isinstance(content, list):
duration = time.time() - start_time
github_api_request_duration_seconds.labels(operation="get_file").observe(
duration
)
github_api_requests_total.labels(operation="get_file", status="not_found").inc()
add_span_attributes(span, {"github.result": "not_found"})
set_span_ok(span)
return None
duration = time.time() - start_time
github_api_request_duration_seconds.labels(operation="get_file").observe(duration)
github_api_requests_total.labels(operation="get_file", status="success").inc()
self._update_rate_limit_metrics(client, installation_id)
add_span_attributes(span, {"github.result": "found"})
set_span_ok(span)
return content.decoded_content.decode("utf-8")
except Exception as e:
> duration = time.time() ^ start_time
^^^^^^^^^^^^^^^^^^^^^^^^
E TypeError: unsupported operand type(s) for ^: 'float' and 'float'
stampbot/github_client.py:417: TypeError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetRepoFile::test_get_repo_file_returns_none_on_error - TypeError: unsupported operand type(s) for ^: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 57 passed, 3 deselected in 0.88s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_BitXor, occurrence: 9
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -473,7 +473,7 @@
if review.user.login == bot_user and review.state == "APPROVED":
bot_review_ids.append(review.id)
- duration = time.time() - start_time
+ duration = time.time() ^ start_time
github_api_request_duration_seconds.labels(operation="find_reviews").observe(
duration
)..........................................................F
=================================== FAILURES ===================================
_______________ TestFindBotReviews.test_find_bot_reviews_success _______________
self = <tests.test_github_client.TestFindBotReviews object at 0x7f18a7e2ccd0>
def test_find_bot_reviews_success(self):
"""Test successful bot review finding."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration.get_app.return_value = Mock(slug="stampbot")
mock_integration_cls.return_value = mock_integration
# Create mock reviews
bot_review = Mock()
bot_review.user.login = "stampbot[bot]"
bot_review.state = "APPROVED"
bot_review.id = 123
other_review = Mock()
other_review.user.login = "other-user"
other_review.state = "APPROVED"
other_review.id = 456
mock_pr = Mock()
mock_pr.get_reviews.return_value = [bot_review, other_review]
mock_repo = Mock()
mock_repo.get_pull.return_value = mock_pr
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
result = client.find_bot_reviews(123456, "owner/repo", 42)
> assert result == [123]
E assert [] == [123]
E
E Right contains one more item: 123
E
E Full diff:
E + []
E - [
E - 123,
E - ]
tests/test_github_client.py:608: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-05-16 19:47:32 [error ] Failed to find bot reviews for PR #42 in owner/repo: unsupported operand type(s) for ^: 'float' and 'float' extra={'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': "unsupported operand type(s) for ^: 'float' and 'float'"}
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestFindBotReviews::test_find_bot_reviews_success - assert [] == [123]
Right contains one more item: 123
Full diff:
+ []
- [
- 123,
- ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 58 passed, 3 deselected in 0.77s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_BitXor, occurrence: 10
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -489,7 +489,7 @@
return bot_review_ids
except Exception as e:
- duration = time.time() - start_time
+ duration = time.time() ^ start_time
github_api_request_duration_seconds.labels(operation="find_reviews").observe(
duration
)...........................................................F
=================================== FAILURES ===================================
_______ TestFindBotReviews.test_find_bot_reviews_returns_empty_on_error ________
self = <stampbot.github_client.GitHubAppClient object at 0x7f7cf5154f30>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42
def find_bot_reviews(
self,
installation_id: int,
repo_full_name: str,
pr_number: int,
) -> list[int]:
"""Find all reviews created by this bot on a PR.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name (owner/repo)
pr_number: Pull request number
Returns:
List of review IDs created by the bot
"""
start_time = time.time()
with create_span(
"github.find_bot_reviews",
{
"github.repo": repo_full_name,
"github.pr_number": pr_number,
"github.installation_id": installation_id,
},
) as span:
try:
client = self._get_installation_client(installation_id)
> repo = client.get_repo(repo_full_name)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/github_client.py:462:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1175: in __call__
return self._mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1179: in _mock_call
return self._execute_mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <Mock name='Github().get_repo' id='140174664975088'>
args = ('owner/repo',), kwargs = {}, effect = Exception('API Error')
def _execute_mock_call(self, /, *args, **kwargs):
# separate from _increment_mock_call so that awaited functions are
# executed separately from their call, also AsyncMock overrides this method
effect = self.side_effect
if effect is not None:
if _is_exception(effect):
> raise effect
E Exception: API Error
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1234: Exception
During handling of the above exception, another exception occurred:
self = <tests.test_github_client.TestFindBotReviews object at 0x7f7cf9204e10>
def test_find_bot_reviews_returns_empty_on_error(self):
"""Test that find_bot_reviews returns empty list on error."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_github = Mock()
mock_github.get_repo.side_effect = Exception("API Error")
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
> result = client.find_bot_reviews(123456, "owner/repo", 42)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_github_client.py:640:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.github_client.GitHubAppClient object at 0x7f7cf5154f30>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42
def find_bot_reviews(
self,
installation_id: int,
repo_full_name: str,
pr_number: int,
) -> list[int]:
"""Find all reviews created by this bot on a PR.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name (owner/repo)
pr_number: Pull request number
Returns:
List of review IDs created by the bot
"""
start_time = time.time()
with create_span(
"github.find_bot_reviews",
{
"github.repo": repo_full_name,
"github.pr_number": pr_number,
"github.installation_id": installation_id,
},
) as span:
try:
client = self._get_installation_client(installation_id)
repo = client.get_repo(repo_full_name)
pr = repo.get_pull(pr_number)
# Get bot user via JWT-authenticated integration (installation tokens
# cannot call GET /user, which is restricted by GitHub Apps integration)
app_info = self.integration.get_app()
bot_user = f"{app_info.slug}[bot]"
# Find all reviews by bot that are approvals
bot_review_ids = []
for review in pr.get_reviews():
if review.user.login == bot_user and review.state == "APPROVED":
bot_review_ids.append(review.id)
duration = time.time() - start_time
github_api_request_duration_seconds.labels(operation="find_reviews").observe(
duration
)
github_api_requests_total.labels(operation="find_reviews", status="success").inc()
self._update_rate_limit_metrics(client, installation_id)
add_span_attributes(
span, {"github.reviews_found": len(bot_review_ids), "github.bot_user": bot_user}
)
set_span_ok(span)
return bot_review_ids
except Exception as e:
> duration = time.time() ^ start_time
^^^^^^^^^^^^^^^^^^^^^^^^
E TypeError: unsupported operand type(s) for ^: 'float' and 'float'
stampbot/github_client.py:492: TypeError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestFindBotReviews::test_find_bot_reviews_returns_empty_on_error - TypeError: unsupported operand type(s) for ^: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 59 passed, 3 deselected in 0.87s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_BitXor, occurrence: 11
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -546,7 +546,7 @@
pr.create_review(body=message, event="COMMENT")
- duration = time.time() - start_time
+ duration = time.time() ^ start_time
github_api_request_duration_seconds.labels(operation="comment").observe(duration)
github_api_requests_total.labels(operation="comment", status="success").inc()
........................................................................ [ 34%]
.F
=================================== FAILURES ===================================
_______ TestCreatePrReviewComment.test_create_pr_review_comment_success ________
self = <tests.test_github_client.TestCreatePrReviewComment object at 0x7ff5310856d0>
def test_create_pr_review_comment_success(self):
"""Test successful review comment creation."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_pr = Mock()
mock_repo = Mock()
mock_repo.get_pull.return_value = mock_pr
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=1, limit=2))
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
result = client.create_pr_review_comment(
123456,
"owner/repo",
42,
"Config error",
)
> assert result is True
E assert False is True
tests/test_github_client.py:1213: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-05-16 19:36:28 [warning ] Failed to post config error review for PR #42 in owner/repo: unsupported operand type(s) for ^: 'float' and 'float' extra={'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': "unsupported operand type(s) for ^: 'float' and 'float'"}
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestCreatePrReviewComment::test_create_pr_review_comment_success - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 73 passed, 3 deselected in 0.78s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_BitXor, occurrence: 12
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -568,7 +568,7 @@
return True
except Exception as e:
- duration = time.time() - start_time
+ duration = time.time() ^ start_time
github_api_request_duration_seconds.labels(operation="comment").observe(duration)
github_api_requests_total.labels(operation="comment", status="failure").inc()
........................................................................ [ 34%]
..F
=================================== FAILURES ===================================
_______ TestCreatePrReviewComment.test_create_pr_review_comment_failure ________
self = <stampbot.github_client.GitHubAppClient object at 0x7fb61e1c1790>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42
message = 'Config error'
def create_pr_review_comment(
self,
installation_id: int,
repo_full_name: str,
pr_number: int,
message: str,
) -> bool:
"""Create a comment review on a pull request.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name (owner/repo)
pr_number: Pull request number
message: Review comment body
Returns:
True if successful, False otherwise
"""
start_time = time.time()
with create_span(
"github.create_review_comment",
{
"github.repo": repo_full_name,
"github.pr_number": pr_number,
"github.installation_id": installation_id,
},
) as span:
try:
client = self._get_installation_client(installation_id)
repo = client.get_repo(repo_full_name)
> pr = repo.get_pull(pr_number)
^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/github_client.py:545:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1175: in __call__
return self._mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1179: in _mock_call
return self._execute_mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <Mock name='Github().get_repo().get_pull' id='140420165864480'>
args = (42,), kwargs = {}, effect = Exception('API Error')
def _execute_mock_call(self, /, *args, **kwargs):
# separate from _increment_mock_call so that awaited functions are
# executed separately from their call, also AsyncMock overrides this method
effect = self.side_effect
if effect is not None:
if _is_exception(effect):
> raise effect
E Exception: API Error
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1234: Exception
During handling of the above exception, another exception occurred:
self = <tests.test_github_client.TestCreatePrReviewComment object at 0x7fb62234d810>
def test_create_pr_review_comment_failure(self):
"""Test review comment creation handles errors."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_repo = Mock()
mock_repo.get_pull.side_effect = Exception("API Error")
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
> result = client.create_pr_review_comment(
123456,
"owner/repo",
42,
"Config error",
)
tests/test_github_client.py:1248:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.github_client.GitHubAppClient object at 0x7fb61e1c1790>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42
message = 'Config error'
def create_pr_review_comment(
self,
installation_id: int,
repo_full_name: str,
pr_number: int,
message: str,
) -> bool:
"""Create a comment review on a pull request.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name (owner/repo)
pr_number: Pull request number
message: Review comment body
Returns:
True if successful, False otherwise
"""
start_time = time.time()
with create_span(
"github.create_review_comment",
{
"github.repo": repo_full_name,
"github.pr_number": pr_number,
"github.installation_id": installation_id,
},
) as span:
try:
client = self._get_installation_client(installation_id)
repo = client.get_repo(repo_full_name)
pr = repo.get_pull(pr_number)
pr.create_review(body=message, event="COMMENT")
duration = time.time() - start_time
github_api_request_duration_seconds.labels(operation="comment").observe(duration)
github_api_requests_total.labels(operation="comment", status="success").inc()
self._update_rate_limit_metrics(client, installation_id)
add_span_attributes(span, {"github.result": "commented"})
set_span_ok(span)
logger.info(
"Posted config error review on PR #%d in %s",
pr_number,
repo_full_name,
extra={
"repo": repo_full_name,
"pr_number": pr_number,
"installation_id": installation_id,
},
)
return True
except Exception as e:
> duration = time.time() ^ start_time
^^^^^^^^^^^^^^^^^^^^^^^^
E TypeError: unsupported operand type(s) for ^: 'float' and 'float'
stampbot/github_client.py:571: TypeError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestCreatePrReviewComment::test_create_pr_review_comment_failure - TypeError: unsupported operand type(s) for ^: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 74 passed, 3 deselected in 0.89s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_BitXor, occurrence: 13
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -619,7 +619,7 @@
repo = client.get_repo(repo_full_name)
repo.get_label(label_name)
- duration = time.time() - start_time
+ duration = time.time() ^ start_time
github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
github_api_requests_total.labels(operation="get_label", status="success").inc()
.....................................................................F
=================================== FAILURES ===================================
__________________ TestRepoHasLabel.test_repo_has_label_true ___________________
self = <tests.test_github_client.TestRepoHasLabel object at 0x7f893fc69450>
def test_repo_has_label_true(self):
"""Test repo_has_label returns True when label exists."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_repo = Mock()
mock_repo.get_label.return_value = Mock()
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=1, limit=2))
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
result = client.repo_has_label(123456, "owner/repo", "autoapprove")
> assert result is True
E assert None is True
tests/test_github_client.py:1054: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-05-16 19:48:45 [debug ] Could not verify label autoapprove in owner/repo: unsupported operand type(s) for ^: 'float' and 'float' extra={'repo': 'owner/repo', 'label': 'autoapprove', 'installation_id': 123456, 'error': "unsupported operand type(s) for ^: 'float' and 'float'"}
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestRepoHasLabel::test_repo_has_label_true - assert None is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 69 passed, 3 deselected in 0.77s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_BitXor, occurrence: 14
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -630,7 +630,7 @@
return True
except GithubException as e:
- duration = time.time() - start_time
+ duration = time.time() ^ start_time
github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
github_api_requests_total.labels(operation="get_label", status="failure").inc()
......................................................................F
=================================== FAILURES ===================================
________________ TestRepoHasLabel.test_repo_has_label_not_found ________________
self = <stampbot.github_client.GitHubAppClient object at 0x7fcc04348a10>
installation_id = 123456, repo_full_name = 'owner/repo', label_name = 'missing'
def repo_has_label(
self,
installation_id: int,
repo_full_name: str,
label_name: str,
) -> bool | None:
"""Check whether a repository has a label.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name (owner/repo)
label_name: Label name to look up
Returns:
True if label exists, False if not found, None if unknown due to error
"""
start_time = time.time()
with create_span(
"github.get_label",
{
"github.repo": repo_full_name,
"github.label": label_name,
"github.installation_id": installation_id,
},
) as span:
try:
client = self._get_installation_client(installation_id)
repo = client.get_repo(repo_full_name)
> repo.get_label(label_name)
stampbot/github_client.py:620:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1175: in __call__
return self._mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1179: in _mock_call
return self._execute_mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <Mock name='Github().get_repo().get_label' id='140514220435712'>
args = ('missing',), kwargs = {}
effect = GithubException(404 {"message": "Not Found"})
def _execute_mock_call(self, /, *args, **kwargs):
# separate from _increment_mock_call so that awaited functions are
# executed separately from their call, also AsyncMock overrides this method
effect = self.side_effect
if effect is not None:
if _is_exception(effect):
> raise effect
E github.GithubException.GithubException: 404 {"message": "Not Found"}
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1234: GithubException
During handling of the above exception, another exception occurred:
self = <tests.test_github_client.TestRepoHasLabel object at 0x7fcc08559590>
def test_repo_has_label_not_found(self):
"""Test repo_has_label returns False when label is missing."""
from github.GithubException import GithubException
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_repo = Mock()
mock_repo.get_label.side_effect = GithubException(404, {"message": "Not Found"}, None)
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
> result = client.repo_has_label(123456, "owner/repo", "missing")
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_github_client.py:1090:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.github_client.GitHubAppClient object at 0x7fcc04348a10>
installation_id = 123456, repo_full_name = 'owner/repo', label_name = 'missing'
def repo_has_label(
self,
installation_id: int,
repo_full_name: str,
label_name: str,
) -> bool | None:
"""Check whether a repository has a label.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name (owner/repo)
label_name: Label name to look up
Returns:
True if label exists, False if not found, None if unknown due to error
"""
start_time = time.time()
with create_span(
"github.get_label",
{
"github.repo": repo_full_name,
"github.label": label_name,
"github.installation_id": installation_id,
},
) as span:
try:
client = self._get_installation_client(installation_id)
repo = client.get_repo(repo_full_name)
repo.get_label(label_name)
duration = time.time() - start_time
github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
github_api_requests_total.labels(operation="get_label", status="success").inc()
self._update_rate_limit_metrics(client, installation_id)
add_span_attributes(span, {"github.label_found": True})
set_span_ok(span)
return True
except GithubException as e:
> duration = time.time() ^ start_time
^^^^^^^^^^^^^^^^^^^^^^^^
E TypeError: unsupported operand type(s) for ^: 'float' and 'float'
stampbot/github_client.py:633: TypeError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestRepoHasLabel::test_repo_has_label_not_found - TypeError: unsupported operand type(s) for ^: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 70 passed, 3 deselected in 0.91s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_BitXor, occurrence: 15
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -655,7 +655,7 @@
return None
except Exception as e:
- duration = time.time() - start_time
+ duration = time.time() ^ start_time
github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
github_api_requests_total.labels(operation="get_label", status="failure").inc()
........................................................................ [ 34%]
F
=================================== FAILURES ===================================
__________________ TestRepoHasLabel.test_repo_has_label_error __________________
self = <stampbot.github_client.GitHubAppClient object at 0x7f597beaecf0>
installation_id = 123456, repo_full_name = 'owner/repo'
label_name = 'autoapprove'
def repo_has_label(
self,
installation_id: int,
repo_full_name: str,
label_name: str,
) -> bool | None:
"""Check whether a repository has a label.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name (owner/repo)
label_name: Label name to look up
Returns:
True if label exists, False if not found, None if unknown due to error
"""
start_time = time.time()
with create_span(
"github.get_label",
{
"github.repo": repo_full_name,
"github.label": label_name,
"github.installation_id": installation_id,
},
) as span:
try:
client = self._get_installation_client(installation_id)
repo = client.get_repo(repo_full_name)
> repo.get_label(label_name)
stampbot/github_client.py:620:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1175: in __call__
return self._mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1179: in _mock_call
return self._execute_mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <Mock name='Github().get_repo().get_label' id='140022307706528'>
args = ('autoapprove',), kwargs = {}, effect = Exception('API Error')
def _execute_mock_call(self, /, *args, **kwargs):
# separate from _increment_mock_call so that awaited functions are
# executed separately from their call, also AsyncMock overrides this method
effect = self.side_effect
if effect is not None:
if _is_exception(effect):
> raise effect
E Exception: API Error
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1234: Exception
During handling of the above exception, another exception occurred:
self = <tests.test_github_client.TestRepoHasLabel object at 0x7f5980282060>
def test_repo_has_label_error(self):
"""Test repo_has_label returns None on error."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_repo = Mock()
mock_repo.get_label.side_effect = Exception("API Error")
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
> result = client.repo_has_label(123456, "owner/repo", "autoapprove")
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_github_client.py:1164:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.github_client.GitHubAppClient object at 0x7f597beaecf0>
installation_id = 123456, repo_full_name = 'owner/repo'
label_name = 'autoapprove'
def repo_has_label(
self,
installation_id: int,
repo_full_name: str,
label_name: str,
) -> bool | None:
"""Check whether a repository has a label.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name (owner/repo)
label_name: Label name to look up
Returns:
True if label exists, False if not found, None if unknown due to error
"""
start_time = time.time()
with create_span(
"github.get_label",
{
"github.repo": repo_full_name,
"github.label": label_name,
"github.installation_id": installation_id,
},
) as span:
try:
client = self._get_installation_client(installation_id)
repo = client.get_repo(repo_full_name)
repo.get_label(label_name)
duration = time.time() - start_time
github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
github_api_requests_total.labels(operation="get_label", status="success").inc()
self._update_rate_limit_metrics(client, installation_id)
add_span_attributes(span, {"github.label_found": True})
set_span_ok(span)
return True
except GithubException as e:
duration = time.time() - start_time
github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
github_api_requests_total.labels(operation="get_label", status="failure").inc()
if e.status == 404:
add_span_attributes(span, {"github.label_found": False})
set_span_ok(span)
return False
set_span_error(span, e)
logger.debug(
"Could not verify label %s in %s: %s",
label_name,
repo_full_name,
_sanitize_error(e),
extra={
"repo": repo_full_name,
"label": label_name,
"installation_id": installation_id,
"error": _sanitize_error(e),
},
)
return None
except Exception as e:
> duration = time.time() ^ start_time
^^^^^^^^^^^^^^^^^^^^^^^^
E TypeError: unsupported operand type(s) for ^: 'float' and 'float'
stampbot/github_client.py:658: TypeError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestRepoHasLabel::test_repo_has_label_error - TypeError: unsupported operand type(s) for ^: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 72 passed, 3 deselected in 0.94s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_BitXor, occurrence: 16
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -718,7 +718,7 @@
has_permission = permission_index >= required_index
- duration = time.time() - start_time
+ duration = time.time() ^ start_time
github_api_request_duration_seconds.labels(operation="get_permission").observe(
duration
)............................................................F
=================================== FAILURES ===================================
_____________ TestUserHasPermission.test_user_has_permission_true ______________
self = <tests.test_github_client.TestUserHasPermission object at 0x7f3a93e24f50>
def test_user_has_permission_true(self):
"""Test required permission satisfied returns True."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_repo = Mock()
mock_repo.get_collaborator_permission.return_value = "write"
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=1, limit=2))
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
result = client.user_has_permission(123456, "owner/repo", "alice", "write")
> assert result is True
E assert False is True
tests/test_github_client.py:683: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-05-16 19:37:25 [warning ] Failed to check collaborator permission for alice in owner/repo: unsupported operand type(s) for ^: 'float' and 'float' extra={'repo': 'owner/repo', 'username': 'alice', 'installation_id': 123456, 'error': "unsupported operand type(s) for ^: 'float' and 'float'"}
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestUserHasPermission::test_user_has_permission_true - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 60 passed, 3 deselected in 0.75s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_BitXor, occurrence: 17
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -738,7 +738,7 @@
return has_permission
except Exception as e:
- duration = time.time() - start_time
+ duration = time.time() ^ start_time
github_api_request_duration_seconds.labels(operation="get_permission").observe(
duration
)..............................................................F
=================================== FAILURES ===================================
_____________ TestUserHasPermission.test_user_has_permission_error _____________
self = <stampbot.github_client.GitHubAppClient object at 0x7fb0274482f0>
installation_id = 123456, repo_full_name = 'owner/repo', username = 'carol'
required_permission = 'maintain'
def user_has_permission(
self,
installation_id: int,
repo_full_name: str,
username: str,
required_permission: str,
) -> bool:
"""Check whether a user has required access to a repository.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name (owner/repo)
username: GitHub username to check
required_permission: Minimum required repository permission
Returns:
True if user meets or exceeds required permission, False otherwise
"""
start_time = time.time()
with create_span(
"github.get_collaborator_permission",
{
"github.repo": repo_full_name,
"github.username": username,
"github.required_permission": required_permission,
"github.installation_id": installation_id,
},
) as span:
try:
client = self._get_installation_client(installation_id)
repo = client.get_repo(repo_full_name)
> permission = repo.get_collaborator_permission(username)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/github_client.py:710:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1175: in __call__
return self._mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1179: in _mock_call
return self._execute_mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <Mock name='Github().get_repo().get_collaborator_permission' id='140394549726240'>
args = ('carol',), kwargs = {}, effect = Exception('API Error')
def _execute_mock_call(self, /, *args, **kwargs):
# separate from _increment_mock_call so that awaited functions are
# executed separately from their call, also AsyncMock overrides this method
effect = self.side_effect
if effect is not None:
if _is_exception(effect):
> raise effect
E Exception: API Error
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1234: Exception
During handling of the above exception, another exception occurred:
self = <tests.test_github_client.TestUserHasPermission object at 0x7fb02b8c9a70>
def test_user_has_permission_error(self):
"""Test permission check returns False on error."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_repo = Mock()
mock_repo.get_collaborator_permission.side_effect = Exception("API Error")
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
> result = client.user_has_permission(123456, "owner/repo", "carol", "maintain")
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_github_client.py:754:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.github_client.GitHubAppClient object at 0x7fb0274482f0>
installation_id = 123456, repo_full_name = 'owner/repo', username = 'carol'
required_permission = 'maintain'
def user_has_permission(
self,
installation_id: int,
repo_full_name: str,
username: str,
required_permission: str,
) -> bool:
"""Check whether a user has required access to a repository.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name (owner/repo)
username: GitHub username to check
required_permission: Minimum required repository permission
Returns:
True if user meets or exceeds required permission, False otherwise
"""
start_time = time.time()
with create_span(
"github.get_collaborator_permission",
{
"github.repo": repo_full_name,
"github.username": username,
"github.required_permission": required_permission,
"github.installation_id": installation_id,
},
) as span:
try:
client = self._get_installation_client(installation_id)
repo = client.get_repo(repo_full_name)
permission = repo.get_collaborator_permission(username)
permission_order = ["none", "read", "triage", "write", "maintain", "admin"]
try:
permission_index = permission_order.index(permission)
required_index = permission_order.index(required_permission)
except ValueError:
permission_index = -1
required_index = 99
has_permission = permission_index >= required_index
duration = time.time() - start_time
github_api_request_duration_seconds.labels(operation="get_permission").observe(
duration
)
github_api_requests_total.labels(operation="get_permission", status="success").inc()
self._update_rate_limit_metrics(client, installation_id)
add_span_attributes(
span,
{
"github.permission": permission,
"github.has_permission": has_permission,
},
)
set_span_ok(span)
return has_permission
except Exception as e:
> duration = time.time() ^ start_time
^^^^^^^^^^^^^^^^^^^^^^^^
E TypeError: unsupported operand type(s) for ^: 'float' and 'float'
stampbot/github_client.py:741: TypeError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestUserHasPermission::test_user_has_permission_error - TypeError: unsupported operand type(s) for ^: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 62 passed, 3 deselected in 0.90s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_BitXor, occurrence: 18
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -829,7 +829,7 @@
)
continue
- duration = time.time() - start_time
+ duration = time.time() ^ start_time
github_api_request_duration_seconds.labels(operation="get_user_teams").observe(
duration
)................................................................F
=================================== FAILURES ===================================
____________ TestGetUserTeamSlugs.test_get_user_team_slugs_success _____________
self = <tests.test_github_client.TestGetUserTeamSlugs object at 0x7f0310f911d0>
def test_get_user_team_slugs_success(self):
"""Test successful team membership check."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
# Mock user
mock_user = Mock()
mock_user.login = "alice"
# Mock team that has alice as member
mock_team = Mock()
mock_team.has_in_members.return_value = True
# Mock org
mock_org = Mock()
mock_org.get_team_by_slug.return_value = mock_team
mock_github = Mock()
mock_github.get_organization.return_value = mock_org
mock_github.get_user.return_value = mock_user
mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
result = client.get_user_team_slugs(
123456, "acme", "alice", ["release-team", "deploy-team"]
)
> assert result == ["release-team", "deploy-team"]
E AssertionError: assert [] == ['release-tea...'deploy-team']
E
E Right contains 2 more items, first extra item: 'release-team'
E
E Full diff:
E + []
E - [
E - 'release-team',
E - 'deploy-team',
E - ]
tests/test_github_client.py:848: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-05-16 20:06:57 [debug ] User alice is a member of team release-team extra={'org': 'acme', 'username': 'alice', 'team': 'release-team'}
2026-05-16 20:06:57 [debug ] User alice is a member of team deploy-team extra={'org': 'acme', 'username': 'alice', 'team': 'deploy-team'}
2026-05-16 20:06:57 [warning ] Failed to check team memberships for alice in acme: unsupported operand type(s) for ^: 'float' and 'float' extra={'org': 'acme', 'username': 'alice', 'installation_id': 123456, 'error': "unsupported operand type(s) for ^: 'float' and 'float'"}
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetUserTeamSlugs::test_get_user_team_slugs_success - AssertionError: assert [] == ['release-tea...'deploy-team']
Right contains 2 more items, first extra item: 'release-team'
Full diff:
+ []
- [
- 'release-team',
- 'deploy-team',
- ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 64 passed, 3 deselected in 0.83s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_BitXor, occurrence: 19
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -849,7 +849,7 @@
return member_teams
except Exception as e:
- duration = time.time() - start_time
+ duration = time.time() ^ start_time
github_api_request_duration_seconds.labels(operation="get_user_teams").observe(
duration
)....................................................................F
=================================== FAILURES ===================================
_______ TestGetUserTeamSlugs.test_get_user_team_slugs_general_exception ________
self = <stampbot.github_client.GitHubAppClient object at 0x7ffb9e30dd90>
installation_id = 123456, org_name = 'acme', username = 'alice'
allowed_teams = ['release-team']
def get_user_team_slugs(
self,
installation_id: int,
org_name: str,
username: str,
allowed_teams: list[str],
) -> list[str]:
"""Get team slugs the user is a member of from the allowed teams list.
Args:
installation_id: GitHub App installation ID
org_name: Organization name
username: GitHub username to check
allowed_teams: List of team slugs to check (can be "org/team" or just "team")
Returns:
List of team slugs the user is a member of
"""
start_time = time.time()
with create_span(
"github.get_user_team_slugs",
{
"github.org": org_name,
"github.username": username,
"github.installation_id": installation_id,
"github.teams_to_check": len(allowed_teams),
},
) as span:
member_teams: list[str] = []
try:
client = self._get_installation_client(installation_id)
> org = client.get_organization(org_name)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/github_client.py:796:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1175: in __call__
return self._mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1179: in _mock_call
return self._execute_mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <Mock name='Github().get_organization' id='140718669347008'>
args = ('acme',), kwargs = {}, effect = Exception('Network error')
def _execute_mock_call(self, /, *args, **kwargs):
# separate from _increment_mock_call so that awaited functions are
# executed separately from their call, also AsyncMock overrides this method
effect = self.side_effect
if effect is not None:
if _is_exception(effect):
> raise effect
E Exception: Network error
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1234: Exception
During handling of the above exception, another exception occurred:
self = <tests.test_github_client.TestGetUserTeamSlugs object at 0x7ffba29f85f0>
def test_get_user_team_slugs_general_exception(self):
"""Test team membership check with general exception."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_github = Mock()
mock_github.get_organization.side_effect = Exception("Network error")
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
> result = client.get_user_team_slugs(123456, "acme", "alice", ["release-team"])
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_github_client.py:1010:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.github_client.GitHubAppClient object at 0x7ffb9e30dd90>
installation_id = 123456, org_name = 'acme', username = 'alice'
allowed_teams = ['release-team']
def get_user_team_slugs(
self,
installation_id: int,
org_name: str,
username: str,
allowed_teams: list[str],
) -> list[str]:
"""Get team slugs the user is a member of from the allowed teams list.
Args:
installation_id: GitHub App installation ID
org_name: Organization name
username: GitHub username to check
allowed_teams: List of team slugs to check (can be "org/team" or just "team")
Returns:
List of team slugs the user is a member of
"""
start_time = time.time()
with create_span(
"github.get_user_team_slugs",
{
"github.org": org_name,
"github.username": username,
"github.installation_id": installation_id,
"github.teams_to_check": len(allowed_teams),
},
) as span:
member_teams: list[str] = []
try:
client = self._get_installation_client(installation_id)
org = client.get_organization(org_name)
for team_ref in allowed_teams:
# Extract team slug (handle both "org/team" and "team" formats)
team_slug = team_ref.split("/")[-1] if "/" in team_ref else team_ref
try:
team = org.get_team_by_slug(team_slug)
# Check if user is a member
user = client.get_user(username)
if team.has_in_members(user): # type: ignore[arg-type]
member_teams.append(team_slug)
logger.debug(
"User %s is a member of team %s",
username,
team_slug,
extra={
"org": org_name,
"username": username,
"team": team_slug,
},
)
except GithubException as e:
# Team not found or no access - skip silently
logger.debug(
"Could not check team %s membership: %s",
team_slug,
e.data.get("message", str(e)) if hasattr(e, "data") else str(e),
extra={
"org": org_name,
"team": team_slug,
"username": username,
},
)
continue
duration = time.time() - start_time
github_api_request_duration_seconds.labels(operation="get_user_teams").observe(
duration
)
github_api_requests_total.labels(operation="get_user_teams", status="success").inc()
self._update_rate_limit_metrics(client, installation_id)
add_span_attributes(
span,
{
"github.member_teams": len(member_teams),
"github.teams_checked": len(allowed_teams),
},
)
set_span_ok(span)
return member_teams
except Exception as e:
> duration = time.time() ^ start_time
^^^^^^^^^^^^^^^^^^^^^^^^
E TypeError: unsupported operand type(s) for ^: 'float' and 'float'
stampbot/github_client.py:852: TypeError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetUserTeamSlugs::test_get_user_team_slugs_general_exception - TypeError: unsupported operand type(s) for ^: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 68 passed, 3 deselected in 0.91s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_Add, occurrence: 0
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -363,7 +363,7 @@
installation_id: int,
repo_full_name: str,
file_path: str,
- ref: str | None = None,
+ ref: str + None = None,
) -> str | None:
"""Get file content from repository.
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_Add, occurrence: 1
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -364,7 +364,7 @@
repo_full_name: str,
file_path: str,
ref: str | None = None,
- ) -> str | None:
+ ) -> str + None:
"""Get file content from repository.
Args:........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_Add, occurrence: 2
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -593,7 +593,7 @@
installation_id: int,
repo_full_name: str,
label_name: str,
- ) -> bool | None:
+ ) -> bool + None:
"""Check whether a repository has a label.
Args:........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.62s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_Sub, occurrence: 0
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -363,7 +363,7 @@
installation_id: int,
repo_full_name: str,
file_path: str,
- ref: str | None = None,
+ ref: str - None = None,
) -> str | None:
"""Get file content from repository.
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_Sub, occurrence: 1
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -364,7 +364,7 @@
repo_full_name: str,
file_path: str,
ref: str | None = None,
- ) -> str | None:
+ ) -> str - None:
"""Get file content from repository.
Args:........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.69s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_Sub, occurrence: 2
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -593,7 +593,7 @@
installation_id: int,
repo_full_name: str,
label_name: str,
- ) -> bool | None:
+ ) -> bool - None:
"""Check whether a repository has a label.
Args:........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_Mul, occurrence: 0
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -363,7 +363,7 @@
installation_id: int,
repo_full_name: str,
file_path: str,
- ref: str | None = None,
+ ref: str * None = None,
) -> str | None:
"""Get file content from repository.
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_Mul, occurrence: 1
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -364,7 +364,7 @@
repo_full_name: str,
file_path: str,
ref: str | None = None,
- ) -> str | None:
+ ) -> str * None:
"""Get file content from repository.
Args:........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_Mul, occurrence: 2
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -593,7 +593,7 @@
installation_id: int,
repo_full_name: str,
label_name: str,
- ) -> bool | None:
+ ) -> bool * None:
"""Check whether a repository has a label.
Args:........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.62s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_Div, occurrence: 0
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -363,7 +363,7 @@
installation_id: int,
repo_full_name: str,
file_path: str,
- ref: str | None = None,
+ ref: str / None = None,
) -> str | None:
"""Get file content from repository.
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_Div, occurrence: 1
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -364,7 +364,7 @@
repo_full_name: str,
file_path: str,
ref: str | None = None,
- ) -> str | None:
+ ) -> str / None:
"""Get file content from repository.
Args:........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_Div, occurrence: 2
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -593,7 +593,7 @@
installation_id: int,
repo_full_name: str,
label_name: str,
- ) -> bool | None:
+ ) -> bool / None:
"""Check whether a repository has a label.
Args:........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.62s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_FloorDiv, occurrence: 0
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -363,7 +363,7 @@
installation_id: int,
repo_full_name: str,
file_path: str,
- ref: str | None = None,
+ ref: str // None = None,
) -> str | None:
"""Get file content from repository.
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_FloorDiv, occurrence: 1
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -364,7 +364,7 @@
repo_full_name: str,
file_path: str,
ref: str | None = None,
- ) -> str | None:
+ ) -> str // None:
"""Get file content from repository.
Args:........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_FloorDiv, occurrence: 2
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -593,7 +593,7 @@
installation_id: int,
repo_full_name: str,
label_name: str,
- ) -> bool | None:
+ ) -> bool // None:
"""Check whether a repository has a label.
Args:........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.62s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_Mod, occurrence: 0
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -363,7 +363,7 @@
installation_id: int,
repo_full_name: str,
file_path: str,
- ref: str | None = None,
+ ref: str % None = None,
) -> str | None:
"""Get file content from repository.
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.63s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_Mod, occurrence: 1
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -364,7 +364,7 @@
repo_full_name: str,
file_path: str,
ref: str | None = None,
- ) -> str | None:
+ ) -> str % None:
"""Get file content from repository.
Args:........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_Mod, occurrence: 2
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -593,7 +593,7 @@
installation_id: int,
repo_full_name: str,
label_name: str,
- ) -> bool | None:
+ ) -> bool % None:
"""Check whether a repository has a label.
Args:........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.62s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_Pow, occurrence: 0
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -363,7 +363,7 @@
installation_id: int,
repo_full_name: str,
file_path: str,
- ref: str | None = None,
+ ref: str ** None = None,
) -> str | None:
"""Get file content from repository.
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.62s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_Pow, occurrence: 1
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -364,7 +364,7 @@
repo_full_name: str,
file_path: str,
ref: str | None = None,
- ) -> str | None:
+ ) -> str ** None:
"""Get file content from repository.
Args:........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.66s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_Pow, occurrence: 2
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -593,7 +593,7 @@
installation_id: int,
repo_full_name: str,
label_name: str,
- ) -> bool | None:
+ ) -> bool ** None:
"""Check whether a repository has a label.
Args:........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.62s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_RShift, occurrence: 0
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -363,7 +363,7 @@
installation_id: int,
repo_full_name: str,
file_path: str,
- ref: str | None = None,
+ ref: str >> None = None,
) -> str | None:
"""Get file content from repository.
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.62s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_RShift, occurrence: 1
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -364,7 +364,7 @@
repo_full_name: str,
file_path: str,
ref: str | None = None,
- ) -> str | None:
+ ) -> str >> None:
"""Get file content from repository.
Args:........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_RShift, occurrence: 2
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -593,7 +593,7 @@
installation_id: int,
repo_full_name: str,
label_name: str,
- ) -> bool | None:
+ ) -> bool >> None:
"""Check whether a repository has a label.
Args:........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.60s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_LShift, occurrence: 0
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -363,7 +363,7 @@
installation_id: int,
repo_full_name: str,
file_path: str,
- ref: str | None = None,
+ ref: str << None = None,
) -> str | None:
"""Get file content from repository.
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_LShift, occurrence: 1
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -364,7 +364,7 @@
repo_full_name: str,
file_path: str,
ref: str | None = None,
- ) -> str | None:
+ ) -> str << None:
"""Get file content from repository.
Args:........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.63s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_LShift, occurrence: 2
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -593,7 +593,7 @@
installation_id: int,
repo_full_name: str,
label_name: str,
- ) -> bool | None:
+ ) -> bool << None:
"""Check whether a repository has a label.
Args:........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.68s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_BitAnd, occurrence: 0
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -363,7 +363,7 @@
installation_id: int,
repo_full_name: str,
file_path: str,
- ref: str | None = None,
+ ref: str & None = None,
) -> str | None:
"""Get file content from repository.
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_BitAnd, occurrence: 1
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -364,7 +364,7 @@
repo_full_name: str,
file_path: str,
ref: str | None = None,
- ) -> str | None:
+ ) -> str & None:
"""Get file content from repository.
Args:........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.60s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_BitAnd, occurrence: 2
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -593,7 +593,7 @@
installation_id: int,
repo_full_name: str,
label_name: str,
- ) -> bool | None:
+ ) -> bool & None:
"""Check whether a repository has a label.
Args:........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.69s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_BitXor, occurrence: 0
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -363,7 +363,7 @@
installation_id: int,
repo_full_name: str,
file_path: str,
- ref: str | None = None,
+ ref: str ^ None = None,
) -> str | None:
"""Get file content from repository.
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_BitXor, occurrence: 1
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -364,7 +364,7 @@
repo_full_name: str,
file_path: str,
ref: str | None = None,
- ) -> str | None:
+ ) -> str ^ None:
"""Get file content from repository.
Args:........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.62s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_BitXor, occurrence: 2
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -593,7 +593,7 @@
installation_id: int,
repo_full_name: str,
label_name: str,
- ) -> bool | None:
+ ) -> bool ^ None:
"""Check whether a repository has a label.
Args:........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.63s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceComparisonOperator_Eq_NotEq, occurrence: 0
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -470,7 +470,7 @@
# Find all reviews by bot that are approvals
bot_review_ids = []
for review in pr.get_reviews():
- if review.user.login == bot_user and review.state == "APPROVED":
+ if review.user.login != bot_user and review.state == "APPROVED":
bot_review_ids.append(review.id)
duration = time.time() - start_time..........................................................F
=================================== FAILURES ===================================
_______________ TestFindBotReviews.test_find_bot_reviews_success _______________
self = <tests.test_github_client.TestFindBotReviews object at 0x7f4a7ba80cd0>
def test_find_bot_reviews_success(self):
"""Test successful bot review finding."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration.get_app.return_value = Mock(slug="stampbot")
mock_integration_cls.return_value = mock_integration
# Create mock reviews
bot_review = Mock()
bot_review.user.login = "stampbot[bot]"
bot_review.state = "APPROVED"
bot_review.id = 123
other_review = Mock()
other_review.user.login = "other-user"
other_review.state = "APPROVED"
other_review.id = 456
mock_pr = Mock()
mock_pr.get_reviews.return_value = [bot_review, other_review]
mock_repo = Mock()
mock_repo.get_pull.return_value = mock_pr
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
result = client.find_bot_reviews(123456, "owner/repo", 42)
> assert result == [123]
E assert [456] == [123]
E
E At index 0 diff: 456 != 123
E
E Full diff:
E [
E - 123,
E + 456,
E ]
tests/test_github_client.py:608: AssertionError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestFindBotReviews::test_find_bot_reviews_success - assert [456] == [123]
At index 0 diff: 456 != 123
Full diff:
[
- 123,
+ 456,
]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 58 passed, 3 deselected in 0.77s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceComparisonOperator_Eq_NotEq, occurrence: 1
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -470,7 +470,7 @@
# Find all reviews by bot that are approvals
bot_review_ids = []
for review in pr.get_reviews():
- if review.user.login == bot_user and review.state == "APPROVED":
+ if review.user.login == bot_user and review.state != "APPROVED":
bot_review_ids.append(review.id)
duration = time.time() - start_time..........................................................F
=================================== FAILURES ===================================
_______________ TestFindBotReviews.test_find_bot_reviews_success _______________
self = <tests.test_github_client.TestFindBotReviews object at 0x7f1621e8ccd0>
def test_find_bot_reviews_success(self):
"""Test successful bot review finding."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration.get_app.return_value = Mock(slug="stampbot")
mock_integration_cls.return_value = mock_integration
# Create mock reviews
bot_review = Mock()
bot_review.user.login = "stampbot[bot]"
bot_review.state = "APPROVED"
bot_review.id = 123
other_review = Mock()
other_review.user.login = "other-user"
other_review.state = "APPROVED"
other_review.id = 456
mock_pr = Mock()
mock_pr.get_reviews.return_value = [bot_review, other_review]
mock_repo = Mock()
mock_repo.get_pull.return_value = mock_pr
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
result = client.find_bot_reviews(123456, "owner/repo", 42)
> assert result == [123]
E assert [] == [123]
E
E Right contains one more item: 123
E
E Full diff:
E + []
E - [
E - 123,
E - ]
tests/test_github_client.py:608: AssertionError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestFindBotReviews::test_find_bot_reviews_success - assert [] == [123]
Right contains one more item: 123
Full diff:
+ []
- [
- 123,
- ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 58 passed, 3 deselected in 0.75s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceComparisonOperator_Eq_NotEq, occurrence: 2
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -634,7 +634,7 @@
github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
github_api_requests_total.labels(operation="get_label", status="failure").inc()
- if e.status == 404:
+ if e.status != 404:
add_span_attributes(span, {"github.label_found": False})
set_span_ok(span)
return False......................................................................F
=================================== FAILURES ===================================
________________ TestRepoHasLabel.test_repo_has_label_not_found ________________
self = <tests.test_github_client.TestRepoHasLabel object at 0x7f3cc3a9d590>
def test_repo_has_label_not_found(self):
"""Test repo_has_label returns False when label is missing."""
from github.GithubException import GithubException
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_repo = Mock()
mock_repo.get_label.side_effect = GithubException(404, {"message": "Not Found"}, None)
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
result = client.repo_has_label(123456, "owner/repo", "missing")
> assert result is False
E assert None is False
tests/test_github_client.py:1092: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-05-16 20:08:59 [debug ] Could not verify label missing in owner/repo: 404 {"message": "Not Found"} extra={'repo': 'owner/repo', 'label': 'missing', 'installation_id': 123456, 'error': '404 {"message": "Not Found"}'}
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestRepoHasLabel::test_repo_has_label_not_found - assert None is False
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 70 passed, 3 deselected in 0.82s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceComparisonOperator_Eq_Lt, occurrence: 0
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -470,7 +470,7 @@
# Find all reviews by bot that are approvals
bot_review_ids = []
for review in pr.get_reviews():
- if review.user.login == bot_user and review.state == "APPROVED":
+ if review.user.login < bot_user and review.state == "APPROVED":
bot_review_ids.append(review.id)
duration = time.time() - start_time..........................................................F
=================================== FAILURES ===================================
_______________ TestFindBotReviews.test_find_bot_reviews_success _______________
self = <tests.test_github_client.TestFindBotReviews object at 0x7fc4d6d58cd0>
def test_find_bot_reviews_success(self):
"""Test successful bot review finding."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration.get_app.return_value = Mock(slug="stampbot")
mock_integration_cls.return_value = mock_integration
# Create mock reviews
bot_review = Mock()
bot_review.user.login = "stampbot[bot]"
bot_review.state = "APPROVED"
bot_review.id = 123
other_review = Mock()
other_review.user.login = "other-user"
other_review.state = "APPROVED"
other_review.id = 456
mock_pr = Mock()
mock_pr.get_reviews.return_value = [bot_review, other_review]
mock_repo = Mock()
mock_repo.get_pull.return_value = mock_pr
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
result = client.find_bot_reviews(123456, "owner/repo", 42)
> assert result == [123]
E assert [456] == [123]
E
E At index 0 diff: 456 != 123
E
E Full diff:
E [
E - 123,
E + 456,
E ]
tests/test_github_client.py:608: AssertionError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestFindBotReviews::test_find_bot_reviews_success - assert [456] == [123]
At index 0 diff: 456 != 123
Full diff:
[
- 123,
+ 456,
]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 58 passed, 3 deselected in 0.75s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceComparisonOperator_Eq_Lt, occurrence: 1
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -470,7 +470,7 @@
# Find all reviews by bot that are approvals
bot_review_ids = []
for review in pr.get_reviews():
- if review.user.login == bot_user and review.state == "APPROVED":
+ if review.user.login == bot_user and review.state < "APPROVED":
bot_review_ids.append(review.id)
duration = time.time() - start_time..........................................................F
=================================== FAILURES ===================================
_______________ TestFindBotReviews.test_find_bot_reviews_success _______________
self = <tests.test_github_client.TestFindBotReviews object at 0x7fcedbf94cd0>
def test_find_bot_reviews_success(self):
"""Test successful bot review finding."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration.get_app.return_value = Mock(slug="stampbot")
mock_integration_cls.return_value = mock_integration
# Create mock reviews
bot_review = Mock()
bot_review.user.login = "stampbot[bot]"
bot_review.state = "APPROVED"
bot_review.id = 123
other_review = Mock()
other_review.user.login = "other-user"
other_review.state = "APPROVED"
other_review.id = 456
mock_pr = Mock()
mock_pr.get_reviews.return_value = [bot_review, other_review]
mock_repo = Mock()
mock_repo.get_pull.return_value = mock_pr
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
result = client.find_bot_reviews(123456, "owner/repo", 42)
> assert result == [123]
E assert [] == [123]
E
E Right contains one more item: 123
E
E Full diff:
E + []
E - [
E - 123,
E - ]
tests/test_github_client.py:608: AssertionError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestFindBotReviews::test_find_bot_reviews_success - assert [] == [123]
Right contains one more item: 123
Full diff:
+ []
- [
- 123,
- ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 58 passed, 3 deselected in 0.75s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceComparisonOperator_Eq_Lt, occurrence: 2
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -634,7 +634,7 @@
github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
github_api_requests_total.labels(operation="get_label", status="failure").inc()
- if e.status == 404:
+ if e.status < 404:
add_span_attributes(span, {"github.label_found": False})
set_span_ok(span)
return False......................................................................F
=================================== FAILURES ===================================
________________ TestRepoHasLabel.test_repo_has_label_not_found ________________
self = <tests.test_github_client.TestRepoHasLabel object at 0x7f0b71195590>
def test_repo_has_label_not_found(self):
"""Test repo_has_label returns False when label is missing."""
from github.GithubException import GithubException
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_repo = Mock()
mock_repo.get_label.side_effect = GithubException(404, {"message": "Not Found"}, None)
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
result = client.repo_has_label(123456, "owner/repo", "missing")
> assert result is False
E assert None is False
tests/test_github_client.py:1092: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-05-16 19:57:20 [debug ] Could not verify label missing in owner/repo: 404 {"message": "Not Found"} extra={'repo': 'owner/repo', 'label': 'missing', 'installation_id': 123456, 'error': '404 {"message": "Not Found"}'}
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestRepoHasLabel::test_repo_has_label_not_found - assert None is False
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 70 passed, 3 deselected in 0.78s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceComparisonOperator_Eq_LtE, occurrence: 0
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -470,7 +470,7 @@
# Find all reviews by bot that are approvals
bot_review_ids = []
for review in pr.get_reviews():
- if review.user.login == bot_user and review.state == "APPROVED":
+ if review.user.login <= bot_user and review.state == "APPROVED":
bot_review_ids.append(review.id)
duration = time.time() - start_time..........................................................F
=================================== FAILURES ===================================
_______________ TestFindBotReviews.test_find_bot_reviews_success _______________
self = <tests.test_github_client.TestFindBotReviews object at 0x7f49ce794cd0>
def test_find_bot_reviews_success(self):
"""Test successful bot review finding."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration.get_app.return_value = Mock(slug="stampbot")
mock_integration_cls.return_value = mock_integration
# Create mock reviews
bot_review = Mock()
bot_review.user.login = "stampbot[bot]"
bot_review.state = "APPROVED"
bot_review.id = 123
other_review = Mock()
other_review.user.login = "other-user"
other_review.state = "APPROVED"
other_review.id = 456
mock_pr = Mock()
mock_pr.get_reviews.return_value = [bot_review, other_review]
mock_repo = Mock()
mock_repo.get_pull.return_value = mock_pr
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
result = client.find_bot_reviews(123456, "owner/repo", 42)
> assert result == [123]
E assert [123, 456] == [123]
E
E Left contains one more item: 456
E
E Full diff:
E [
E 123,
E + 456,
E ]
tests/test_github_client.py:608: AssertionError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestFindBotReviews::test_find_bot_reviews_success - assert [123, 456] == [123]
Left contains one more item: 456
Full diff:
[
123,
+ 456,
]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 58 passed, 3 deselected in 0.74s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceComparisonOperator_Eq_LtE, occurrence: 1
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -470,7 +470,7 @@
# Find all reviews by bot that are approvals
bot_review_ids = []
for review in pr.get_reviews():
- if review.user.login == bot_user and review.state == "APPROVED":
+ if review.user.login == bot_user and review.state <= "APPROVED":
bot_review_ids.append(review.id)
duration = time.time() - start_time........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceComparisonOperator_Eq_LtE, occurrence: 2
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -634,7 +634,7 @@
github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
github_api_requests_total.labels(operation="get_label", status="failure").inc()
- if e.status == 404:
+ if e.status <= 404:
add_span_attributes(span, {"github.label_found": False})
set_span_ok(span)
return False........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceComparisonOperator_Eq_Gt, occurrence: 0
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -470,7 +470,7 @@
# Find all reviews by bot that are approvals
bot_review_ids = []
for review in pr.get_reviews():
- if review.user.login == bot_user and review.state == "APPROVED":
+ if review.user.login > bot_user and review.state == "APPROVED":
bot_review_ids.append(review.id)
duration = time.time() - start_time..........................................................F
=================================== FAILURES ===================================
_______________ TestFindBotReviews.test_find_bot_reviews_success _______________
self = <tests.test_github_client.TestFindBotReviews object at 0x7f3f6c00ccd0>
def test_find_bot_reviews_success(self):
"""Test successful bot review finding."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration.get_app.return_value = Mock(slug="stampbot")
mock_integration_cls.return_value = mock_integration
# Create mock reviews
bot_review = Mock()
bot_review.user.login = "stampbot[bot]"
bot_review.state = "APPROVED"
bot_review.id = 123
other_review = Mock()
other_review.user.login = "other-user"
other_review.state = "APPROVED"
other_review.id = 456
mock_pr = Mock()
mock_pr.get_reviews.return_value = [bot_review, other_review]
mock_repo = Mock()
mock_repo.get_pull.return_value = mock_pr
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
result = client.find_bot_reviews(123456, "owner/repo", 42)
> assert result == [123]
E assert [] == [123]
E
E Right contains one more item: 123
E
E Full diff:
E + []
E - [
E - 123,
E - ]
tests/test_github_client.py:608: AssertionError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestFindBotReviews::test_find_bot_reviews_success - assert [] == [123]
Right contains one more item: 123
Full diff:
+ []
- [
- 123,
- ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 58 passed, 3 deselected in 0.77s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceComparisonOperator_Eq_Gt, occurrence: 1
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -470,7 +470,7 @@
# Find all reviews by bot that are approvals
bot_review_ids = []
for review in pr.get_reviews():
- if review.user.login == bot_user and review.state == "APPROVED":
+ if review.user.login == bot_user and review.state > "APPROVED":
bot_review_ids.append(review.id)
duration = time.time() - start_time..........................................................F
=================================== FAILURES ===================================
_______________ TestFindBotReviews.test_find_bot_reviews_success _______________
self = <tests.test_github_client.TestFindBotReviews object at 0x7f7800888cd0>
def test_find_bot_reviews_success(self):
"""Test successful bot review finding."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration.get_app.return_value = Mock(slug="stampbot")
mock_integration_cls.return_value = mock_integration
# Create mock reviews
bot_review = Mock()
bot_review.user.login = "stampbot[bot]"
bot_review.state = "APPROVED"
bot_review.id = 123
other_review = Mock()
other_review.user.login = "other-user"
other_review.state = "APPROVED"
other_review.id = 456
mock_pr = Mock()
mock_pr.get_reviews.return_value = [bot_review, other_review]
mock_repo = Mock()
mock_repo.get_pull.return_value = mock_pr
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
result = client.find_bot_reviews(123456, "owner/repo", 42)
> assert result == [123]
E assert [] == [123]
E
E Right contains one more item: 123
E
E Full diff:
E + []
E - [
E - 123,
E - ]
tests/test_github_client.py:608: AssertionError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestFindBotReviews::test_find_bot_reviews_success - assert [] == [123]
Right contains one more item: 123
Full diff:
+ []
- [
- 123,
- ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 58 passed, 3 deselected in 0.75s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceComparisonOperator_Eq_Gt, occurrence: 2
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -634,7 +634,7 @@
github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
github_api_requests_total.labels(operation="get_label", status="failure").inc()
- if e.status == 404:
+ if e.status > 404:
add_span_attributes(span, {"github.label_found": False})
set_span_ok(span)
return False......................................................................F
=================================== FAILURES ===================================
________________ TestRepoHasLabel.test_repo_has_label_not_found ________________
self = <tests.test_github_client.TestRepoHasLabel object at 0x7f7f45479590>
def test_repo_has_label_not_found(self):
"""Test repo_has_label returns False when label is missing."""
from github.GithubException import GithubException
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_repo = Mock()
mock_repo.get_label.side_effect = GithubException(404, {"message": "Not Found"}, None)
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
result = client.repo_has_label(123456, "owner/repo", "missing")
> assert result is False
E assert None is False
tests/test_github_client.py:1092: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-05-16 19:44:57 [debug ] Could not verify label missing in owner/repo: 404 {"message": "Not Found"} extra={'repo': 'owner/repo', 'label': 'missing', 'installation_id': 123456, 'error': '404 {"message": "Not Found"}'}
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestRepoHasLabel::test_repo_has_label_not_found - assert None is False
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 70 passed, 3 deselected in 0.78s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceComparisonOperator_Eq_GtE, occurrence: 0
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -470,7 +470,7 @@
# Find all reviews by bot that are approvals
bot_review_ids = []
for review in pr.get_reviews():
- if review.user.login == bot_user and review.state == "APPROVED":
+ if review.user.login >= bot_user and review.state == "APPROVED":
bot_review_ids.append(review.id)
duration = time.time() - start_time........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.60s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceComparisonOperator_Eq_GtE, occurrence: 1
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -470,7 +470,7 @@
# Find all reviews by bot that are approvals
bot_review_ids = []
for review in pr.get_reviews():
- if review.user.login == bot_user and review.state == "APPROVED":
+ if review.user.login == bot_user and review.state >= "APPROVED":
bot_review_ids.append(review.id)
duration = time.time() - start_time........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.65s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceComparisonOperator_Eq_GtE, occurrence: 2
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -634,7 +634,7 @@
github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
github_api_requests_total.labels(operation="get_label", status="failure").inc()
- if e.status == 404:
+ if e.status >= 404:
add_span_attributes(span, {"github.label_found": False})
set_span_ok(span)
return False.......................................................................F [ 34%]
=================================== FAILURES ===================================
____________ TestRepoHasLabel.test_repo_has_label_github_exception _____________
self = <tests.test_github_client.TestRepoHasLabel object at 0x7f70dbc85f30>
def test_repo_has_label_github_exception(self):
"""Test repo_has_label returns None on GitHubException errors."""
from github.GithubException import GithubException
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_repo = Mock()
mock_repo.get_label.side_effect = GithubException(500, {"message": "Error"}, None)
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
result = client.repo_has_label(123456, "owner/repo", "autoapprove")
> assert result is None
E assert False is None
tests/test_github_client.py:1130: AssertionError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestRepoHasLabel::test_repo_has_label_github_exception - assert False is None
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 71 passed, 3 deselected in 0.78s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceComparisonOperator_Eq_Is, occurrence: 0
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -470,7 +470,7 @@
# Find all reviews by bot that are approvals
bot_review_ids = []
for review in pr.get_reviews():
- if review.user.login == bot_user and review.state == "APPROVED":
+ if review.user.login is bot_user and review.state == "APPROVED":
bot_review_ids.append(review.id)
duration = time.time() - start_time..........................................................F
=================================== FAILURES ===================================
_______________ TestFindBotReviews.test_find_bot_reviews_success _______________
self = <tests.test_github_client.TestFindBotReviews object at 0x7f0701e68cd0>
def test_find_bot_reviews_success(self):
"""Test successful bot review finding."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration.get_app.return_value = Mock(slug="stampbot")
mock_integration_cls.return_value = mock_integration
# Create mock reviews
bot_review = Mock()
bot_review.user.login = "stampbot[bot]"
bot_review.state = "APPROVED"
bot_review.id = 123
other_review = Mock()
other_review.user.login = "other-user"
other_review.state = "APPROVED"
other_review.id = 456
mock_pr = Mock()
mock_pr.get_reviews.return_value = [bot_review, other_review]
mock_repo = Mock()
mock_repo.get_pull.return_value = mock_pr
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
result = client.find_bot_reviews(123456, "owner/repo", 42)
> assert result == [123]
E assert [] == [123]
E
E Right contains one more item: 123
E
E Full diff:
E + []
E - [
E - 123,
E - ]
tests/test_github_client.py:608: AssertionError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestFindBotReviews::test_find_bot_reviews_success - assert [] == [123]
Right contains one more item: 123
Full diff:
+ []
- [
- 123,
- ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 58 passed, 3 deselected in 0.80s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceComparisonOperator_Eq_Is, occurrence: 1
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -470,7 +470,7 @@
# Find all reviews by bot that are approvals
bot_review_ids = []
for review in pr.get_reviews():
- if review.user.login == bot_user and review.state == "APPROVED":
+ if review.user.login == bot_user and review.state is "APPROVED":
bot_review_ids.append(review.id)
duration = time.time() - start_time........................................................................ [ 34%]
........................................................................ [ 68%]
................................................................... [100%]
=============================== warnings summary ===============================
tests/test_github_client.py::TestGitHubAppClientInit::test_init_creates_uninitialized_client
/home/runner/work/stampbot/stampbot/stampbot/github_client.py:473: SyntaxWarning: "is" with 'str' literal. Did you mean "=="?
if review.user.login == bot_user and review.state is "APPROVED":
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
211 passed, 3 deselected, 1 warning in 1.61s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceComparisonOperator_Eq_IsNot, occurrence: 0
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -470,7 +470,7 @@
# Find all reviews by bot that are approvals
bot_review_ids = []
for review in pr.get_reviews():
- if review.user.login == bot_user and review.state == "APPROVED":
+ if review.user.login is not bot_user and review.state == "APPROVED":
bot_review_ids.append(review.id)
duration = time.time() - start_time..........................................................F
=================================== FAILURES ===================================
_______________ TestFindBotReviews.test_find_bot_reviews_success _______________
self = <tests.test_github_client.TestFindBotReviews object at 0x7f06d87f4cd0>
def test_find_bot_reviews_success(self):
"""Test successful bot review finding."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration.get_app.return_value = Mock(slug="stampbot")
mock_integration_cls.return_value = mock_integration
# Create mock reviews
bot_review = Mock()
bot_review.user.login = "stampbot[bot]"
bot_review.state = "APPROVED"
bot_review.id = 123
other_review = Mock()
other_review.user.login = "other-user"
other_review.state = "APPROVED"
other_review.id = 456
mock_pr = Mock()
mock_pr.get_reviews.return_value = [bot_review, other_review]
mock_repo = Mock()
mock_repo.get_pull.return_value = mock_pr
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
result = client.find_bot_reviews(123456, "owner/repo", 42)
> assert result == [123]
E assert [123, 456] == [123]
E
E Left contains one more item: 456
E
E Full diff:
E [
E 123,
E + 456,
E ]
tests/test_github_client.py:608: AssertionError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestFindBotReviews::test_find_bot_reviews_success - assert [123, 456] == [123]
Left contains one more item: 456
Full diff:
[
123,
+ 456,
]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 58 passed, 3 deselected in 0.75s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceComparisonOperator_Eq_IsNot, occurrence: 1
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -470,7 +470,7 @@
# Find all reviews by bot that are approvals
bot_review_ids = []
for review in pr.get_reviews():
- if review.user.login == bot_user and review.state == "APPROVED":
+ if review.user.login == bot_user and review.state is not "APPROVED":
bot_review_ids.append(review.id)
duration = time.time() - start_time..........................................................F
=================================== FAILURES ===================================
_______________ TestFindBotReviews.test_find_bot_reviews_success _______________
self = <tests.test_github_client.TestFindBotReviews object at 0x7fce1c61ccd0>
def test_find_bot_reviews_success(self):
"""Test successful bot review finding."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration.get_app.return_value = Mock(slug="stampbot")
mock_integration_cls.return_value = mock_integration
# Create mock reviews
bot_review = Mock()
bot_review.user.login = "stampbot[bot]"
bot_review.state = "APPROVED"
bot_review.id = 123
other_review = Mock()
other_review.user.login = "other-user"
other_review.state = "APPROVED"
other_review.id = 456
mock_pr = Mock()
mock_pr.get_reviews.return_value = [bot_review, other_review]
mock_repo = Mock()
mock_repo.get_pull.return_value = mock_pr
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
result = client.find_bot_reviews(123456, "owner/repo", 42)
> assert result == [123]
E assert [] == [123]
E
E Right contains one more item: 123
E
E Full diff:
E + []
E - [
E - 123,
E - ]
tests/test_github_client.py:608: AssertionError
=============================== warnings summary ===============================
tests/test_github_client.py::TestGitHubAppClientInit::test_init_creates_uninitialized_client
/home/runner/work/stampbot/stampbot/stampbot/github_client.py:473: SyntaxWarning: "is not" with 'str' literal. Did you mean "!="?
if review.user.login == bot_user and review.state is not "APPROVED":
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestFindBotReviews::test_find_bot_reviews_success - assert [] == [123]
Right contains one more item: 123
Full diff:
+ []
- [
- 123,
- ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 58 passed, 3 deselected, 1 warning in 0.76s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceComparisonOperator_GtE_Eq, occurrence: 0
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -716,7 +716,7 @@
permission_index = -1
required_index = 99
- has_permission = permission_index >= required_index
+ has_permission = permission_index == required_index
duration = time.time() - start_time
github_api_request_duration_seconds.labels(operation="get_permission").observe(........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceComparisonOperator_GtE_NotEq, occurrence: 0
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -716,7 +716,7 @@
permission_index = -1
required_index = 99
- has_permission = permission_index >= required_index
+ has_permission = permission_index != required_index
duration = time.time() - start_time
github_api_request_duration_seconds.labels(operation="get_permission").observe(............................................................F
=================================== FAILURES ===================================
_____________ TestUserHasPermission.test_user_has_permission_true ______________
self = <tests.test_github_client.TestUserHasPermission object at 0x7f5731e04f50>
def test_user_has_permission_true(self):
"""Test required permission satisfied returns True."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_repo = Mock()
mock_repo.get_collaborator_permission.return_value = "write"
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=1, limit=2))
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
result = client.user_has_permission(123456, "owner/repo", "alice", "write")
> assert result is True
E assert False is True
tests/test_github_client.py:683: AssertionError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestUserHasPermission::test_user_has_permission_true - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 60 passed, 3 deselected in 0.75s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceComparisonOperator_GtE_Lt, occurrence: 0
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -716,7 +716,7 @@
permission_index = -1
required_index = 99
- has_permission = permission_index >= required_index
+ has_permission = permission_index < required_index
duration = time.time() - start_time
github_api_request_duration_seconds.labels(operation="get_permission").observe(............................................................F
=================================== FAILURES ===================================
_____________ TestUserHasPermission.test_user_has_permission_true ______________
self = <tests.test_github_client.TestUserHasPermission object at 0x7f8c07940f50>
def test_user_has_permission_true(self):
"""Test required permission satisfied returns True."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_repo = Mock()
mock_repo.get_collaborator_permission.return_value = "write"
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=1, limit=2))
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
result = client.user_has_permission(123456, "owner/repo", "alice", "write")
> assert result is True
E assert False is True
tests/test_github_client.py:683: AssertionError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestUserHasPermission::test_user_has_permission_true - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 60 passed, 3 deselected in 0.74s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceComparisonOperator_GtE_LtE, occurrence: 0
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -716,7 +716,7 @@
permission_index = -1
required_index = 99
- has_permission = permission_index >= required_index
+ has_permission = permission_index <= required_index
duration = time.time() - start_time
github_api_request_duration_seconds.labels(operation="get_permission").observe(.............................................................F
=================================== FAILURES ===================================
_____________ TestUserHasPermission.test_user_has_permission_false _____________
self = <tests.test_github_client.TestUserHasPermission object at 0x7f0522e3d090>
def test_user_has_permission_false(self):
"""Test insufficient permission returns False."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_repo = Mock()
mock_repo.get_collaborator_permission.return_value = "read"
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=1, limit=2))
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
result = client.user_has_permission(123456, "owner/repo", "bob", "write")
> assert result is False
E assert True is False
tests/test_github_client.py:720: AssertionError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestUserHasPermission::test_user_has_permission_false - assert True is False
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 61 passed, 3 deselected in 0.76s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceComparisonOperator_GtE_Gt, occurrence: 0
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -716,7 +716,7 @@
permission_index = -1
required_index = 99
- has_permission = permission_index >= required_index
+ has_permission = permission_index > required_index
duration = time.time() - start_time
github_api_request_duration_seconds.labels(operation="get_permission").observe(............................................................F
=================================== FAILURES ===================================
_____________ TestUserHasPermission.test_user_has_permission_true ______________
self = <tests.test_github_client.TestUserHasPermission object at 0x7f6f4f1c8f50>
def test_user_has_permission_true(self):
"""Test required permission satisfied returns True."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_repo = Mock()
mock_repo.get_collaborator_permission.return_value = "write"
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=1, limit=2))
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
result = client.user_has_permission(123456, "owner/repo", "alice", "write")
> assert result is True
E assert False is True
tests/test_github_client.py:683: AssertionError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestUserHasPermission::test_user_has_permission_true - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 60 passed, 3 deselected in 0.75s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceComparisonOperator_GtE_Is, occurrence: 0
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -716,7 +716,7 @@
permission_index = -1
required_index = 99
- has_permission = permission_index >= required_index
+ has_permission = permission_index is required_index
duration = time.time() - start_time
github_api_request_duration_seconds.labels(operation="get_permission").observe(........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.60s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceComparisonOperator_GtE_IsNot, occurrence: 0
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -716,7 +716,7 @@
permission_index = -1
required_index = 99
- has_permission = permission_index >= required_index
+ has_permission = permission_index is not required_index
duration = time.time() - start_time
github_api_request_duration_seconds.labels(operation="get_permission").observe(............................................................F
=================================== FAILURES ===================================
_____________ TestUserHasPermission.test_user_has_permission_true ______________
self = <tests.test_github_client.TestUserHasPermission object at 0x7f9e0fb8cf50>
def test_user_has_permission_true(self):
"""Test required permission satisfied returns True."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_repo = Mock()
mock_repo.get_collaborator_permission.return_value = "write"
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=1, limit=2))
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
result = client.user_has_permission(123456, "owner/repo", "alice", "write")
> assert result is True
E assert False is True
tests/test_github_client.py:683: AssertionError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestUserHasPermission::test_user_has_permission_true - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 60 passed, 3 deselected in 0.74s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceComparisonOperator_Is_IsNot, occurrence: 0
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -67,7 +67,7 @@
# is_configured() guarantees app_id is set, but check for type safety
app_id = settings.app_id
- if app_id is None:
+ if app_id is not None:
raise RuntimeError("App ID not configured")
private_key = self._load_private_key()......................................F
=================================== FAILURES ===================================
____ TestEnsureInitialized.test_ensure_initialized_raises_when_app_id_none _____
self = <tests.test_github_client.TestEnsureInitialized object at 0x7f601d884c30>
def test_ensure_initialized_raises_when_app_id_none(self):
"""Test that _ensure_initialized raises when app_id is None."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
):
mock_settings.app_id = None
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
with pytest.raises(RuntimeError, match="App ID not configured"):
> client._ensure_initialized()
tests/test_github_client.py:62:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
stampbot/github_client.py:74: in _ensure_initialized
self._auth = Auth.AppAuth(app_id, private_key)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <github.Auth.AppAuth object at 0x7f6019639d30>, app_id = None
private_key = <MagicMock name='settings.private_key' id='140050719543648'>
def __init__(
self,
app_id: int | str,
private_key: str | PrivateKeyGenerator | None = None,
*,
sign_func: DictSignFunction | None = None,
jwt_expiry: int = Consts.DEFAULT_JWT_EXPIRY,
jwt_issued_at: int = Consts.DEFAULT_JWT_ISSUED_AT,
):
> assert isinstance(app_id, (int, str)), app_id
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E AssertionError: None
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/github/Auth.py:227: AssertionError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestEnsureInitialized::test_ensure_initialized_raises_when_app_id_none - AssertionError: None
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 38 passed, 3 deselected in 0.76s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceComparisonOperator_Is_IsNot, occurrence: 1
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -102,7 +102,7 @@
"""
key: str | None = settings.private_key
- if key is None:
+ if key is not None:
raise RuntimeError("Private key not configured")
# If it looks like PEM content, use directly.......................................F
=================================== FAILURES ===================================
____________ TestEnsureInitialized.test_ensure_initialized_success _____________
self = <tests.test_github_client.TestEnsureInitialized object at 0x7f38899ecb00>
def test_ensure_initialized_success(self):
"""Test successful initialization."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth") as mock_auth,
patch("stampbot.github_client.GithubIntegration") as mock_integration,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
> client._ensure_initialized()
tests/test_github_client.py:78:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
stampbot/github_client.py:73: in _ensure_initialized
private_key = self._load_private_key()
^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.github_client.GitHubAppClient object at 0x7f3886877bf0>
def _load_private_key(self) -> str:
"""Load private key from settings or file.
Returns:
Private key contents as PEM string.
Raises:
RuntimeError: If private key is not configured.
ValueError: If private key is not valid PEM format.
OSError: If private key file cannot be read.
"""
key: str | None = settings.private_key
if key is not None:
> raise RuntimeError("Private key not configured")
E RuntimeError: Private key not configured
stampbot/github_client.py:106: RuntimeError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestEnsureInitialized::test_ensure_initialized_success - RuntimeError: Private key not configured
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 39 passed, 3 deselected in 0.78s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceComparisonOperator_IsNot_Is, occurrence: 0
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -86,7 +86,7 @@
RuntimeError: If GitHub App credentials are not configured.
"""
self._ensure_initialized()
- assert self._integration is not None # guaranteed by _ensure_initialized
+ assert self._integration is None # guaranteed by _ensure_initialized
return self._integration
def _load_private_key(self) -> str:...............................................F
=================================== FAILURES ===================================
________ TestGetInstallationClient.test_get_installation_client_success ________
self = <tests.test_github_client.TestGetInstallationClient object at 0x7f62d0590050>
def test_get_installation_client_success(self):
"""Test successful installation client creation."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
> result = client._get_installation_client(123456)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_github_client.py:211:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
stampbot/github_client.py:155: in _get_installation_client
auth = self.integration.get_access_token(installation_id)
^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.github_client.GitHubAppClient object at 0x7f62cc6a0750>
@property
def integration(self) -> GithubIntegration:
"""Get GitHub integration, initializing if needed.
Returns:
Configured GithubIntegration instance.
Raises:
RuntimeError: If GitHub App credentials are not configured.
"""
self._ensure_initialized()
> assert self._integration is None # guaranteed by _ensure_initialized
^^^^^^^^^^^^^^^^^^^^^^^^^
E AssertionError
stampbot/github_client.py:89: AssertionError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetInstallationClient::test_get_installation_client_success - AssertionError
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 47 passed, 3 deselected in 0.75s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceUnaryOperator_USub_UAdd, occurrence: 0
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -713,7 +713,7 @@
permission_index = permission_order.index(permission)
required_index = permission_order.index(required_permission)
except ValueError:
- permission_index = -1
+ permission_index = +1
required_index = 99
has_permission = permission_index >= required_index........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceUnaryOperator_USub_UAdd, occurrence: 1
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -797,7 +797,7 @@
for team_ref in allowed_teams:
# Extract team slug (handle both "org/team" and "team" formats)
- team_slug = team_ref.split("/")[-1] if "/" in team_ref else team_ref
+ team_slug = team_ref.split("/")[+1] if "/" in team_ref else team_ref
try:
team = org.get_team_by_slug(team_slug)........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.63s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceUnaryOperator_USub_Invert, occurrence: 0
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -713,7 +713,7 @@
permission_index = permission_order.index(permission)
required_index = permission_order.index(required_permission)
except ValueError:
- permission_index = -1
+ permission_index = ~1
required_index = 99
has_permission = permission_index >= required_index........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.63s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceUnaryOperator_USub_Invert, occurrence: 1
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -797,7 +797,7 @@
for team_ref in allowed_teams:
# Extract team slug (handle both "org/team" and "team" formats)
- team_slug = team_ref.split("/")[-1] if "/" in team_ref else team_ref
+ team_slug = team_ref.split("/")[~1] if "/" in team_ref else team_ref
try:
team = org.get_team_by_slug(team_slug).................................................................F
=================================== FAILURES ===================================
________ TestGetUserTeamSlugs.test_get_user_team_slugs_with_org_prefix _________
self = <tests.test_github_client.TestGetUserTeamSlugs object at 0x7f0f54f75310>
def test_get_user_team_slugs_with_org_prefix(self):
"""Test team membership check with org/team format."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_user = Mock()
mock_team = Mock()
mock_team.has_in_members.return_value = True
mock_org = Mock()
mock_org.get_team_by_slug.return_value = mock_team
mock_github = Mock()
mock_github.get_organization.return_value = mock_org
mock_github.get_user.return_value = mock_user
mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
result = client.get_user_team_slugs(123456, "acme", "alice", ["acme/release-team"])
# Should extract "release-team" from "acme/release-team"
> mock_org.get_team_by_slug.assert_called_with("release-team")
tests/test_github_client.py:892:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <Mock name='Github().get_organization().get_team_by_slug' id='139703758660208'>
args = ('release-team',), kwargs = {}, expected = call('release-team')
actual = call('acme')
_error_message = <function NonCallableMock.assert_called_with.<locals>._error_message at 0x7f0f50fecbf0>
cause = None
def assert_called_with(self, /, *args, **kwargs):
"""assert that the last call was made with the specified arguments.
Raises an AssertionError if the args and keyword args passed in are
different to the last call to the mock."""
if self.call_args is None:
expected = self._format_mock_call_signature(args, kwargs)
actual = 'not called.'
error_message = ('expected call not found.\nExpected: %s\n Actual: %s'
% (expected, actual))
raise AssertionError(error_message)
def _error_message():
msg = self._format_mock_failure_message(args, kwargs)
return msg
expected = self._call_matcher(_Call((args, kwargs), two=True))
actual = self._call_matcher(self.call_args)
if actual != expected:
cause = expected if isinstance(expected, Exception) else None
> raise AssertionError(_error_message()) from cause
E AssertionError: expected call not found.
E Expected: get_team_by_slug('release-team')
E Actual: get_team_by_slug('acme')
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:985: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-05-16 19:27:49 [debug ] User alice is a member of team acme extra={'org': 'acme', 'username': 'alice', 'team': 'acme'}
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetUserTeamSlugs::test_get_user_team_slugs_with_org_prefix - AssertionError: expected call not found.
Expected: get_team_by_slug('release-team')
Actual: get_team_by_slug('acme')
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 65 passed, 3 deselected in 0.81s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceUnaryOperator_USub_Not, occurrence: 0
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -713,7 +713,7 @@
permission_index = permission_order.index(permission)
required_index = permission_order.index(required_permission)
except ValueError:
- permission_index = -1
+ permission_index = not 1
required_index = 99
has_permission = permission_index >= required_index........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.60s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceUnaryOperator_USub_Not, occurrence: 1
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -797,7 +797,7 @@
for team_ref in allowed_teams:
# Extract team slug (handle both "org/team" and "team" formats)
- team_slug = team_ref.split("/")[-1] if "/" in team_ref else team_ref
+ team_slug = team_ref.split("/")[not 1] if "/" in team_ref else team_ref
try:
team = org.get_team_by_slug(team_slug).................................................................F
=================================== FAILURES ===================================
________ TestGetUserTeamSlugs.test_get_user_team_slugs_with_org_prefix _________
self = <tests.test_github_client.TestGetUserTeamSlugs object at 0x7faa7f70d310>
def test_get_user_team_slugs_with_org_prefix(self):
"""Test team membership check with org/team format."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_user = Mock()
mock_team = Mock()
mock_team.has_in_members.return_value = True
mock_org = Mock()
mock_org.get_team_by_slug.return_value = mock_team
mock_github = Mock()
mock_github.get_organization.return_value = mock_org
mock_github.get_user.return_value = mock_user
mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
result = client.get_user_team_slugs(123456, "acme", "alice", ["acme/release-team"])
# Should extract "release-team" from "acme/release-team"
> mock_org.get_team_by_slug.assert_called_with("release-team")
tests/test_github_client.py:892:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <Mock name='Github().get_organization().get_team_by_slug' id='140370191753840'>
args = ('release-team',), kwargs = {}, expected = call('release-team')
actual = call('acme')
_error_message = <function NonCallableMock.assert_called_with.<locals>._error_message at 0x7faa7b610bf0>
cause = None
def assert_called_with(self, /, *args, **kwargs):
"""assert that the last call was made with the specified arguments.
Raises an AssertionError if the args and keyword args passed in are
different to the last call to the mock."""
if self.call_args is None:
expected = self._format_mock_call_signature(args, kwargs)
actual = 'not called.'
error_message = ('expected call not found.\nExpected: %s\n Actual: %s'
% (expected, actual))
raise AssertionError(error_message)
def _error_message():
msg = self._format_mock_failure_message(args, kwargs)
return msg
expected = self._call_matcher(_Call((args, kwargs), two=True))
actual = self._call_matcher(self.call_args)
if actual != expected:
cause = expected if isinstance(expected, Exception) else None
> raise AssertionError(_error_message()) from cause
E AssertionError: expected call not found.
E Expected: get_team_by_slug('release-team')
E Actual: get_team_by_slug('acme')
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:985: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-05-16 19:37:06 [debug ] User alice is a member of team acme extra={'org': 'acme', 'username': 'alice', 'team': 'acme'}
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetUserTeamSlugs::test_get_user_team_slugs_with_org_prefix - AssertionError: expected call not found.
Expected: get_team_by_slug('release-team')
Actual: get_team_by_slug('acme')
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 65 passed, 3 deselected in 0.80s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceUnaryOperator_Delete_USub, occurrence: 0
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -713,7 +713,7 @@
permission_index = permission_order.index(permission)
required_index = permission_order.index(required_permission)
except ValueError:
- permission_index = -1
+ permission_index = 1
required_index = 99
has_permission = permission_index >= required_index........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.63s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceUnaryOperator_Delete_USub, occurrence: 1
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -797,7 +797,7 @@
for team_ref in allowed_teams:
# Extract team slug (handle both "org/team" and "team" formats)
- team_slug = team_ref.split("/")[-1] if "/" in team_ref else team_ref
+ team_slug = team_ref.split("/")[1] if "/" in team_ref else team_ref
try:
team = org.get_team_by_slug(team_slug)........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.62s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceUnaryOperator_Delete_Not, occurrence: 0
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -62,7 +62,7 @@
if self._initialized:
return
- if not is_configured():
+ if is_configured():
raise RuntimeError("GitHub App not configured. Visit /setup to create your GitHub App.")
# is_configured() guarantees app_id is set, but check for type safety.....................................F
=================================== FAILURES ===================================
___ TestEnsureInitialized.test_ensure_initialized_raises_when_not_configured ___
self = <tests.test_github_client.TestEnsureInitialized object at 0x7f48a5fdfc50>
def test_ensure_initialized_raises_when_not_configured(self):
"""Test that _ensure_initialized raises when app not configured."""
with patch("stampbot.github_client.is_configured", return_value=False):
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
> with pytest.raises(RuntimeError, match="GitHub App not configured"):
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E Failed: DID NOT RAISE <class 'RuntimeError'>
tests/test_github_client.py:47: Failed
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestEnsureInitialized::test_ensure_initialized_raises_when_not_configured - Failed: DID NOT RAISE <class 'RuntimeError'>
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 37 passed, 3 deselected in 0.77s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceUnaryOperator_Delete_Not, occurrence: 1
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -117,7 +117,7 @@
# Security: prevent path traversal by ensuring the path doesn't escape
# expected directories and doesn't follow symlinks to unexpected locations
- if ".." in str(key_path) or not os.path.isfile(key_path):
+ if ".." in str(key_path) or os.path.isfile(key_path):
raise ValueError(f"Invalid private key path: {key}")
try:...........................................F
=================================== FAILURES ===================================
___________ TestLoadPrivateKey.test_load_private_key_reads_from_file ___________
self = <tests.test_github_client.TestLoadPrivateKey object at 0x7fa30a8916e0>
tmp_path = PosixPath('/tmp/pytest-of-runner/pytest-940/test_load_private_key_reads_fr0')
def test_load_private_key_reads_from_file(self, tmp_path):
"""Test that private key is read from file when path provided."""
pem_key = TEST_PEM_KEY
key_file = tmp_path / "private-key.pem"
key_file.write_text(pem_key)
with patch("stampbot.github_client.settings") as mock_settings:
mock_settings.private_key = str(key_file)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
> result = client._load_private_key()
^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_github_client.py:136:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.github_client.GitHubAppClient object at 0x7fa307992250>
def _load_private_key(self) -> str:
"""Load private key from settings or file.
Returns:
Private key contents as PEM string.
Raises:
RuntimeError: If private key is not configured.
ValueError: If private key is not valid PEM format.
OSError: If private key file cannot be read.
"""
key: str | None = settings.private_key
if key is None:
raise RuntimeError("Private key not configured")
# If it looks like PEM content, use directly
if key.startswith("-----BEGIN"):
pem_content = key
else:
# Treat as file path - read the file
import os
from pathlib import Path
key_path = Path(key).resolve()
# Security: prevent path traversal by ensuring the path doesn't escape
# expected directories and doesn't follow symlinks to unexpected locations
if ".." in str(key_path) or os.path.isfile(key_path):
> raise ValueError(f"Invalid private key path: {key}")
E ValueError: Invalid private key path: /tmp/pytest-of-runner/pytest-940/test_load_private_key_reads_fr0/private-key.pem
stampbot/github_client.py:121: ValueError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestLoadPrivateKey::test_load_private_key_reads_from_file - ValueError: Invalid private key path: /tmp/pytest-of-runner/pytest-940/test_load_private_key_reads_fr0/private-key.pem
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 43 passed, 3 deselected in 0.76s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceUnaryOperator_Delete_Not, occurrence: 2
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -128,7 +128,7 @@
raise
# Validate the content is actually a PEM-formatted key
- if not pem_content.strip().startswith("-----BEGIN"):
+ if pem_content.strip().startswith("-----BEGIN"):
raise ValueError("Private key must be in PEM format")
return pem_content.......................................F
=================================== FAILURES ===================================
____________ TestEnsureInitialized.test_ensure_initialized_success _____________
self = <tests.test_github_client.TestEnsureInitialized object at 0x7fa746294b00>
def test_ensure_initialized_success(self):
"""Test successful initialization."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth") as mock_auth,
patch("stampbot.github_client.GithubIntegration") as mock_integration,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
> client._ensure_initialized()
tests/test_github_client.py:78:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
stampbot/github_client.py:73: in _ensure_initialized
private_key = self._load_private_key()
^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.github_client.GitHubAppClient object at 0x7fa74338fbf0>
def _load_private_key(self) -> str:
"""Load private key from settings or file.
Returns:
Private key contents as PEM string.
Raises:
RuntimeError: If private key is not configured.
ValueError: If private key is not valid PEM format.
OSError: If private key file cannot be read.
"""
key: str | None = settings.private_key
if key is None:
raise RuntimeError("Private key not configured")
# If it looks like PEM content, use directly
if key.startswith("-----BEGIN"):
pem_content = key
else:
# Treat as file path - read the file
import os
from pathlib import Path
key_path = Path(key).resolve()
# Security: prevent path traversal by ensuring the path doesn't escape
# expected directories and doesn't follow symlinks to unexpected locations
if ".." in str(key_path) or not os.path.isfile(key_path):
raise ValueError(f"Invalid private key path: {key}")
try:
with open(key_path) as f:
pem_content = f.read()
except Exception as e:
logger.error("Failed to read private key from file: %s", e)
raise
# Validate the content is actually a PEM-formatted key
if pem_content.strip().startswith("-----BEGIN"):
> raise ValueError("Private key must be in PEM format")
E ValueError: Private key must be in PEM format
stampbot/github_client.py:132: ValueError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestEnsureInitialized::test_ensure_initialized_success - ValueError: Private key must be in PEM format
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 39 passed, 3 deselected in 0.76s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/AddNot, occurrence: 0
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -59,7 +59,7 @@
Raises:
RuntimeError: If GitHub App credentials are not configured.
"""
- if self._initialized:
+ if not self._initialized:
return
if not is_configured():.....................................F
=================================== FAILURES ===================================
___ TestEnsureInitialized.test_ensure_initialized_raises_when_not_configured ___
self = <tests.test_github_client.TestEnsureInitialized object at 0x7f454be57c50>
def test_ensure_initialized_raises_when_not_configured(self):
"""Test that _ensure_initialized raises when app not configured."""
with patch("stampbot.github_client.is_configured", return_value=False):
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
> with pytest.raises(RuntimeError, match="GitHub App not configured"):
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E Failed: DID NOT RAISE <class 'RuntimeError'>
tests/test_github_client.py:47: Failed
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestEnsureInitialized::test_ensure_initialized_raises_when_not_configured - Failed: DID NOT RAISE <class 'RuntimeError'>
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 37 passed, 3 deselected in 0.72s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/AddNot, occurrence: 1
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -62,7 +62,7 @@
if self._initialized:
return
- if not is_configured():
+ if not not is_configured():
raise RuntimeError("GitHub App not configured. Visit /setup to create your GitHub App.")
# is_configured() guarantees app_id is set, but check for type safety.....................................F
=================================== FAILURES ===================================
___ TestEnsureInitialized.test_ensure_initialized_raises_when_not_configured ___
self = <tests.test_github_client.TestEnsureInitialized object at 0x7fa2766c3c50>
def test_ensure_initialized_raises_when_not_configured(self):
"""Test that _ensure_initialized raises when app not configured."""
with patch("stampbot.github_client.is_configured", return_value=False):
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
> with pytest.raises(RuntimeError, match="GitHub App not configured"):
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E Failed: DID NOT RAISE <class 'RuntimeError'>
tests/test_github_client.py:47: Failed
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestEnsureInitialized::test_ensure_initialized_raises_when_not_configured - Failed: DID NOT RAISE <class 'RuntimeError'>
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 37 passed, 3 deselected in 0.73s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/AddNot, occurrence: 2
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -67,7 +67,7 @@
# is_configured() guarantees app_id is set, but check for type safety
app_id = settings.app_id
- if app_id is None:
+ if not app_id is None:
raise RuntimeError("App ID not configured")
private_key = self._load_private_key()......................................F
=================================== FAILURES ===================================
____ TestEnsureInitialized.test_ensure_initialized_raises_when_app_id_none _____
self = <tests.test_github_client.TestEnsureInitialized object at 0x7fc93a7a4c30>
def test_ensure_initialized_raises_when_app_id_none(self):
"""Test that _ensure_initialized raises when app_id is None."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
):
mock_settings.app_id = None
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
with pytest.raises(RuntimeError, match="App ID not configured"):
> client._ensure_initialized()
tests/test_github_client.py:62:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
stampbot/github_client.py:74: in _ensure_initialized
self._auth = Auth.AppAuth(app_id, private_key)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <github.Auth.AppAuth object at 0x7fc936535d30>, app_id = None
private_key = <MagicMock name='settings.private_key' id='140502176584032'>
def __init__(
self,
app_id: int | str,
private_key: str | PrivateKeyGenerator | None = None,
*,
sign_func: DictSignFunction | None = None,
jwt_expiry: int = Consts.DEFAULT_JWT_EXPIRY,
jwt_issued_at: int = Consts.DEFAULT_JWT_ISSUED_AT,
):
> assert isinstance(app_id, (int, str)), app_id
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E AssertionError: None
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/github/Auth.py:227: AssertionError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestEnsureInitialized::test_ensure_initialized_raises_when_app_id_none - AssertionError: None
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 38 passed, 3 deselected in 0.75s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/AddNot, occurrence: 3
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -86,7 +86,7 @@
RuntimeError: If GitHub App credentials are not configured.
"""
self._ensure_initialized()
- assert self._integration is not None # guaranteed by _ensure_initialized
+ assert not self._integration is not None # guaranteed by _ensure_initialized
return self._integration
def _load_private_key(self) -> str:...............................................F
=================================== FAILURES ===================================
________ TestGetInstallationClient.test_get_installation_client_success ________
self = <tests.test_github_client.TestGetInstallationClient object at 0x7fa470ed8050>
def test_get_installation_client_success(self):
"""Test successful installation client creation."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
> result = client._get_installation_client(123456)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_github_client.py:211:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
stampbot/github_client.py:155: in _get_installation_client
auth = self.integration.get_access_token(installation_id)
^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.github_client.GitHubAppClient object at 0x7fa46d0a0750>
@property
def integration(self) -> GithubIntegration:
"""Get GitHub integration, initializing if needed.
Returns:
Configured GithubIntegration instance.
Raises:
RuntimeError: If GitHub App credentials are not configured.
"""
self._ensure_initialized()
> assert not self._integration is not None # guaranteed by _ensure_initialized
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E AssertionError
stampbot/github_client.py:89: AssertionError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetInstallationClient::test_get_installation_client_success - AssertionError
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 47 passed, 3 deselected in 0.77s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/AddNot, occurrence: 4
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -102,7 +102,7 @@
"""
key: str | None = settings.private_key
- if key is None:
+ if not key is None:
raise RuntimeError("Private key not configured")
# If it looks like PEM content, use directly.......................................F
=================================== FAILURES ===================================
____________ TestEnsureInitialized.test_ensure_initialized_success _____________
self = <tests.test_github_client.TestEnsureInitialized object at 0x7fc06a300b00>
def test_ensure_initialized_success(self):
"""Test successful initialization."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth") as mock_auth,
patch("stampbot.github_client.GithubIntegration") as mock_integration,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
> client._ensure_initialized()
tests/test_github_client.py:78:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
stampbot/github_client.py:73: in _ensure_initialized
private_key = self._load_private_key()
^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.github_client.GitHubAppClient object at 0x7fc06738bbf0>
def _load_private_key(self) -> str:
"""Load private key from settings or file.
Returns:
Private key contents as PEM string.
Raises:
RuntimeError: If private key is not configured.
ValueError: If private key is not valid PEM format.
OSError: If private key file cannot be read.
"""
key: str | None = settings.private_key
if not key is None:
> raise RuntimeError("Private key not configured")
E RuntimeError: Private key not configured
stampbot/github_client.py:106: RuntimeError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestEnsureInitialized::test_ensure_initialized_success - RuntimeError: Private key not configured
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 39 passed, 3 deselected in 0.76s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/AddNot, occurrence: 5
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -106,7 +106,7 @@
raise RuntimeError("Private key not configured")
# If it looks like PEM content, use directly
- if key.startswith("-----BEGIN"):
+ if not key.startswith("-----BEGIN"):
pem_content = key
else:
# Treat as file path - read the file.......................................F
=================================== FAILURES ===================================
____________ TestEnsureInitialized.test_ensure_initialized_success _____________
self = <tests.test_github_client.TestEnsureInitialized object at 0x7f6487bc4b00>
def test_ensure_initialized_success(self):
"""Test successful initialization."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth") as mock_auth,
patch("stampbot.github_client.GithubIntegration") as mock_integration,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
> client._ensure_initialized()
tests/test_github_client.py:78:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
stampbot/github_client.py:73: in _ensure_initialized
private_key = self._load_private_key()
^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.github_client.GitHubAppClient object at 0x7f6483a67bf0>
def _load_private_key(self) -> str:
"""Load private key from settings or file.
Returns:
Private key contents as PEM string.
Raises:
RuntimeError: If private key is not configured.
ValueError: If private key is not valid PEM format.
OSError: If private key file cannot be read.
"""
key: str | None = settings.private_key
if key is None:
raise RuntimeError("Private key not configured")
# If it looks like PEM content, use directly
if not key.startswith("-----BEGIN"):
pem_content = key
else:
# Treat as file path - read the file
import os
from pathlib import Path
key_path = Path(key).resolve()
# Security: prevent path traversal by ensuring the path doesn't escape
# expected directories and doesn't follow symlinks to unexpected locations
if ".." in str(key_path) or not os.path.isfile(key_path):
> raise ValueError(f"Invalid private key path: {key}")
E ValueError: Invalid private key path: -----BEGIN RSA PRIVATE KEY-----
E test
E -----END RSA PRIVATE KEY-----
stampbot/github_client.py:121: ValueError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestEnsureInitialized::test_ensure_initialized_success - ValueError: Invalid private key path: -----BEGIN RSA PRIVATE KEY-----
test
-----END RSA PRIVATE KEY-----
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 39 passed, 3 deselected in 0.75s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/AddNot, occurrence: 6
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -117,7 +117,7 @@
# Security: prevent path traversal by ensuring the path doesn't escape
# expected directories and doesn't follow symlinks to unexpected locations
- if ".." in str(key_path) or not os.path.isfile(key_path):
+ if not ".." in str(key_path) or not os.path.isfile(key_path):
raise ValueError(f"Invalid private key path: {key}")
try:...........................................F
=================================== FAILURES ===================================
___________ TestLoadPrivateKey.test_load_private_key_reads_from_file ___________
self = <tests.test_github_client.TestLoadPrivateKey object at 0x7f42236856e0>
tmp_path = PosixPath('/tmp/pytest-of-runner/pytest-865/test_load_private_key_reads_fr0')
def test_load_private_key_reads_from_file(self, tmp_path):
"""Test that private key is read from file when path provided."""
pem_key = TEST_PEM_KEY
key_file = tmp_path / "private-key.pem"
key_file.write_text(pem_key)
with patch("stampbot.github_client.settings") as mock_settings:
mock_settings.private_key = str(key_file)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
> result = client._load_private_key()
^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_github_client.py:136:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.github_client.GitHubAppClient object at 0x7f42206ae250>
def _load_private_key(self) -> str:
"""Load private key from settings or file.
Returns:
Private key contents as PEM string.
Raises:
RuntimeError: If private key is not configured.
ValueError: If private key is not valid PEM format.
OSError: If private key file cannot be read.
"""
key: str | None = settings.private_key
if key is None:
raise RuntimeError("Private key not configured")
# If it looks like PEM content, use directly
if key.startswith("-----BEGIN"):
pem_content = key
else:
# Treat as file path - read the file
import os
from pathlib import Path
key_path = Path(key).resolve()
# Security: prevent path traversal by ensuring the path doesn't escape
# expected directories and doesn't follow symlinks to unexpected locations
if not ".." in str(key_path) or not os.path.isfile(key_path):
> raise ValueError(f"Invalid private key path: {key}")
E ValueError: Invalid private key path: /tmp/pytest-of-runner/pytest-865/test_load_private_key_reads_fr0/private-key.pem
stampbot/github_client.py:121: ValueError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestLoadPrivateKey::test_load_private_key_reads_from_file - ValueError: Invalid private key path: /tmp/pytest-of-runner/pytest-865/test_load_private_key_reads_fr0/private-key.pem
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 43 passed, 3 deselected in 0.76s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/AddNot, occurrence: 7
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -128,7 +128,7 @@
raise
# Validate the content is actually a PEM-formatted key
- if not pem_content.strip().startswith("-----BEGIN"):
+ if not not pem_content.strip().startswith("-----BEGIN"):
raise ValueError("Private key must be in PEM format")
return pem_content.......................................F
=================================== FAILURES ===================================
____________ TestEnsureInitialized.test_ensure_initialized_success _____________
self = <tests.test_github_client.TestEnsureInitialized object at 0x7f40c671cb00>
def test_ensure_initialized_success(self):
"""Test successful initialization."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth") as mock_auth,
patch("stampbot.github_client.GithubIntegration") as mock_integration,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
> client._ensure_initialized()
tests/test_github_client.py:78:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
stampbot/github_client.py:73: in _ensure_initialized
private_key = self._load_private_key()
^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.github_client.GitHubAppClient object at 0x7f40c356fbf0>
def _load_private_key(self) -> str:
"""Load private key from settings or file.
Returns:
Private key contents as PEM string.
Raises:
RuntimeError: If private key is not configured.
ValueError: If private key is not valid PEM format.
OSError: If private key file cannot be read.
"""
key: str | None = settings.private_key
if key is None:
raise RuntimeError("Private key not configured")
# If it looks like PEM content, use directly
if key.startswith("-----BEGIN"):
pem_content = key
else:
# Treat as file path - read the file
import os
from pathlib import Path
key_path = Path(key).resolve()
# Security: prevent path traversal by ensuring the path doesn't escape
# expected directories and doesn't follow symlinks to unexpected locations
if ".." in str(key_path) or not os.path.isfile(key_path):
raise ValueError(f"Invalid private key path: {key}")
try:
with open(key_path) as f:
pem_content = f.read()
except Exception as e:
logger.error("Failed to read private key from file: %s", e)
raise
# Validate the content is actually a PEM-formatted key
if not not pem_content.strip().startswith("-----BEGIN"):
> raise ValueError("Private key must be in PEM format")
E ValueError: Private key must be in PEM format
stampbot/github_client.py:132: ValueError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestEnsureInitialized::test_ensure_initialized_success - ValueError: Private key must be in PEM format
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 39 passed, 3 deselected in 0.77s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/AddNot, occurrence: 8
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -392,7 +392,7 @@
repo = client.get_repo(repo_full_name)
content = repo.get_contents(file_path, ref=ref) # type: ignore[arg-type]
- if isinstance(content, list):
+ if not isinstance(content, list):
duration = time.time() - start_time
github_api_request_duration_seconds.labels(operation="get_file").observe(
duration.......................................................F
=================================== FAILURES ===================================
__________________ TestGetRepoFile.test_get_repo_file_success __________________
self = <tests.test_github_client.TestGetRepoFile object at 0x7f32a818ca50>
def test_get_repo_file_success(self):
"""Test successful file retrieval."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_content = Mock()
mock_content.decoded_content = b"file content"
mock_repo = Mock()
mock_repo.get_contents.return_value = mock_content
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
result = client.get_repo_file(123456, "owner/repo", "stampbot.toml")
> assert result == "file content"
E AssertionError: assert None == 'file content'
tests/test_github_client.py:480: AssertionError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetRepoFile::test_get_repo_file_success - AssertionError: assert None == 'file content'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 55 passed, 3 deselected in 0.73s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/AddNot, occurrence: 9
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -470,7 +470,7 @@
# Find all reviews by bot that are approvals
bot_review_ids = []
for review in pr.get_reviews():
- if review.user.login == bot_user and review.state == "APPROVED":
+ if not review.user.login == bot_user and review.state == "APPROVED":
bot_review_ids.append(review.id)
duration = time.time() - start_time..........................................................F
=================================== FAILURES ===================================
_______________ TestFindBotReviews.test_find_bot_reviews_success _______________
self = <tests.test_github_client.TestFindBotReviews object at 0x7f438400ccd0>
def test_find_bot_reviews_success(self):
"""Test successful bot review finding."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration.get_app.return_value = Mock(slug="stampbot")
mock_integration_cls.return_value = mock_integration
# Create mock reviews
bot_review = Mock()
bot_review.user.login = "stampbot[bot]"
bot_review.state = "APPROVED"
bot_review.id = 123
other_review = Mock()
other_review.user.login = "other-user"
other_review.state = "APPROVED"
other_review.id = 456
mock_pr = Mock()
mock_pr.get_reviews.return_value = [bot_review, other_review]
mock_repo = Mock()
mock_repo.get_pull.return_value = mock_pr
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
result = client.find_bot_reviews(123456, "owner/repo", 42)
> assert result == [123]
E assert [456] == [123]
E
E At index 0 diff: 456 != 123
E
E Full diff:
E [
E - 123,
E + 456,
E ]
tests/test_github_client.py:608: AssertionError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestFindBotReviews::test_find_bot_reviews_success - assert [456] == [123]
At index 0 diff: 456 != 123
Full diff:
[
- 123,
+ 456,
]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 58 passed, 3 deselected in 0.75s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/AddNot, occurrence: 10
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -634,7 +634,7 @@
github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
github_api_requests_total.labels(operation="get_label", status="failure").inc()
- if e.status == 404:
+ if not e.status == 404:
add_span_attributes(span, {"github.label_found": False})
set_span_ok(span)
return False......................................................................F
=================================== FAILURES ===================================
________________ TestRepoHasLabel.test_repo_has_label_not_found ________________
self = <tests.test_github_client.TestRepoHasLabel object at 0x7f2689cbd590>
def test_repo_has_label_not_found(self):
"""Test repo_has_label returns False when label is missing."""
from github.GithubException import GithubException
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_repo = Mock()
mock_repo.get_label.side_effect = GithubException(404, {"message": "Not Found"}, None)
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
result = client.repo_has_label(123456, "owner/repo", "missing")
> assert result is False
E assert None is False
tests/test_github_client.py:1092: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-05-16 20:02:30 [debug ] Could not verify label missing in owner/repo: 404 {"message": "Not Found"} extra={'repo': 'owner/repo', 'label': 'missing', 'installation_id': 123456, 'error': '404 {"message": "Not Found"}'}
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestRepoHasLabel::test_repo_has_label_not_found - assert None is False
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 70 passed, 3 deselected in 0.79s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/AddNot, occurrence: 11
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -797,7 +797,7 @@
for team_ref in allowed_teams:
# Extract team slug (handle both "org/team" and "team" formats)
- team_slug = team_ref.split("/")[-1] if "/" in team_ref else team_ref
+ team_slug = team_ref.split("/")[-1] if not "/" in team_ref else team_ref
try:
team = org.get_team_by_slug(team_slug).................................................................F
=================================== FAILURES ===================================
________ TestGetUserTeamSlugs.test_get_user_team_slugs_with_org_prefix _________
self = <tests.test_github_client.TestGetUserTeamSlugs object at 0x7f31859cd310>
def test_get_user_team_slugs_with_org_prefix(self):
"""Test team membership check with org/team format."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_user = Mock()
mock_team = Mock()
mock_team.has_in_members.return_value = True
mock_org = Mock()
mock_org.get_team_by_slug.return_value = mock_team
mock_github = Mock()
mock_github.get_organization.return_value = mock_org
mock_github.get_user.return_value = mock_user
mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
result = client.get_user_team_slugs(123456, "acme", "alice", ["acme/release-team"])
# Should extract "release-team" from "acme/release-team"
> mock_org.get_team_by_slug.assert_called_with("release-team")
tests/test_github_client.py:892:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <Mock name='Github().get_organization().get_team_by_slug' id='139850603307632'>
args = ('release-team',), kwargs = {}, expected = call('release-team')
actual = call('acme/release-team')
_error_message = <function NonCallableMock.assert_called_with.<locals>._error_message at 0x7f31819e4bf0>
cause = None
def assert_called_with(self, /, *args, **kwargs):
"""assert that the last call was made with the specified arguments.
Raises an AssertionError if the args and keyword args passed in are
different to the last call to the mock."""
if self.call_args is None:
expected = self._format_mock_call_signature(args, kwargs)
actual = 'not called.'
error_message = ('expected call not found.\nExpected: %s\n Actual: %s'
% (expected, actual))
raise AssertionError(error_message)
def _error_message():
msg = self._format_mock_failure_message(args, kwargs)
return msg
expected = self._call_matcher(_Call((args, kwargs), two=True))
actual = self._call_matcher(self.call_args)
if actual != expected:
cause = expected if isinstance(expected, Exception) else None
> raise AssertionError(_error_message()) from cause
E AssertionError: expected call not found.
E Expected: get_team_by_slug('release-team')
E Actual: get_team_by_slug('acme/release-team')
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:985: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-05-16 19:28:02 [debug ] User alice is a member of team acme/release-team extra={'org': 'acme', 'username': 'alice', 'team': 'acme/release-team'}
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetUserTeamSlugs::test_get_user_team_slugs_with_org_prefix - AssertionError: expected call not found.
Expected: get_team_by_slug('release-team')
Actual: get_team_by_slug('acme/release-team')
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 65 passed, 3 deselected in 0.82s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/AddNot, occurrence: 12
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -803,7 +803,7 @@
team = org.get_team_by_slug(team_slug)
# Check if user is a member
user = client.get_user(username)
- if team.has_in_members(user): # type: ignore[arg-type]
+ if not team.has_in_members(user): # type: ignore[arg-type]
member_teams.append(team_slug)
logger.debug(
"User %s is a member of team %s",................................................................F
=================================== FAILURES ===================================
____________ TestGetUserTeamSlugs.test_get_user_team_slugs_success _____________
self = <tests.test_github_client.TestGetUserTeamSlugs object at 0x7f56e3e851d0>
def test_get_user_team_slugs_success(self):
"""Test successful team membership check."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
# Mock user
mock_user = Mock()
mock_user.login = "alice"
# Mock team that has alice as member
mock_team = Mock()
mock_team.has_in_members.return_value = True
# Mock org
mock_org = Mock()
mock_org.get_team_by_slug.return_value = mock_team
mock_github = Mock()
mock_github.get_organization.return_value = mock_org
mock_github.get_user.return_value = mock_user
mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
result = client.get_user_team_slugs(
123456, "acme", "alice", ["release-team", "deploy-team"]
)
> assert result == ["release-team", "deploy-team"]
E AssertionError: assert [] == ['release-tea...'deploy-team']
E
E Right contains 2 more items, first extra item: 'release-team'
E
E Full diff:
E + []
E - [
E - 'release-team',
E - 'deploy-team',
E - ]
tests/test_github_client.py:848: AssertionError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetUserTeamSlugs::test_get_user_team_slugs_success - AssertionError: assert [] == ['release-tea...'deploy-team']
Right contains 2 more items, first extra item: 'release-team'
Full diff:
+ []
- [
- 'release-team',
- 'deploy-team',
- ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 64 passed, 3 deselected in 0.78s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/AddNot, occurrence: 13
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -820,7 +820,7 @@
logger.debug(
"Could not check team %s membership: %s",
team_slug,
- e.data.get("message", str(e)) if hasattr(e, "data") else str(e),
+ e.data.get("message", str(e)) if not hasattr(e, "data") else str(e),
extra={
"org": org_name,
"team": team_slug,........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.64s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceTrueWithFalse, occurrence: 0
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -73,7 +73,7 @@
private_key = self._load_private_key()
self._auth = Auth.AppAuth(app_id, private_key)
self._integration = GithubIntegration(auth=self._auth)
- self._initialized = True
+ self._initialized = False
@property
def integration(self) -> GithubIntegration:.......................................F
=================================== FAILURES ===================================
____________ TestEnsureInitialized.test_ensure_initialized_success _____________
self = <tests.test_github_client.TestEnsureInitialized object at 0x7f9a3bd88b00>
def test_ensure_initialized_success(self):
"""Test successful initialization."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth") as mock_auth,
patch("stampbot.github_client.GithubIntegration") as mock_integration,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
client._ensure_initialized()
> assert client._initialized is True
E assert False is True
E + where False = <stampbot.github_client.GitHubAppClient object at 0x7f9a37b5bbf0>._initialized
tests/test_github_client.py:80: AssertionError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestEnsureInitialized::test_ensure_initialized_success - assert False is True
+ where False = <stampbot.github_client.GitHubAppClient object at 0x7f9a37b5bbf0>._initialized
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 39 passed, 3 deselected in 0.73s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceTrueWithFalse, occurrence: 1
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -257,7 +257,7 @@
"installation_id": installation_id,
},
)
- return True
+ return False
except Exception as e:
duration = time.time() - start_time...................................................F
=================================== FAILURES ===================================
____________________ TestApprovePR.test_approve_pr_success _____________________
self = <tests.test_github_client.TestApprovePR object at 0x7f63a1634550>
def test_approve_pr_success(self):
"""Test successful PR approval."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_pr = Mock()
mock_repo = Mock()
mock_repo.get_pull.return_value = mock_pr
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
result = client.approve_pr(123456, "owner/repo", 42)
> assert result is True
E assert False is True
tests/test_github_client.py:313: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-05-16 19:33:38 [info ] Approved PR #42 in owner/repo extra={'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456}
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestApprovePR::test_approve_pr_success - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 51 passed, 3 deselected in 0.73s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceTrueWithFalse, occurrence: 2
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -335,7 +335,7 @@
"review_id": review_id,
},
)
- return True
+ return False
except Exception as e:
duration = time.time() - start_time.....................................................F
=================================== FAILURES ===================================
______________ TestDismissApproval.test_dismiss_approval_success _______________
self = <tests.test_github_client.TestDismissApproval object at 0x7fb8ee1407d0>
def test_dismiss_approval_success(self):
"""Test successful approval dismissal."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_review = Mock()
mock_pr = Mock()
mock_pr.get_review.return_value = mock_review
mock_repo = Mock()
mock_repo.get_pull.return_value = mock_pr
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
result = client.dismiss_approval(123456, "owner/repo", 42, 999)
> assert result is True
E assert False is True
tests/test_github_client.py:396: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-05-16 19:44:06 [info ] Dismissed approval for PR #42 in owner/repo extra={'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'review_id': 999}
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestDismissApproval::test_dismiss_approval_success - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 53 passed, 3 deselected in 0.76s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceTrueWithFalse, occurrence: 3
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -565,7 +565,7 @@
"installation_id": installation_id,
},
)
- return True
+ return False
except Exception as e:
duration = time.time() - start_time........................................................................ [ 34%]
.F
=================================== FAILURES ===================================
_______ TestCreatePrReviewComment.test_create_pr_review_comment_success ________
self = <tests.test_github_client.TestCreatePrReviewComment object at 0x7f58e04616d0>
def test_create_pr_review_comment_success(self):
"""Test successful review comment creation."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_pr = Mock()
mock_repo = Mock()
mock_repo.get_pull.return_value = mock_pr
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=1, limit=2))
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
result = client.create_pr_review_comment(
123456,
"owner/repo",
42,
"Config error",
)
> assert result is True
E assert False is True
tests/test_github_client.py:1213: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-05-16 20:03:22 [info ] Posted config error review on PR #42 in owner/repo extra={'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456}
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestCreatePrReviewComment::test_create_pr_review_comment_success - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 73 passed, 3 deselected in 0.80s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceTrueWithFalse, occurrence: 4
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -625,7 +625,7 @@
self._update_rate_limit_metrics(client, installation_id)
- add_span_attributes(span, {"github.label_found": True})
+ add_span_attributes(span, {"github.label_found": False})
set_span_ok(span)
return True
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.62s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceTrueWithFalse, occurrence: 5
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -627,7 +627,7 @@
add_span_attributes(span, {"github.label_found": True})
set_span_ok(span)
- return True
+ return False
except GithubException as e:
duration = time.time() - start_time.....................................................................F
=================================== FAILURES ===================================
__________________ TestRepoHasLabel.test_repo_has_label_true ___________________
self = <tests.test_github_client.TestRepoHasLabel object at 0x7fc48c985450>
def test_repo_has_label_true(self):
"""Test repo_has_label returns True when label exists."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_repo = Mock()
mock_repo.get_label.return_value = Mock()
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=1, limit=2))
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
result = client.repo_has_label(123456, "owner/repo", "autoapprove")
> assert result is True
E assert False is True
tests/test_github_client.py:1054: AssertionError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestRepoHasLabel::test_repo_has_label_true - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 69 passed, 3 deselected in 0.82s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceFalseWithTrue, occurrence: 0
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -51,7 +51,7 @@
"""Initialize GitHub App client (lazy initialization)."""
self._auth: Auth.AppAuth | None = None
self._integration: GithubIntegration | None = None
- self._initialized = False
+ self._initialized = True
def _ensure_initialized(self) -> None:
"""Ensure client is initialized with credentials....................................F
=================================== FAILURES ===================================
________ TestGitHubAppClientInit.test_init_creates_uninitialized_client ________
self = <tests.test_github_client.TestGitHubAppClientInit object at 0x7f130f5979d0>
def test_init_creates_uninitialized_client(self):
"""Test that __init__ creates an uninitialized client."""
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
assert client._auth is None
assert client._integration is None
> assert client._initialized is False
E assert True is False
E + where True = <stampbot.github_client.GitHubAppClient object at 0x7f130b44b390>._initialized
tests/test_github_client.py:24: AssertionError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGitHubAppClientInit::test_init_creates_uninitialized_client - assert True is False
+ where True = <stampbot.github_client.GitHubAppClient object at 0x7f130b44b390>._initialized
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 35 passed, 3 deselected in 0.71s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceFalseWithTrue, occurrence: 1
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -275,7 +275,7 @@
"error": _sanitize_error(e),
},
)
- return False
+ return True
def dismiss_approval(
self,....................................................F
=================================== FAILURES ===================================
____________________ TestApprovePR.test_approve_pr_failure _____________________
self = <tests.test_github_client.TestApprovePR object at 0x7fb8f664c690>
def test_approve_pr_failure(self):
"""Test PR approval failure."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_pr = Mock()
mock_pr.create_review.side_effect = Exception("API Error")
mock_repo = Mock()
mock_repo.get_pull.return_value = mock_pr
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
result = client.approve_pr(123456, "owner/repo", 42)
> assert result is False
E assert True is False
tests/test_github_client.py:352: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-05-16 19:56:36 [error ] Failed to approve PR #42 in owner/repo: API Error extra={'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': 'API Error'}
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestApprovePR::test_approve_pr_failure - assert True is False
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 52 passed, 3 deselected in 0.75s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceFalseWithTrue, occurrence: 2
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -356,7 +356,7 @@
"error": _sanitize_error(e),
},
)
- return False
+ return True
def get_repo_file(
self,......................................................F
=================================== FAILURES ===================================
______________ TestDismissApproval.test_dismiss_approval_failure _______________
self = <tests.test_github_client.TestDismissApproval object at 0x7fbc19f7c910>
def test_dismiss_approval_failure(self):
"""Test approval dismissal failure."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_review = Mock()
mock_review.dismiss.side_effect = Exception("API Error")
mock_pr = Mock()
mock_pr.get_review.return_value = mock_review
mock_repo = Mock()
mock_repo.get_pull.return_value = mock_pr
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
result = client.dismiss_approval(123456, "owner/repo", 42, 999)
> assert result is False
E assert True is False
tests/test_github_client.py:437: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-05-16 19:47:01 [error ] Failed to dismiss approval for PR #42 in owner/repo: API Error extra={'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': 'API Error'}
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestDismissApproval::test_dismiss_approval_failure - assert True is False
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 54 passed, 3 deselected in 0.77s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceFalseWithTrue, occurrence: 3
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -586,7 +586,7 @@
"error": _sanitize_error(e),
},
)
- return False
+ return True
def repo_has_label(
self,........................................................................ [ 34%]
..F
=================================== FAILURES ===================================
_______ TestCreatePrReviewComment.test_create_pr_review_comment_failure ________
self = <tests.test_github_client.TestCreatePrReviewComment object at 0x7f0b982ad810>
def test_create_pr_review_comment_failure(self):
"""Test review comment creation handles errors."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_repo = Mock()
mock_repo.get_pull.side_effect = Exception("API Error")
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
result = client.create_pr_review_comment(
123456,
"owner/repo",
42,
"Config error",
)
> assert result is False
E assert True is False
tests/test_github_client.py:1255: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-05-16 19:50:46 [warning ] Failed to post config error review for PR #42 in owner/repo: API Error extra={'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': 'API Error'}
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestCreatePrReviewComment::test_create_pr_review_comment_failure - assert True is False
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 74 passed, 3 deselected in 0.78s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceFalseWithTrue, occurrence: 4
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -635,7 +635,7 @@
github_api_requests_total.labels(operation="get_label", status="failure").inc()
if e.status == 404:
- add_span_attributes(span, {"github.label_found": False})
+ add_span_attributes(span, {"github.label_found": True})
set_span_ok(span)
return False
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.63s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceFalseWithTrue, occurrence: 5
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -637,7 +637,7 @@
if e.status == 404:
add_span_attributes(span, {"github.label_found": False})
set_span_ok(span)
- return False
+ return True
set_span_error(span, e)
logger.debug(......................................................................F
=================================== FAILURES ===================================
________________ TestRepoHasLabel.test_repo_has_label_not_found ________________
self = <tests.test_github_client.TestRepoHasLabel object at 0x7f24b3339590>
def test_repo_has_label_not_found(self):
"""Test repo_has_label returns False when label is missing."""
from github.GithubException import GithubException
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_repo = Mock()
mock_repo.get_label.side_effect = GithubException(404, {"message": "Not Found"}, None)
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
result = client.repo_has_label(123456, "owner/repo", "missing")
> assert result is False
E assert True is False
tests/test_github_client.py:1092: AssertionError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestRepoHasLabel::test_repo_has_label_not_found - assert True is False
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 70 passed, 3 deselected in 0.76s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceFalseWithTrue, occurrence: 6
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -758,7 +758,7 @@
"error": _sanitize_error(e),
},
)
- return False
+ return True
def get_user_team_slugs(
self,..............................................................F
=================================== FAILURES ===================================
_____________ TestUserHasPermission.test_user_has_permission_error _____________
self = <tests.test_github_client.TestUserHasPermission object at 0x7fcdc327da70>
def test_user_has_permission_error(self):
"""Test permission check returns False on error."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_repo = Mock()
mock_repo.get_collaborator_permission.side_effect = Exception("API Error")
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
result = client.user_has_permission(123456, "owner/repo", "carol", "maintain")
> assert result is False
E assert True is False
tests/test_github_client.py:756: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-05-16 20:08:03 [warning ] Failed to check collaborator permission for carol in owner/repo: API Error extra={'repo': 'owner/repo', 'username': 'carol', 'installation_id': 123456, 'error': 'API Error'}
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestUserHasPermission::test_user_has_permission_error - assert True is False
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 62 passed, 3 deselected in 0.82s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceAndWithOr, occurrence: 0
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -470,7 +470,7 @@
# Find all reviews by bot that are approvals
bot_review_ids = []
for review in pr.get_reviews():
- if review.user.login == bot_user and review.state == "APPROVED":
+ if review.user.login == bot_user or review.state == "APPROVED":
bot_review_ids.append(review.id)
duration = time.time() - start_time..........................................................F
=================================== FAILURES ===================================
_______________ TestFindBotReviews.test_find_bot_reviews_success _______________
self = <tests.test_github_client.TestFindBotReviews object at 0x7f26b9da8cd0>
def test_find_bot_reviews_success(self):
"""Test successful bot review finding."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration.get_app.return_value = Mock(slug="stampbot")
mock_integration_cls.return_value = mock_integration
# Create mock reviews
bot_review = Mock()
bot_review.user.login = "stampbot[bot]"
bot_review.state = "APPROVED"
bot_review.id = 123
other_review = Mock()
other_review.user.login = "other-user"
other_review.state = "APPROVED"
other_review.id = 456
mock_pr = Mock()
mock_pr.get_reviews.return_value = [bot_review, other_review]
mock_repo = Mock()
mock_repo.get_pull.return_value = mock_pr
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
result = client.find_bot_reviews(123456, "owner/repo", 42)
> assert result == [123]
E assert [123, 456] == [123]
E
E Left contains one more item: 456
E
E Full diff:
E [
E 123,
E + 456,
E ]
tests/test_github_client.py:608: AssertionError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestFindBotReviews::test_find_bot_reviews_success - assert [123, 456] == [123]
Left contains one more item: 456
Full diff:
[
123,
+ 456,
]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 58 passed, 3 deselected in 0.76s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceOrWithAnd, occurrence: 0
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -117,7 +117,7 @@
# Security: prevent path traversal by ensuring the path doesn't escape
# expected directories and doesn't follow symlinks to unexpected locations
- if ".." in str(key_path) or not os.path.isfile(key_path):
+ if ".." in str(key_path) and not os.path.isfile(key_path):
raise ValueError(f"Invalid private key path: {key}")
try:............................................F
=================================== FAILURES ===================================
________ TestLoadPrivateKey.test_load_private_key_raises_on_file_error _________
self = <tests.test_github_client.TestLoadPrivateKey object at 0x7f0567f59810>
def test_load_private_key_raises_on_file_error(self):
"""Test that _load_private_key raises when file doesn't exist."""
with patch("stampbot.github_client.settings") as mock_settings:
mock_settings.private_key = "/nonexistent/path/to/key.pem"
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
with pytest.raises(ValueError, match="Invalid private key path"):
> client._load_private_key()
tests/test_github_client.py:148:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.github_client.GitHubAppClient object at 0x7f0563dde3f0>
def _load_private_key(self) -> str:
"""Load private key from settings or file.
Returns:
Private key contents as PEM string.
Raises:
RuntimeError: If private key is not configured.
ValueError: If private key is not valid PEM format.
OSError: If private key file cannot be read.
"""
key: str | None = settings.private_key
if key is None:
raise RuntimeError("Private key not configured")
# If it looks like PEM content, use directly
if key.startswith("-----BEGIN"):
pem_content = key
else:
# Treat as file path - read the file
import os
from pathlib import Path
key_path = Path(key).resolve()
# Security: prevent path traversal by ensuring the path doesn't escape
# expected directories and doesn't follow symlinks to unexpected locations
if ".." in str(key_path) and not os.path.isfile(key_path):
raise ValueError(f"Invalid private key path: {key}")
try:
> with open(key_path) as f:
^^^^^^^^^^^^^^
E FileNotFoundError: [Errno 2] No such file or directory: '/nonexistent/path/to/key.pem'
stampbot/github_client.py:124: FileNotFoundError
----------------------------- Captured stdout call -----------------------------
2026-05-16 19:29:52 [error ] Failed to read private key from file: [Errno 2] No such file or directory: '/nonexistent/path/to/key.pem'
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestLoadPrivateKey::test_load_private_key_raises_on_file_error - FileNotFoundError: [Errno 2] No such file or directory: '/nonexistent/path/to/key.pem'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 44 passed, 3 deselected in 0.76s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceOrWithAnd, occurrence: 1
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -383,7 +383,7 @@
{
"github.repo": repo_full_name,
"github.file_path": file_path,
- "github.ref": ref or "default",
+ "github.ref": ref and "default",
"github.installation_id": installation_id,
},
) as span:........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.66s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceContinueWithBreak, occurrence: 0
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -827,7 +827,7 @@
"username": username,
},
)
- continue
+ break
duration = time.time() - start_time
github_api_request_duration_seconds.labels(operation="get_user_teams").observe(........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ExceptionReplacer, occurrence: 0
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -123,7 +123,7 @@
try:
with open(key_path) as f:
pem_content = f.read()
- except Exception as e:
+ except CosmicRayTestingException as e:
logger.error("Failed to read private key from file: %s", e)
raise
.............................................F
=================================== FAILURES ===================================
________ TestLoadPrivateKey.test_load_private_key_raises_on_read_error _________
self = <stampbot.github_client.GitHubAppClient object at 0x7f4b0fc03f20>
def _load_private_key(self) -> str:
"""Load private key from settings or file.
Returns:
Private key contents as PEM string.
Raises:
RuntimeError: If private key is not configured.
ValueError: If private key is not valid PEM format.
OSError: If private key file cannot be read.
"""
key: str | None = settings.private_key
if key is None:
raise RuntimeError("Private key not configured")
# If it looks like PEM content, use directly
if key.startswith("-----BEGIN"):
pem_content = key
else:
# Treat as file path - read the file
import os
from pathlib import Path
key_path = Path(key).resolve()
# Security: prevent path traversal by ensuring the path doesn't escape
# expected directories and doesn't follow symlinks to unexpected locations
if ".." in str(key_path) or not os.path.isfile(key_path):
raise ValueError(f"Invalid private key path: {key}")
try:
> with open(key_path) as f:
^^^^^^^^^^^^^^
stampbot/github_client.py:124:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1175: in __call__
return self._mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1179: in _mock_call
return self._execute_mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <MagicMock name='open' id='139960363868448'>
args = (PosixPath('/tmp/pytest-of-runner/pytest-394/test_load_private_key_raises_o0/private-key.pem'),)
kwargs = {}, effect = PermissionError('Access denied')
def _execute_mock_call(self, /, *args, **kwargs):
# separate from _increment_mock_call so that awaited functions are
# executed separately from their call, also AsyncMock overrides this method
effect = self.side_effect
if effect is not None:
if _is_exception(effect):
> raise effect
E PermissionError: Access denied
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1234: PermissionError
During handling of the above exception, another exception occurred:
self = <tests.test_github_client.TestLoadPrivateKey object at 0x7f4b13d40710>
tmp_path = PosixPath('/tmp/pytest-of-runner/pytest-394/test_load_private_key_raises_o0')
def test_load_private_key_raises_on_read_error(self, tmp_path):
"""Test that _load_private_key raises when file read fails."""
key_file = tmp_path / "private-key.pem"
key_file.write_text("dummy")
with patch("stampbot.github_client.settings") as mock_settings:
mock_settings.private_key = str(key_file)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
# Mock open to raise an exception after the file existence check passes
with patch("builtins.open", side_effect=PermissionError("Access denied")):
with pytest.raises(PermissionError, match="Access denied"):
> client._load_private_key()
tests/test_github_client.py:165:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.github_client.GitHubAppClient object at 0x7f4b0fc03f20>
def _load_private_key(self) -> str:
"""Load private key from settings or file.
Returns:
Private key contents as PEM string.
Raises:
RuntimeError: If private key is not configured.
ValueError: If private key is not valid PEM format.
OSError: If private key file cannot be read.
"""
key: str | None = settings.private_key
if key is None:
raise RuntimeError("Private key not configured")
# If it looks like PEM content, use directly
if key.startswith("-----BEGIN"):
pem_content = key
else:
# Treat as file path - read the file
import os
from pathlib import Path
key_path = Path(key).resolve()
# Security: prevent path traversal by ensuring the path doesn't escape
# expected directories and doesn't follow symlinks to unexpected locations
if ".." in str(key_path) or not os.path.isfile(key_path):
raise ValueError(f"Invalid private key path: {key}")
try:
with open(key_path) as f:
pem_content = f.read()
> except CosmicRayTestingException as e:
^^^^^^^^^^^^^^^^^^^^^^^^^
E NameError: name 'CosmicRayTestingException' is not defined
stampbot/github_client.py:126: NameError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestLoadPrivateKey::test_load_private_key_raises_on_read_error - NameError: name 'CosmicRayTestingException' is not defined
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 45 passed, 3 deselected in 0.84s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ExceptionReplacer, occurrence: 1
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -175,7 +175,7 @@
set_span_ok(span)
return client
- except Exception as e:
+ except CosmicRayTestingException as e:
duration = time.time() - start_time
github_api_request_duration_seconds.labels(operation="get_token").observe(duration)
github_api_requests_total.labels(operation="get_token", status="failure").inc()................................................F
=================================== FAILURES ===================================
________ TestGetInstallationClient.test_get_installation_client_failure ________
self = <stampbot.github_client.GitHubAppClient object at 0x7f9b880b5160>
installation_id = 123456
def _get_installation_client(self, installation_id: int) -> Github:
"""Get authenticated GitHub client for an installation.
Args:
installation_id: GitHub App installation ID.
Returns:
Authenticated Github client instance with timeout and retry configured.
Raises:
github.GithubException: If token exchange fails.
"""
start_time = time.time()
with create_span(
"github.get_installation_token",
{"github.installation_id": installation_id},
) as span:
try:
> auth = self.integration.get_access_token(installation_id)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/github_client.py:155:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1175: in __call__
return self._mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1179: in _mock_call
return self._execute_mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <Mock name='GithubIntegration().get_access_token' id='140305960190032'>
args = (123456,), kwargs = {}, effect = Exception('Token exchange failed')
def _execute_mock_call(self, /, *args, **kwargs):
# separate from _increment_mock_call so that awaited functions are
# executed separately from their call, also AsyncMock overrides this method
effect = self.side_effect
if effect is not None:
if _is_exception(effect):
> raise effect
E Exception: Token exchange failed
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1234: Exception
During handling of the above exception, another exception occurred:
self = <tests.test_github_client.TestGetInstallationClient object at 0x7f9b8ae44190>
def test_get_installation_client_failure(self):
"""Test installation client creation failure."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_integration.get_access_token.side_effect = Exception("Token exchange failed")
mock_integration_cls.return_value = mock_integration
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
with pytest.raises(Exception, match="Token exchange failed"):
> client._get_installation_client(123456)
tests/test_github_client.py:240:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.github_client.GitHubAppClient object at 0x7f9b880b5160>
installation_id = 123456
def _get_installation_client(self, installation_id: int) -> Github:
"""Get authenticated GitHub client for an installation.
Args:
installation_id: GitHub App installation ID.
Returns:
Authenticated Github client instance with timeout and retry configured.
Raises:
github.GithubException: If token exchange fails.
"""
start_time = time.time()
with create_span(
"github.get_installation_token",
{"github.installation_id": installation_id},
) as span:
try:
auth = self.integration.get_access_token(installation_id)
# Configure retry with exponential backoff
retry = Retry(
total=GITHUB_API_RETRY_TOTAL,
backoff_factor=GITHUB_API_RETRY_BACKOFF,
status_forcelist=[500, 502, 503, 504],
allowed_methods=["GET", "POST", "PUT", "DELETE", "PATCH"],
)
client = Github(
auth=Auth.Token(auth.token),
timeout=GITHUB_API_TIMEOUT,
retry=retry,
)
duration = time.time() - start_time
github_api_request_duration_seconds.labels(operation="get_token").observe(duration)
github_api_requests_total.labels(operation="get_token", status="success").inc()
set_span_ok(span)
return client
> except CosmicRayTestingException as e:
^^^^^^^^^^^^^^^^^^^^^^^^^
E NameError: name 'CosmicRayTestingException' is not defined
stampbot/github_client.py:178: NameError
During handling of the above exception, another exception occurred:
self = <tests.test_github_client.TestGetInstallationClient object at 0x7f9b8ae44190>
def test_get_installation_client_failure(self):
"""Test installation client creation failure."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_integration.get_access_token.side_effect = Exception("Token exchange failed")
mock_integration_cls.return_value = mock_integration
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
> with pytest.raises(Exception, match="Token exchange failed"):
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E AssertionError: Regex pattern did not match.
E Expected regex: 'Token exchange failed'
E Actual message: "name 'CosmicRayTestingException' is not defined"
tests/test_github_client.py:239: AssertionError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetInstallationClient::test_get_installation_client_failure - AssertionError: Regex pattern did not match.
Expected regex: 'Token exchange failed'
Actual message: "name 'CosmicRayTestingException' is not defined"
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 48 passed, 3 deselected in 0.85s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ExceptionReplacer, occurrence: 2
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -197,7 +197,7 @@
github_api_rate_limit_limit.labels(installation_id=str(installation_id)).set(
rate_limit.core.limit # type: ignore[attr-defined]
)
- except Exception:
+ except CosmicRayTestingException:
# Don't fail operations due to rate limit metric errors
pass
..................................................F
=================================== FAILURES ===================================
_ TestUpdateRateLimitMetrics.test_update_rate_limit_metrics_handles_exception __
self = <stampbot.github_client.GitHubAppClient object at 0x7f2d9f648b90>
client = <Mock id='139833924946976'>, installation_id = 123456
def _update_rate_limit_metrics(self, client: Github, installation_id: int) -> None:
"""Update rate limit metrics from GitHub client.
Args:
client: Authenticated GitHub client.
installation_id: GitHub App installation ID for metric labels.
"""
try:
> rate_limit = client.get_rate_limit()
^^^^^^^^^^^^^^^^^^^^^^^
stampbot/github_client.py:193:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1175: in __call__
return self._mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1179: in _mock_call
return self._execute_mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <Mock name='mock.get_rate_limit' id='139833924947312'>, args = ()
kwargs = {}, effect = Exception('API Error')
def _execute_mock_call(self, /, *args, **kwargs):
# separate from _increment_mock_call so that awaited functions are
# executed separately from their call, also AsyncMock overrides this method
effect = self.side_effect
if effect is not None:
if _is_exception(effect):
> raise effect
E Exception: API Error
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1234: Exception
During handling of the above exception, another exception occurred:
self = <tests.test_github_client.TestUpdateRateLimitMetrics object at 0x7f2da38c4410>
def test_update_rate_limit_metrics_handles_exception(self):
"""Test that rate limit metrics errors are silently ignored."""
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
mock_github_client = Mock()
mock_github_client.get_rate_limit.side_effect = Exception("API Error")
# Should not raise - errors are silently ignored
> client._update_rate_limit_metrics(mock_github_client, 123456)
tests/test_github_client.py:271:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.github_client.GitHubAppClient object at 0x7f2d9f648b90>
client = <Mock id='139833924946976'>, installation_id = 123456
def _update_rate_limit_metrics(self, client: Github, installation_id: int) -> None:
"""Update rate limit metrics from GitHub client.
Args:
client: Authenticated GitHub client.
installation_id: GitHub App installation ID for metric labels.
"""
try:
rate_limit = client.get_rate_limit()
github_api_rate_limit_remaining.labels(installation_id=str(installation_id)).set(
rate_limit.core.remaining # type: ignore[attr-defined]
)
github_api_rate_limit_limit.labels(installation_id=str(installation_id)).set(
rate_limit.core.limit # type: ignore[attr-defined]
)
> except CosmicRayTestingException:
^^^^^^^^^^^^^^^^^^^^^^^^^
E NameError: name 'CosmicRayTestingException' is not defined
stampbot/github_client.py:200: NameError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestUpdateRateLimitMetrics::test_update_rate_limit_metrics_handles_exception - NameError: name 'CosmicRayTestingException' is not defined
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 50 passed, 3 deselected in 0.85s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ExceptionReplacer, occurrence: 3
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -259,7 +259,7 @@
)
return True
- except Exception as e:
+ except CosmicRayTestingException as e:
duration = time.time() - start_time
github_api_request_duration_seconds.labels(operation="approve").observe(duration)
github_api_requests_total.labels(operation="approve", status="failure").inc()....................................................F
=================================== FAILURES ===================================
____________________ TestApprovePR.test_approve_pr_failure _____________________
self = <stampbot.github_client.GitHubAppClient object at 0x7f4bf1066d00>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42
comment = 'Auto-approved by Stampbot'
def approve_pr(
self,
installation_id: int,
repo_full_name: str,
pr_number: int,
comment: str = "Auto-approved by Stampbot",
) -> bool:
"""Approve a pull request.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name (owner/repo)
pr_number: Pull request number
comment: Review comment
Returns:
True if successful, False otherwise
"""
start_time = time.time()
with create_span(
"github.approve_pr",
{
"github.repo": repo_full_name,
"github.pr_number": pr_number,
"github.installation_id": installation_id,
},
) as span:
try:
client = self._get_installation_client(installation_id)
repo = client.get_repo(repo_full_name)
pr = repo.get_pull(pr_number)
# Create approval review
> pr.create_review(
body=comment,
event="APPROVE",
)
stampbot/github_client.py:238:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1175: in __call__
return self._mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1179: in _mock_call
return self._execute_mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <Mock name='Github().get_repo().get_pull().create_review' id='139964143417872'>
args = (), kwargs = {'body': 'Auto-approved by Stampbot', 'event': 'APPROVE'}
effect = Exception('API Error')
def _execute_mock_call(self, /, *args, **kwargs):
# separate from _increment_mock_call so that awaited functions are
# executed separately from their call, also AsyncMock overrides this method
effect = self.side_effect
if effect is not None:
if _is_exception(effect):
> raise effect
E Exception: API Error
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1234: Exception
During handling of the above exception, another exception occurred:
self = <tests.test_github_client.TestApprovePR object at 0x7f4bf4f80690>
def test_approve_pr_failure(self):
"""Test PR approval failure."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_pr = Mock()
mock_pr.create_review.side_effect = Exception("API Error")
mock_repo = Mock()
mock_repo.get_pull.return_value = mock_pr
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
> result = client.approve_pr(123456, "owner/repo", 42)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_github_client.py:350:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.github_client.GitHubAppClient object at 0x7f4bf1066d00>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42
comment = 'Auto-approved by Stampbot'
def approve_pr(
self,
installation_id: int,
repo_full_name: str,
pr_number: int,
comment: str = "Auto-approved by Stampbot",
) -> bool:
"""Approve a pull request.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name (owner/repo)
pr_number: Pull request number
comment: Review comment
Returns:
True if successful, False otherwise
"""
start_time = time.time()
with create_span(
"github.approve_pr",
{
"github.repo": repo_full_name,
"github.pr_number": pr_number,
"github.installation_id": installation_id,
},
) as span:
try:
client = self._get_installation_client(installation_id)
repo = client.get_repo(repo_full_name)
pr = repo.get_pull(pr_number)
# Create approval review
pr.create_review(
body=comment,
event="APPROVE",
)
duration = time.time() - start_time
github_api_request_duration_seconds.labels(operation="approve").observe(duration)
github_api_requests_total.labels(operation="approve", status="success").inc()
self._update_rate_limit_metrics(client, installation_id)
add_span_attributes(span, {"github.result": "approved"})
set_span_ok(span)
logger.info(
f"Approved PR #{pr_number} in {repo_full_name}",
extra={
"repo": repo_full_name,
"pr_number": pr_number,
"installation_id": installation_id,
},
)
return True
> except CosmicRayTestingException as e:
^^^^^^^^^^^^^^^^^^^^^^^^^
E NameError: name 'CosmicRayTestingException' is not defined
stampbot/github_client.py:262: NameError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestApprovePR::test_approve_pr_failure - NameError: name 'CosmicRayTestingException' is not defined
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 52 passed, 3 deselected in 0.85s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ExceptionReplacer, occurrence: 4
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -337,7 +337,7 @@
)
return True
- except Exception as e:
+ except CosmicRayTestingException as e:
duration = time.time() - start_time
github_api_request_duration_seconds.labels(operation="dismiss").observe(duration)
github_api_requests_total.labels(operation="dismiss", status="failure").inc()......................................................F
=================================== FAILURES ===================================
______________ TestDismissApproval.test_dismiss_approval_failure _______________
self = <stampbot.github_client.GitHubAppClient object at 0x7fa1cda0a8f0>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42
review_id = 999, message = 'Approval dismissed by Stampbot'
def dismiss_approval(
self,
installation_id: int,
repo_full_name: str,
pr_number: int,
review_id: int,
message: str = "Approval dismissed by Stampbot",
) -> bool:
"""Dismiss a pull request approval.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name (owner/repo)
pr_number: Pull request number
review_id: Review ID to dismiss
message: Dismissal message
Returns:
True if successful, False otherwise
"""
start_time = time.time()
with create_span(
"github.dismiss_approval",
{
"github.repo": repo_full_name,
"github.pr_number": pr_number,
"github.review_id": review_id,
"github.installation_id": installation_id,
},
) as span:
try:
client = self._get_installation_client(installation_id)
repo = client.get_repo(repo_full_name)
pr = repo.get_pull(pr_number)
# Get the review and dismiss it
review = pr.get_review(review_id)
> review.dismiss(message)
stampbot/github_client.py:318:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1175: in __call__
return self._mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1179: in _mock_call
return self._execute_mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <Mock name='Github().get_repo().get_pull().get_review().dismiss' id='140332917306576'>
args = ('Approval dismissed by Stampbot',), kwargs = {}
effect = Exception('API Error')
def _execute_mock_call(self, /, *args, **kwargs):
# separate from _increment_mock_call so that awaited functions are
# executed separately from their call, also AsyncMock overrides this method
effect = self.side_effect
if effect is not None:
if _is_exception(effect):
> raise effect
E Exception: API Error
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1234: Exception
During handling of the above exception, another exception occurred:
self = <tests.test_github_client.TestDismissApproval object at 0x7fa1d1a34910>
def test_dismiss_approval_failure(self):
"""Test approval dismissal failure."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_review = Mock()
mock_review.dismiss.side_effect = Exception("API Error")
mock_pr = Mock()
mock_pr.get_review.return_value = mock_review
mock_repo = Mock()
mock_repo.get_pull.return_value = mock_pr
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
> result = client.dismiss_approval(123456, "owner/repo", 42, 999)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_github_client.py:435:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.github_client.GitHubAppClient object at 0x7fa1cda0a8f0>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42
review_id = 999, message = 'Approval dismissed by Stampbot'
def dismiss_approval(
self,
installation_id: int,
repo_full_name: str,
pr_number: int,
review_id: int,
message: str = "Approval dismissed by Stampbot",
) -> bool:
"""Dismiss a pull request approval.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name (owner/repo)
pr_number: Pull request number
review_id: Review ID to dismiss
message: Dismissal message
Returns:
True if successful, False otherwise
"""
start_time = time.time()
with create_span(
"github.dismiss_approval",
{
"github.repo": repo_full_name,
"github.pr_number": pr_number,
"github.review_id": review_id,
"github.installation_id": installation_id,
},
) as span:
try:
client = self._get_installation_client(installation_id)
repo = client.get_repo(repo_full_name)
pr = repo.get_pull(pr_number)
# Get the review and dismiss it
review = pr.get_review(review_id)
review.dismiss(message)
duration = time.time() - start_time
github_api_request_duration_seconds.labels(operation="dismiss").observe(duration)
github_api_requests_total.labels(operation="dismiss", status="success").inc()
self._update_rate_limit_metrics(client, installation_id)
add_span_attributes(span, {"github.result": "dismissed"})
set_span_ok(span)
logger.info(
f"Dismissed approval for PR #{pr_number} in {repo_full_name}",
extra={
"repo": repo_full_name,
"pr_number": pr_number,
"installation_id": installation_id,
"review_id": review_id,
},
)
return True
> except CosmicRayTestingException as e:
^^^^^^^^^^^^^^^^^^^^^^^^^
E NameError: name 'CosmicRayTestingException' is not defined
stampbot/github_client.py:340: NameError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestDismissApproval::test_dismiss_approval_failure - NameError: name 'CosmicRayTestingException' is not defined
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 54 passed, 3 deselected in 0.86s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ExceptionReplacer, occurrence: 5
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -413,7 +413,7 @@
return content.decoded_content.decode("utf-8")
- except Exception as e:
+ except CosmicRayTestingException as e:
duration = time.time() - start_time
github_api_request_duration_seconds.labels(operation="get_file").observe(duration)
github_api_requests_total.labels(operation="get_file", status="not_found").inc().........................................................F
=================================== FAILURES ===================================
___________ TestGetRepoFile.test_get_repo_file_returns_none_on_error ___________
self = <stampbot.github_client.GitHubAppClient object at 0x7f66f7154dd0>
installation_id = 123456, repo_full_name = 'owner/repo'
file_path = 'nonexistent.toml', ref = None
def get_repo_file(
self,
installation_id: int,
repo_full_name: str,
file_path: str,
ref: str | None = None,
) -> str | None:
"""Get file content from repository.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name (owner/repo)
file_path: Path to file in repository
ref: Git reference (branch, tag, commit). Defaults to default branch
Returns:
File content as string, or None if not found
"""
start_time = time.time()
with create_span(
"github.get_file",
{
"github.repo": repo_full_name,
"github.file_path": file_path,
"github.ref": ref or "default",
"github.installation_id": installation_id,
},
) as span:
try:
client = self._get_installation_client(installation_id)
repo = client.get_repo(repo_full_name)
> content = repo.get_contents(file_path, ref=ref) # type: ignore[arg-type]
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/github_client.py:394:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1175: in __call__
return self._mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1179: in _mock_call
return self._execute_mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <Mock name='Github().get_repo().get_contents' id='140080209069792'>
args = ('nonexistent.toml',), kwargs = {'ref': None}
effect = Exception('File not found')
def _execute_mock_call(self, /, *args, **kwargs):
# separate from _increment_mock_call so that awaited functions are
# executed separately from their call, also AsyncMock overrides this method
effect = self.side_effect
if effect is not None:
if _is_exception(effect):
> raise effect
E Exception: File not found
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1234: Exception
During handling of the above exception, another exception occurred:
self = <tests.test_github_client.TestGetRepoFile object at 0x7f66fb55d940>
def test_get_repo_file_returns_none_on_error(self):
"""Test that get_repo_file returns None when file not found."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_repo = Mock()
mock_repo.get_contents.side_effect = Exception("File not found")
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
> result = client.get_repo_file(123456, "owner/repo", "nonexistent.toml")
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_github_client.py:551:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.github_client.GitHubAppClient object at 0x7f66f7154dd0>
installation_id = 123456, repo_full_name = 'owner/repo'
file_path = 'nonexistent.toml', ref = None
def get_repo_file(
self,
installation_id: int,
repo_full_name: str,
file_path: str,
ref: str | None = None,
) -> str | None:
"""Get file content from repository.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name (owner/repo)
file_path: Path to file in repository
ref: Git reference (branch, tag, commit). Defaults to default branch
Returns:
File content as string, or None if not found
"""
start_time = time.time()
with create_span(
"github.get_file",
{
"github.repo": repo_full_name,
"github.file_path": file_path,
"github.ref": ref or "default",
"github.installation_id": installation_id,
},
) as span:
try:
client = self._get_installation_client(installation_id)
repo = client.get_repo(repo_full_name)
content = repo.get_contents(file_path, ref=ref) # type: ignore[arg-type]
if isinstance(content, list):
duration = time.time() - start_time
github_api_request_duration_seconds.labels(operation="get_file").observe(
duration
)
github_api_requests_total.labels(operation="get_file", status="not_found").inc()
add_span_attributes(span, {"github.result": "not_found"})
set_span_ok(span)
return None
duration = time.time() - start_time
github_api_request_duration_seconds.labels(operation="get_file").observe(duration)
github_api_requests_total.labels(operation="get_file", status="success").inc()
self._update_rate_limit_metrics(client, installation_id)
add_span_attributes(span, {"github.result": "found"})
set_span_ok(span)
return content.decoded_content.decode("utf-8")
> except CosmicRayTestingException as e:
^^^^^^^^^^^^^^^^^^^^^^^^^
E NameError: name 'CosmicRayTestingException' is not defined
stampbot/github_client.py:416: NameError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetRepoFile::test_get_repo_file_returns_none_on_error - NameError: name 'CosmicRayTestingException' is not defined
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 57 passed, 3 deselected in 0.90s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ExceptionReplacer, occurrence: 6
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -488,7 +488,7 @@
return bot_review_ids
- except Exception as e:
+ except CosmicRayTestingException as e:
duration = time.time() - start_time
github_api_request_duration_seconds.labels(operation="find_reviews").observe(
duration...........................................................F
=================================== FAILURES ===================================
_______ TestFindBotReviews.test_find_bot_reviews_returns_empty_on_error ________
self = <stampbot.github_client.GitHubAppClient object at 0x7f66b5664f30>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42
def find_bot_reviews(
self,
installation_id: int,
repo_full_name: str,
pr_number: int,
) -> list[int]:
"""Find all reviews created by this bot on a PR.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name (owner/repo)
pr_number: Pull request number
Returns:
List of review IDs created by the bot
"""
start_time = time.time()
with create_span(
"github.find_bot_reviews",
{
"github.repo": repo_full_name,
"github.pr_number": pr_number,
"github.installation_id": installation_id,
},
) as span:
try:
client = self._get_installation_client(installation_id)
> repo = client.get_repo(repo_full_name)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/github_client.py:462:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1175: in __call__
return self._mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1179: in _mock_call
return self._execute_mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <Mock name='Github().get_repo' id='140079107244784'>
args = ('owner/repo',), kwargs = {}, effect = Exception('API Error')
def _execute_mock_call(self, /, *args, **kwargs):
# separate from _increment_mock_call so that awaited functions are
# executed separately from their call, also AsyncMock overrides this method
effect = self.side_effect
if effect is not None:
if _is_exception(effect):
> raise effect
E Exception: API Error
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1234: Exception
During handling of the above exception, another exception occurred:
self = <tests.test_github_client.TestFindBotReviews object at 0x7f66b9a88e10>
def test_find_bot_reviews_returns_empty_on_error(self):
"""Test that find_bot_reviews returns empty list on error."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_github = Mock()
mock_github.get_repo.side_effect = Exception("API Error")
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
> result = client.find_bot_reviews(123456, "owner/repo", 42)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_github_client.py:640:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.github_client.GitHubAppClient object at 0x7f66b5664f30>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42
def find_bot_reviews(
self,
installation_id: int,
repo_full_name: str,
pr_number: int,
) -> list[int]:
"""Find all reviews created by this bot on a PR.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name (owner/repo)
pr_number: Pull request number
Returns:
List of review IDs created by the bot
"""
start_time = time.time()
with create_span(
"github.find_bot_reviews",
{
"github.repo": repo_full_name,
"github.pr_number": pr_number,
"github.installation_id": installation_id,
},
) as span:
try:
client = self._get_installation_client(installation_id)
repo = client.get_repo(repo_full_name)
pr = repo.get_pull(pr_number)
# Get bot user via JWT-authenticated integration (installation tokens
# cannot call GET /user, which is restricted by GitHub Apps integration)
app_info = self.integration.get_app()
bot_user = f"{app_info.slug}[bot]"
# Find all reviews by bot that are approvals
bot_review_ids = []
for review in pr.get_reviews():
if review.user.login == bot_user and review.state == "APPROVED":
bot_review_ids.append(review.id)
duration = time.time() - start_time
github_api_request_duration_seconds.labels(operation="find_reviews").observe(
duration
)
github_api_requests_total.labels(operation="find_reviews", status="success").inc()
self._update_rate_limit_metrics(client, installation_id)
add_span_attributes(
span, {"github.reviews_found": len(bot_review_ids), "github.bot_user": bot_user}
)
set_span_ok(span)
return bot_review_ids
> except CosmicRayTestingException as e:
^^^^^^^^^^^^^^^^^^^^^^^^^
E NameError: name 'CosmicRayTestingException' is not defined
stampbot/github_client.py:491: NameError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestFindBotReviews::test_find_bot_reviews_returns_empty_on_error - NameError: name 'CosmicRayTestingException' is not defined
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 59 passed, 3 deselected in 0.87s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ExceptionReplacer, occurrence: 7
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -567,7 +567,7 @@
)
return True
- except Exception as e:
+ except CosmicRayTestingException as e:
duration = time.time() - start_time
github_api_request_duration_seconds.labels(operation="comment").observe(duration)
github_api_requests_total.labels(operation="comment", status="failure").inc()........................................................................ [ 34%]
..F
=================================== FAILURES ===================================
_______ TestCreatePrReviewComment.test_create_pr_review_comment_failure ________
self = <stampbot.github_client.GitHubAppClient object at 0x7fb7bd9ba5d0>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42
message = 'Config error'
def create_pr_review_comment(
self,
installation_id: int,
repo_full_name: str,
pr_number: int,
message: str,
) -> bool:
"""Create a comment review on a pull request.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name (owner/repo)
pr_number: Pull request number
message: Review comment body
Returns:
True if successful, False otherwise
"""
start_time = time.time()
with create_span(
"github.create_review_comment",
{
"github.repo": repo_full_name,
"github.pr_number": pr_number,
"github.installation_id": installation_id,
},
) as span:
try:
client = self._get_installation_client(installation_id)
repo = client.get_repo(repo_full_name)
> pr = repo.get_pull(pr_number)
^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/github_client.py:545:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1175: in __call__
return self._mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1179: in _mock_call
return self._execute_mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <Mock name='Github().get_repo().get_pull' id='140427136764960'>
args = (42,), kwargs = {}, effect = Exception('API Error')
def _execute_mock_call(self, /, *args, **kwargs):
# separate from _increment_mock_call so that awaited functions are
# executed separately from their call, also AsyncMock overrides this method
effect = self.side_effect
if effect is not None:
if _is_exception(effect):
> raise effect
E Exception: API Error
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1234: Exception
During handling of the above exception, another exception occurred:
self = <tests.test_github_client.TestCreatePrReviewComment object at 0x7fb7c1b65810>
def test_create_pr_review_comment_failure(self):
"""Test review comment creation handles errors."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_repo = Mock()
mock_repo.get_pull.side_effect = Exception("API Error")
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
> result = client.create_pr_review_comment(
123456,
"owner/repo",
42,
"Config error",
)
tests/test_github_client.py:1248:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.github_client.GitHubAppClient object at 0x7fb7bd9ba5d0>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42
message = 'Config error'
def create_pr_review_comment(
self,
installation_id: int,
repo_full_name: str,
pr_number: int,
message: str,
) -> bool:
"""Create a comment review on a pull request.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name (owner/repo)
pr_number: Pull request number
message: Review comment body
Returns:
True if successful, False otherwise
"""
start_time = time.time()
with create_span(
"github.create_review_comment",
{
"github.repo": repo_full_name,
"github.pr_number": pr_number,
"github.installation_id": installation_id,
},
) as span:
try:
client = self._get_installation_client(installation_id)
repo = client.get_repo(repo_full_name)
pr = repo.get_pull(pr_number)
pr.create_review(body=message, event="COMMENT")
duration = time.time() - start_time
github_api_request_duration_seconds.labels(operation="comment").observe(duration)
github_api_requests_total.labels(operation="comment", status="success").inc()
self._update_rate_limit_metrics(client, installation_id)
add_span_attributes(span, {"github.result": "commented"})
set_span_ok(span)
logger.info(
"Posted config error review on PR #%d in %s",
pr_number,
repo_full_name,
extra={
"repo": repo_full_name,
"pr_number": pr_number,
"installation_id": installation_id,
},
)
return True
> except CosmicRayTestingException as e:
^^^^^^^^^^^^^^^^^^^^^^^^^
E NameError: name 'CosmicRayTestingException' is not defined
stampbot/github_client.py:570: NameError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestCreatePrReviewComment::test_create_pr_review_comment_failure - NameError: name 'CosmicRayTestingException' is not defined
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 74 passed, 3 deselected in 0.90s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ExceptionReplacer, occurrence: 8
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -629,7 +629,7 @@
set_span_ok(span)
return True
- except GithubException as e:
+ except CosmicRayTestingException as e:
duration = time.time() - start_time
github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
github_api_requests_total.labels(operation="get_label", status="failure").inc()......................................................................F
=================================== FAILURES ===================================
________________ TestRepoHasLabel.test_repo_has_label_not_found ________________
self = <stampbot.github_client.GitHubAppClient object at 0x7f4215f41e50>
installation_id = 123456, repo_full_name = 'owner/repo', label_name = 'missing'
def repo_has_label(
self,
installation_id: int,
repo_full_name: str,
label_name: str,
) -> bool | None:
"""Check whether a repository has a label.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name (owner/repo)
label_name: Label name to look up
Returns:
True if label exists, False if not found, None if unknown due to error
"""
start_time = time.time()
with create_span(
"github.get_label",
{
"github.repo": repo_full_name,
"github.label": label_name,
"github.installation_id": installation_id,
},
) as span:
try:
client = self._get_installation_client(installation_id)
repo = client.get_repo(repo_full_name)
> repo.get_label(label_name)
stampbot/github_client.py:620:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1175: in __call__
return self._mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1179: in _mock_call
return self._execute_mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <Mock name='Github().get_repo().get_label' id='139921812711680'>
args = ('missing',), kwargs = {}
effect = GithubException(404 {"message": "Not Found"})
def _execute_mock_call(self, /, *args, **kwargs):
# separate from _increment_mock_call so that awaited functions are
# executed separately from their call, also AsyncMock overrides this method
effect = self.side_effect
if effect is not None:
if _is_exception(effect):
> raise effect
E github.GithubException.GithubException: 404 {"message": "Not Found"}
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1234: GithubException
During handling of the above exception, another exception occurred:
self = <tests.test_github_client.TestRepoHasLabel object at 0x7f421a2a1590>
def test_repo_has_label_not_found(self):
"""Test repo_has_label returns False when label is missing."""
from github.GithubException import GithubException
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_repo = Mock()
mock_repo.get_label.side_effect = GithubException(404, {"message": "Not Found"}, None)
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
> result = client.repo_has_label(123456, "owner/repo", "missing")
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_github_client.py:1090:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.github_client.GitHubAppClient object at 0x7f4215f41e50>
installation_id = 123456, repo_full_name = 'owner/repo', label_name = 'missing'
def repo_has_label(
self,
installation_id: int,
repo_full_name: str,
label_name: str,
) -> bool | None:
"""Check whether a repository has a label.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name (owner/repo)
label_name: Label name to look up
Returns:
True if label exists, False if not found, None if unknown due to error
"""
start_time = time.time()
with create_span(
"github.get_label",
{
"github.repo": repo_full_name,
"github.label": label_name,
"github.installation_id": installation_id,
},
) as span:
try:
client = self._get_installation_client(installation_id)
repo = client.get_repo(repo_full_name)
repo.get_label(label_name)
duration = time.time() - start_time
github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
github_api_requests_total.labels(operation="get_label", status="success").inc()
self._update_rate_limit_metrics(client, installation_id)
add_span_attributes(span, {"github.label_found": True})
set_span_ok(span)
return True
> except CosmicRayTestingException as e:
^^^^^^^^^^^^^^^^^^^^^^^^^
E NameError: name 'CosmicRayTestingException' is not defined
stampbot/github_client.py:632: NameError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestRepoHasLabel::test_repo_has_label_not_found - NameError: name 'CosmicRayTestingException' is not defined
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 70 passed, 3 deselected in 0.93s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ExceptionReplacer, occurrence: 9
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -654,7 +654,7 @@
)
return None
- except Exception as e:
+ except CosmicRayTestingException as e:
duration = time.time() - start_time
github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
github_api_requests_total.labels(operation="get_label", status="failure").inc()........................................................................ [ 34%]
F
=================================== FAILURES ===================================
__________________ TestRepoHasLabel.test_repo_has_label_error __________________
self = <stampbot.github_client.GitHubAppClient object at 0x7f3614363b30>
installation_id = 123456, repo_full_name = 'owner/repo'
label_name = 'autoapprove'
def repo_has_label(
self,
installation_id: int,
repo_full_name: str,
label_name: str,
) -> bool | None:
"""Check whether a repository has a label.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name (owner/repo)
label_name: Label name to look up
Returns:
True if label exists, False if not found, None if unknown due to error
"""
start_time = time.time()
with create_span(
"github.get_label",
{
"github.repo": repo_full_name,
"github.label": label_name,
"github.installation_id": installation_id,
},
) as span:
try:
client = self._get_installation_client(installation_id)
repo = client.get_repo(repo_full_name)
> repo.get_label(label_name)
stampbot/github_client.py:620:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1175: in __call__
return self._mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1179: in _mock_call
return self._execute_mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <Mock name='Github().get_repo().get_label' id='139870243952288'>
args = ('autoapprove',), kwargs = {}, effect = Exception('API Error')
def _execute_mock_call(self, /, *args, **kwargs):
# separate from _increment_mock_call so that awaited functions are
# executed separately from their call, also AsyncMock overrides this method
effect = self.side_effect
if effect is not None:
if _is_exception(effect):
> raise effect
E Exception: API Error
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1234: Exception
During handling of the above exception, another exception occurred:
self = <tests.test_github_client.TestRepoHasLabel object at 0x7f36188fa060>
def test_repo_has_label_error(self):
"""Test repo_has_label returns None on error."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_repo = Mock()
mock_repo.get_label.side_effect = Exception("API Error")
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
> result = client.repo_has_label(123456, "owner/repo", "autoapprove")
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_github_client.py:1164:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.github_client.GitHubAppClient object at 0x7f3614363b30>
installation_id = 123456, repo_full_name = 'owner/repo'
label_name = 'autoapprove'
def repo_has_label(
self,
installation_id: int,
repo_full_name: str,
label_name: str,
) -> bool | None:
"""Check whether a repository has a label.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name (owner/repo)
label_name: Label name to look up
Returns:
True if label exists, False if not found, None if unknown due to error
"""
start_time = time.time()
with create_span(
"github.get_label",
{
"github.repo": repo_full_name,
"github.label": label_name,
"github.installation_id": installation_id,
},
) as span:
try:
client = self._get_installation_client(installation_id)
repo = client.get_repo(repo_full_name)
repo.get_label(label_name)
duration = time.time() - start_time
github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
github_api_requests_total.labels(operation="get_label", status="success").inc()
self._update_rate_limit_metrics(client, installation_id)
add_span_attributes(span, {"github.label_found": True})
set_span_ok(span)
return True
except GithubException as e:
duration = time.time() - start_time
github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
github_api_requests_total.labels(operation="get_label", status="failure").inc()
if e.status == 404:
add_span_attributes(span, {"github.label_found": False})
set_span_ok(span)
return False
set_span_error(span, e)
logger.debug(
"Could not verify label %s in %s: %s",
label_name,
repo_full_name,
_sanitize_error(e),
extra={
"repo": repo_full_name,
"label": label_name,
"installation_id": installation_id,
"error": _sanitize_error(e),
},
)
return None
> except CosmicRayTestingException as e:
^^^^^^^^^^^^^^^^^^^^^^^^^
E NameError: name 'CosmicRayTestingException' is not defined
stampbot/github_client.py:657: NameError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestRepoHasLabel::test_repo_has_label_error - NameError: name 'CosmicRayTestingException' is not defined
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 72 passed, 3 deselected in 0.91s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ExceptionReplacer, occurrence: 10
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -712,7 +712,7 @@
try:
permission_index = permission_order.index(permission)
required_index = permission_order.index(required_permission)
- except ValueError:
+ except CosmicRayTestingException:
permission_index = -1
required_index = 99
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ExceptionReplacer, occurrence: 11
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -737,7 +737,7 @@
return has_permission
- except Exception as e:
+ except CosmicRayTestingException as e:
duration = time.time() - start_time
github_api_request_duration_seconds.labels(operation="get_permission").observe(
duration..............................................................F
=================================== FAILURES ===================================
_____________ TestUserHasPermission.test_user_has_permission_error _____________
self = <stampbot.github_client.GitHubAppClient object at 0x7fe73422be90>
installation_id = 123456, repo_full_name = 'owner/repo', username = 'carol'
required_permission = 'maintain'
def user_has_permission(
self,
installation_id: int,
repo_full_name: str,
username: str,
required_permission: str,
) -> bool:
"""Check whether a user has required access to a repository.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name (owner/repo)
username: GitHub username to check
required_permission: Minimum required repository permission
Returns:
True if user meets or exceeds required permission, False otherwise
"""
start_time = time.time()
with create_span(
"github.get_collaborator_permission",
{
"github.repo": repo_full_name,
"github.username": username,
"github.required_permission": required_permission,
"github.installation_id": installation_id,
},
) as span:
try:
client = self._get_installation_client(installation_id)
repo = client.get_repo(repo_full_name)
> permission = repo.get_collaborator_permission(username)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/github_client.py:710:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1175: in __call__
return self._mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1179: in _mock_call
return self._execute_mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <Mock name='Github().get_repo().get_collaborator_permission' id='140630988819488'>
args = ('carol',), kwargs = {}, effect = Exception('API Error')
def _execute_mock_call(self, /, *args, **kwargs):
# separate from _increment_mock_call so that awaited functions are
# executed separately from their call, also AsyncMock overrides this method
effect = self.side_effect
if effect is not None:
if _is_exception(effect):
> raise effect
E Exception: API Error
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1234: Exception
During handling of the above exception, another exception occurred:
self = <tests.test_github_client.TestUserHasPermission object at 0x7fe7386eda70>
def test_user_has_permission_error(self):
"""Test permission check returns False on error."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_repo = Mock()
mock_repo.get_collaborator_permission.side_effect = Exception("API Error")
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
> result = client.user_has_permission(123456, "owner/repo", "carol", "maintain")
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_github_client.py:754:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.github_client.GitHubAppClient object at 0x7fe73422be90>
installation_id = 123456, repo_full_name = 'owner/repo', username = 'carol'
required_permission = 'maintain'
def user_has_permission(
self,
installation_id: int,
repo_full_name: str,
username: str,
required_permission: str,
) -> bool:
"""Check whether a user has required access to a repository.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name (owner/repo)
username: GitHub username to check
required_permission: Minimum required repository permission
Returns:
True if user meets or exceeds required permission, False otherwise
"""
start_time = time.time()
with create_span(
"github.get_collaborator_permission",
{
"github.repo": repo_full_name,
"github.username": username,
"github.required_permission": required_permission,
"github.installation_id": installation_id,
},
) as span:
try:
client = self._get_installation_client(installation_id)
repo = client.get_repo(repo_full_name)
permission = repo.get_collaborator_permission(username)
permission_order = ["none", "read", "triage", "write", "maintain", "admin"]
try:
permission_index = permission_order.index(permission)
required_index = permission_order.index(required_permission)
except ValueError:
permission_index = -1
required_index = 99
has_permission = permission_index >= required_index
duration = time.time() - start_time
github_api_request_duration_seconds.labels(operation="get_permission").observe(
duration
)
github_api_requests_total.labels(operation="get_permission", status="success").inc()
self._update_rate_limit_metrics(client, installation_id)
add_span_attributes(
span,
{
"github.permission": permission,
"github.has_permission": has_permission,
},
)
set_span_ok(span)
return has_permission
> except CosmicRayTestingException as e:
^^^^^^^^^^^^^^^^^^^^^^^^^
E NameError: name 'CosmicRayTestingException' is not defined
stampbot/github_client.py:740: NameError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestUserHasPermission::test_user_has_permission_error - NameError: name 'CosmicRayTestingException' is not defined
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 62 passed, 3 deselected in 0.87s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ExceptionReplacer, occurrence: 12
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -815,7 +815,7 @@
"team": team_slug,
},
)
- except GithubException as e:
+ except CosmicRayTestingException as e:
# Team not found or no access - skip silently
logger.debug(
"Could not check team %s membership: %s",........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ExceptionReplacer, occurrence: 13
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -848,7 +848,7 @@
return member_teams
- except Exception as e:
+ except CosmicRayTestingException as e:
duration = time.time() - start_time
github_api_request_duration_seconds.labels(operation="get_user_teams").observe(
duration....................................................................F
=================================== FAILURES ===================================
_______ TestGetUserTeamSlugs.test_get_user_team_slugs_general_exception ________
self = <stampbot.github_client.GitHubAppClient object at 0x7f4ef3d05c70>
installation_id = 123456, org_name = 'acme', username = 'alice'
allowed_teams = ['release-team']
def get_user_team_slugs(
self,
installation_id: int,
org_name: str,
username: str,
allowed_teams: list[str],
) -> list[str]:
"""Get team slugs the user is a member of from the allowed teams list.
Args:
installation_id: GitHub App installation ID
org_name: Organization name
username: GitHub username to check
allowed_teams: List of team slugs to check (can be "org/team" or just "team")
Returns:
List of team slugs the user is a member of
"""
start_time = time.time()
with create_span(
"github.get_user_team_slugs",
{
"github.org": org_name,
"github.username": username,
"github.installation_id": installation_id,
"github.teams_to_check": len(allowed_teams),
},
) as span:
member_teams: list[str] = []
try:
client = self._get_installation_client(installation_id)
> org = client.get_organization(org_name)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/github_client.py:796:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1175: in __call__
return self._mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1179: in _mock_call
return self._execute_mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <Mock name='Github().get_organization' id='139977076537536'>
args = ('acme',), kwargs = {}, effect = Exception('Network error')
def _execute_mock_call(self, /, *args, **kwargs):
# separate from _increment_mock_call so that awaited functions are
# executed separately from their call, also AsyncMock overrides this method
effect = self.side_effect
if effect is not None:
if _is_exception(effect):
> raise effect
E Exception: Network error
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1234: Exception
During handling of the above exception, another exception occurred:
self = <tests.test_github_client.TestGetUserTeamSlugs object at 0x7f4ef810c5f0>
def test_get_user_team_slugs_general_exception(self):
"""Test team membership check with general exception."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_github = Mock()
mock_github.get_organization.side_effect = Exception("Network error")
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
> result = client.get_user_team_slugs(123456, "acme", "alice", ["release-team"])
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_github_client.py:1010:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.github_client.GitHubAppClient object at 0x7f4ef3d05c70>
installation_id = 123456, org_name = 'acme', username = 'alice'
allowed_teams = ['release-team']
def get_user_team_slugs(
self,
installation_id: int,
org_name: str,
username: str,
allowed_teams: list[str],
) -> list[str]:
"""Get team slugs the user is a member of from the allowed teams list.
Args:
installation_id: GitHub App installation ID
org_name: Organization name
username: GitHub username to check
allowed_teams: List of team slugs to check (can be "org/team" or just "team")
Returns:
List of team slugs the user is a member of
"""
start_time = time.time()
with create_span(
"github.get_user_team_slugs",
{
"github.org": org_name,
"github.username": username,
"github.installation_id": installation_id,
"github.teams_to_check": len(allowed_teams),
},
) as span:
member_teams: list[str] = []
try:
client = self._get_installation_client(installation_id)
org = client.get_organization(org_name)
for team_ref in allowed_teams:
# Extract team slug (handle both "org/team" and "team" formats)
team_slug = team_ref.split("/")[-1] if "/" in team_ref else team_ref
try:
team = org.get_team_by_slug(team_slug)
# Check if user is a member
user = client.get_user(username)
if team.has_in_members(user): # type: ignore[arg-type]
member_teams.append(team_slug)
logger.debug(
"User %s is a member of team %s",
username,
team_slug,
extra={
"org": org_name,
"username": username,
"team": team_slug,
},
)
except GithubException as e:
# Team not found or no access - skip silently
logger.debug(
"Could not check team %s membership: %s",
team_slug,
e.data.get("message", str(e)) if hasattr(e, "data") else str(e),
extra={
"org": org_name,
"team": team_slug,
"username": username,
},
)
continue
duration = time.time() - start_time
github_api_request_duration_seconds.labels(operation="get_user_teams").observe(
duration
)
github_api_requests_total.labels(operation="get_user_teams", status="success").inc()
self._update_rate_limit_metrics(client, installation_id)
add_span_attributes(
span,
{
"github.member_teams": len(member_teams),
"github.teams_checked": len(allowed_teams),
},
)
set_span_ok(span)
return member_teams
> except CosmicRayTestingException as e:
^^^^^^^^^^^^^^^^^^^^^^^^^
E NameError: name 'CosmicRayTestingException' is not defined
stampbot/github_client.py:851: NameError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetUserTeamSlugs::test_get_user_team_slugs_general_exception - NameError: name 'CosmicRayTestingException' is not defined
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 68 passed, 3 deselected in 0.89s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/NumberReplacer, occurrence: 0
--- mutation diff --- --- astampbot/github_client.py +++ bstampbot/github_client.py @@ -21,7 +21,7 @@ from stampbot.telemetry import add_span_attributes, create_span, set_span_error, set_span_ok # GitHub API configuration -GITHUB_API_TIMEOUT = 30 # seconds +GITHUB_API_TIMEOUT = 31 # seconds GITHUB_API_RETRY_TOTAL = 3 GITHUB_API_RETRY_BACKOFF = 0.5 # exponential backoff factor
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/NumberReplacer, occurrence: 1
--- mutation diff --- --- astampbot/github_client.py +++ bstampbot/github_client.py @@ -21,7 +21,7 @@ from stampbot.telemetry import add_span_attributes, create_span, set_span_error, set_span_ok # GitHub API configuration -GITHUB_API_TIMEOUT = 30 # seconds +GITHUB_API_TIMEOUT = 29 # seconds GITHUB_API_RETRY_TOTAL = 3 GITHUB_API_RETRY_BACKOFF = 0.5 # exponential backoff factor
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.67s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/NumberReplacer, occurrence: 2
--- mutation diff --- --- astampbot/github_client.py +++ bstampbot/github_client.py @@ -22,7 +22,7 @@ # GitHub API configuration GITHUB_API_TIMEOUT = 30 # seconds -GITHUB_API_RETRY_TOTAL = 3 +GITHUB_API_RETRY_TOTAL = 4 GITHUB_API_RETRY_BACKOFF = 0.5 # exponential backoff factor logger = get_logger(__name__)
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/NumberReplacer, occurrence: 3
--- mutation diff --- --- astampbot/github_client.py +++ bstampbot/github_client.py @@ -22,7 +22,7 @@ # GitHub API configuration GITHUB_API_TIMEOUT = 30 # seconds -GITHUB_API_RETRY_TOTAL = 3 +GITHUB_API_RETRY_TOTAL = 2 GITHUB_API_RETRY_BACKOFF = 0.5 # exponential backoff factor logger = get_logger(__name__)
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/NumberReplacer, occurrence: 4
--- mutation diff --- --- astampbot/github_client.py +++ bstampbot/github_client.py @@ -23,7 +23,7 @@ # GitHub API configuration GITHUB_API_TIMEOUT = 30 # seconds GITHUB_API_RETRY_TOTAL = 3 -GITHUB_API_RETRY_BACKOFF = 0.5 # exponential backoff factor +GITHUB_API_RETRY_BACKOFF = 1.5 # exponential backoff factor logger = get_logger(__name__)
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.60s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/NumberReplacer, occurrence: 5
--- mutation diff --- --- astampbot/github_client.py +++ bstampbot/github_client.py @@ -23,7 +23,7 @@ # GitHub API configuration GITHUB_API_TIMEOUT = 30 # seconds GITHUB_API_RETRY_TOTAL = 3 -GITHUB_API_RETRY_BACKOFF = 0.5 # exponential backoff factor +GITHUB_API_RETRY_BACKOFF = -0.5 # exponential backoff factor logger = get_logger(__name__)
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/NumberReplacer, occurrence: 6
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -158,7 +158,7 @@
retry = Retry(
total=GITHUB_API_RETRY_TOTAL,
backoff_factor=GITHUB_API_RETRY_BACKOFF,
- status_forcelist=[500, 502, 503, 504],
+ status_forcelist=[ 501, 502, 503, 504],
allowed_methods=["GET", "POST", "PUT", "DELETE", "PATCH"],
)
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.63s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/NumberReplacer, occurrence: 7
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -158,7 +158,7 @@
retry = Retry(
total=GITHUB_API_RETRY_TOTAL,
backoff_factor=GITHUB_API_RETRY_BACKOFF,
- status_forcelist=[500, 502, 503, 504],
+ status_forcelist=[ 499, 502, 503, 504],
allowed_methods=["GET", "POST", "PUT", "DELETE", "PATCH"],
)
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.60s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/NumberReplacer, occurrence: 8
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -158,7 +158,7 @@
retry = Retry(
total=GITHUB_API_RETRY_TOTAL,
backoff_factor=GITHUB_API_RETRY_BACKOFF,
- status_forcelist=[500, 502, 503, 504],
+ status_forcelist=[500, 503, 503, 504],
allowed_methods=["GET", "POST", "PUT", "DELETE", "PATCH"],
)
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/NumberReplacer, occurrence: 9
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -158,7 +158,7 @@
retry = Retry(
total=GITHUB_API_RETRY_TOTAL,
backoff_factor=GITHUB_API_RETRY_BACKOFF,
- status_forcelist=[500, 502, 503, 504],
+ status_forcelist=[500, 501, 503, 504],
allowed_methods=["GET", "POST", "PUT", "DELETE", "PATCH"],
)
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/NumberReplacer, occurrence: 10
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -158,7 +158,7 @@
retry = Retry(
total=GITHUB_API_RETRY_TOTAL,
backoff_factor=GITHUB_API_RETRY_BACKOFF,
- status_forcelist=[500, 502, 503, 504],
+ status_forcelist=[500, 502, 504, 504],
allowed_methods=["GET", "POST", "PUT", "DELETE", "PATCH"],
)
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.65s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/NumberReplacer, occurrence: 11
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -158,7 +158,7 @@
retry = Retry(
total=GITHUB_API_RETRY_TOTAL,
backoff_factor=GITHUB_API_RETRY_BACKOFF,
- status_forcelist=[500, 502, 503, 504],
+ status_forcelist=[500, 502, 502, 504],
allowed_methods=["GET", "POST", "PUT", "DELETE", "PATCH"],
)
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.62s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/NumberReplacer, occurrence: 12
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -158,7 +158,7 @@
retry = Retry(
total=GITHUB_API_RETRY_TOTAL,
backoff_factor=GITHUB_API_RETRY_BACKOFF,
- status_forcelist=[500, 502, 503, 504],
+ status_forcelist=[500, 502, 503, 505],
allowed_methods=["GET", "POST", "PUT", "DELETE", "PATCH"],
)
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.62s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/NumberReplacer, occurrence: 13
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -158,7 +158,7 @@
retry = Retry(
total=GITHUB_API_RETRY_TOTAL,
backoff_factor=GITHUB_API_RETRY_BACKOFF,
- status_forcelist=[500, 502, 503, 504],
+ status_forcelist=[500, 502, 503, 503],
allowed_methods=["GET", "POST", "PUT", "DELETE", "PATCH"],
)
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.60s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/NumberReplacer, occurrence: 14
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -634,7 +634,7 @@
github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
github_api_requests_total.labels(operation="get_label", status="failure").inc()
- if e.status == 404:
+ if e.status == 405:
add_span_attributes(span, {"github.label_found": False})
set_span_ok(span)
return False......................................................................F
=================================== FAILURES ===================================
________________ TestRepoHasLabel.test_repo_has_label_not_found ________________
self = <tests.test_github_client.TestRepoHasLabel object at 0x7f71eb1d9590>
def test_repo_has_label_not_found(self):
"""Test repo_has_label returns False when label is missing."""
from github.GithubException import GithubException
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_repo = Mock()
mock_repo.get_label.side_effect = GithubException(404, {"message": "Not Found"}, None)
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
result = client.repo_has_label(123456, "owner/repo", "missing")
> assert result is False
E assert None is False
tests/test_github_client.py:1092: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-05-16 19:38:16 [debug ] Could not verify label missing in owner/repo: 404 {"message": "Not Found"} extra={'repo': 'owner/repo', 'label': 'missing', 'installation_id': 123456, 'error': '404 {"message": "Not Found"}'}
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestRepoHasLabel::test_repo_has_label_not_found - assert None is False
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 70 passed, 3 deselected in 0.77s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/NumberReplacer, occurrence: 15
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -634,7 +634,7 @@
github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
github_api_requests_total.labels(operation="get_label", status="failure").inc()
- if e.status == 404:
+ if e.status == 403:
add_span_attributes(span, {"github.label_found": False})
set_span_ok(span)
return False......................................................................F
=================================== FAILURES ===================================
________________ TestRepoHasLabel.test_repo_has_label_not_found ________________
self = <tests.test_github_client.TestRepoHasLabel object at 0x7f7cc8e55590>
def test_repo_has_label_not_found(self):
"""Test repo_has_label returns False when label is missing."""
from github.GithubException import GithubException
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_repo = Mock()
mock_repo.get_label.side_effect = GithubException(404, {"message": "Not Found"}, None)
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
result = client.repo_has_label(123456, "owner/repo", "missing")
> assert result is False
E assert None is False
tests/test_github_client.py:1092: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-05-16 19:31:45 [debug ] Could not verify label missing in owner/repo: 404 {"message": "Not Found"} extra={'repo': 'owner/repo', 'label': 'missing', 'installation_id': 123456, 'error': '404 {"message": "Not Found"}'}
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestRepoHasLabel::test_repo_has_label_not_found - assert None is False
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 70 passed, 3 deselected in 0.77s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/NumberReplacer, occurrence: 16
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -713,7 +713,7 @@
permission_index = permission_order.index(permission)
required_index = permission_order.index(required_permission)
except ValueError:
- permission_index = -1
+ permission_index = - 2
required_index = 99
has_permission = permission_index >= required_index........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.64s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/NumberReplacer, occurrence: 17
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -713,7 +713,7 @@
permission_index = permission_order.index(permission)
required_index = permission_order.index(required_permission)
except ValueError:
- permission_index = -1
+ permission_index = - 0
required_index = 99
has_permission = permission_index >= required_index........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/NumberReplacer, occurrence: 18
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -714,7 +714,7 @@
required_index = permission_order.index(required_permission)
except ValueError:
permission_index = -1
- required_index = 99
+ required_index = 100
has_permission = permission_index >= required_index
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.60s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/NumberReplacer, occurrence: 19
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -714,7 +714,7 @@
required_index = permission_order.index(required_permission)
except ValueError:
permission_index = -1
- required_index = 99
+ required_index = 98
has_permission = permission_index >= required_index
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.62s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/NumberReplacer, occurrence: 20
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -797,7 +797,7 @@
for team_ref in allowed_teams:
# Extract team slug (handle both "org/team" and "team" formats)
- team_slug = team_ref.split("/")[-1] if "/" in team_ref else team_ref
+ team_slug = team_ref.split("/")[- 2] if "/" in team_ref else team_ref
try:
team = org.get_team_by_slug(team_slug).................................................................F
=================================== FAILURES ===================================
________ TestGetUserTeamSlugs.test_get_user_team_slugs_with_org_prefix _________
self = <tests.test_github_client.TestGetUserTeamSlugs object at 0x7f7889479310>
def test_get_user_team_slugs_with_org_prefix(self):
"""Test team membership check with org/team format."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_user = Mock()
mock_team = Mock()
mock_team.has_in_members.return_value = True
mock_org = Mock()
mock_org.get_team_by_slug.return_value = mock_team
mock_github = Mock()
mock_github.get_organization.return_value = mock_org
mock_github.get_user.return_value = mock_user
mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
result = client.get_user_team_slugs(123456, "acme", "alice", ["acme/release-team"])
# Should extract "release-team" from "acme/release-team"
> mock_org.get_team_by_slug.assert_called_with("release-team")
tests/test_github_client.py:892:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <Mock name='Github().get_organization().get_team_by_slug' id='140155607949936'>
args = ('release-team',), kwargs = {}, expected = call('release-team')
actual = call('acme')
_error_message = <function NonCallableMock.assert_called_with.<locals>._error_message at 0x7f7885300bf0>
cause = None
def assert_called_with(self, /, *args, **kwargs):
"""assert that the last call was made with the specified arguments.
Raises an AssertionError if the args and keyword args passed in are
different to the last call to the mock."""
if self.call_args is None:
expected = self._format_mock_call_signature(args, kwargs)
actual = 'not called.'
error_message = ('expected call not found.\nExpected: %s\n Actual: %s'
% (expected, actual))
raise AssertionError(error_message)
def _error_message():
msg = self._format_mock_failure_message(args, kwargs)
return msg
expected = self._call_matcher(_Call((args, kwargs), two=True))
actual = self._call_matcher(self.call_args)
if actual != expected:
cause = expected if isinstance(expected, Exception) else None
> raise AssertionError(_error_message()) from cause
E AssertionError: expected call not found.
E Expected: get_team_by_slug('release-team')
E Actual: get_team_by_slug('acme')
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:985: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-05-16 19:38:20 [debug ] User alice is a member of team acme extra={'org': 'acme', 'username': 'alice', 'team': 'acme'}
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetUserTeamSlugs::test_get_user_team_slugs_with_org_prefix - AssertionError: expected call not found.
Expected: get_team_by_slug('release-team')
Actual: get_team_by_slug('acme')
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 65 passed, 3 deselected in 0.81s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/NumberReplacer, occurrence: 21
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -797,7 +797,7 @@
for team_ref in allowed_teams:
# Extract team slug (handle both "org/team" and "team" formats)
- team_slug = team_ref.split("/")[-1] if "/" in team_ref else team_ref
+ team_slug = team_ref.split("/")[- 0] if "/" in team_ref else team_ref
try:
team = org.get_team_by_slug(team_slug).................................................................F
=================================== FAILURES ===================================
________ TestGetUserTeamSlugs.test_get_user_team_slugs_with_org_prefix _________
self = <tests.test_github_client.TestGetUserTeamSlugs object at 0x7fe448db5310>
def test_get_user_team_slugs_with_org_prefix(self):
"""Test team membership check with org/team format."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
mock_user = Mock()
mock_team = Mock()
mock_team.has_in_members.return_value = True
mock_org = Mock()
mock_org.get_team_by_slug.return_value = mock_team
mock_github = Mock()
mock_github.get_organization.return_value = mock_org
mock_github.get_user.return_value = mock_user
mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
result = client.get_user_team_slugs(123456, "acme", "alice", ["acme/release-team"])
# Should extract "release-team" from "acme/release-team"
> mock_org.get_team_by_slug.assert_called_with("release-team")
tests/test_github_client.py:892:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <Mock name='Github().get_organization().get_team_by_slug' id='140618382172784'>
args = ('release-team',), kwargs = {}, expected = call('release-team')
actual = call('acme')
_error_message = <function NonCallableMock.assert_called_with.<locals>._error_message at 0x7fe444ce0bf0>
cause = None
def assert_called_with(self, /, *args, **kwargs):
"""assert that the last call was made with the specified arguments.
Raises an AssertionError if the args and keyword args passed in are
different to the last call to the mock."""
if self.call_args is None:
expected = self._format_mock_call_signature(args, kwargs)
actual = 'not called.'
error_message = ('expected call not found.\nExpected: %s\n Actual: %s'
% (expected, actual))
raise AssertionError(error_message)
def _error_message():
msg = self._format_mock_failure_message(args, kwargs)
return msg
expected = self._call_matcher(_Call((args, kwargs), two=True))
actual = self._call_matcher(self.call_args)
if actual != expected:
cause = expected if isinstance(expected, Exception) else None
> raise AssertionError(_error_message()) from cause
E AssertionError: expected call not found.
E Expected: get_team_by_slug('release-team')
E Actual: get_team_by_slug('acme')
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:985: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-05-16 19:24:21 [debug ] User alice is a member of team acme extra={'org': 'acme', 'username': 'alice', 'team': 'acme'}
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetUserTeamSlugs::test_get_user_team_slugs_with_org_prefix - AssertionError: expected call not found.
Expected: get_team_by_slug('release-team')
Actual: get_team_by_slug('acme')
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 65 passed, 3 deselected in 0.81s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/RemoveDecorator, occurrence: 0
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -74,8 +74,6 @@
self._auth = Auth.AppAuth(app_id, private_key)
self._integration = GithubIntegration(auth=self._auth)
self._initialized = True
-
- @property
def integration(self) -> GithubIntegration:
"""Get GitHub integration, initializing if needed.
........................................F
=================================== FAILURES ===================================
_____ TestIntegrationProperty.test_integration_raises_when_not_initialized _____
self = <tests.test_github_client.TestIntegrationProperty object at 0x7f74d1997d90>
def test_integration_raises_when_not_initialized(self):
"""Test that integration property raises when not initialized after _ensure_initialized."""
with patch("stampbot.github_client.is_configured", return_value=False):
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
> with pytest.raises(RuntimeError):
^^^^^^^^^^^^^^^^^^^^^^^^^^^
E Failed: DID NOT RAISE <class 'RuntimeError'>
tests/test_github_client.py:94: Failed
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestIntegrationProperty::test_integration_raises_when_not_initialized - Failed: DID NOT RAISE <class 'RuntimeError'>
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 40 passed, 3 deselected in 0.72s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ZeroIterationForLoop, occurrence: 0
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -469,7 +469,7 @@
# Find all reviews by bot that are approvals
bot_review_ids = []
- for review in pr.get_reviews():
+ for review in []:
if review.user.login == bot_user and review.state == "APPROVED":
bot_review_ids.append(review.id)
..........................................................F
=================================== FAILURES ===================================
_______________ TestFindBotReviews.test_find_bot_reviews_success _______________
self = <tests.test_github_client.TestFindBotReviews object at 0x7f2ff8dd8cd0>
def test_find_bot_reviews_success(self):
"""Test successful bot review finding."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration.get_app.return_value = Mock(slug="stampbot")
mock_integration_cls.return_value = mock_integration
# Create mock reviews
bot_review = Mock()
bot_review.user.login = "stampbot[bot]"
bot_review.state = "APPROVED"
bot_review.id = 123
other_review = Mock()
other_review.user.login = "other-user"
other_review.state = "APPROVED"
other_review.id = 456
mock_pr = Mock()
mock_pr.get_reviews.return_value = [bot_review, other_review]
mock_repo = Mock()
mock_repo.get_pull.return_value = mock_pr
mock_github = Mock()
mock_github.get_repo.return_value = mock_repo
mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
result = client.find_bot_reviews(123456, "owner/repo", 42)
> assert result == [123]
E assert [] == [123]
E
E Right contains one more item: 123
E
E Full diff:
E + []
E - [
E - 123,
E - ]
tests/test_github_client.py:608: AssertionError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestFindBotReviews::test_find_bot_reviews_success - assert [] == [123]
Right contains one more item: 123
Full diff:
+ []
- [
- 123,
- ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 58 passed, 3 deselected in 0.76s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ZeroIterationForLoop, occurrence: 1
--- mutation diff ---
--- astampbot/github_client.py
+++ bstampbot/github_client.py
@@ -795,7 +795,7 @@
client = self._get_installation_client(installation_id)
org = client.get_organization(org_name)
- for team_ref in allowed_teams:
+ for team_ref in []:
# Extract team slug (handle both "org/team" and "team" formats)
team_slug = team_ref.split("/")[-1] if "/" in team_ref else team_ref
................................................................F
=================================== FAILURES ===================================
____________ TestGetUserTeamSlugs.test_get_user_team_slugs_success _____________
self = <tests.test_github_client.TestGetUserTeamSlugs object at 0x7f73d28151d0>
def test_get_user_team_slugs_success(self):
"""Test successful team membership check."""
with (
patch("stampbot.github_client.is_configured", return_value=True),
patch("stampbot.github_client.settings") as mock_settings,
patch("stampbot.github_client.Auth.AppAuth"),
patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
patch("stampbot.github_client.Github") as mock_github_cls,
patch("stampbot.github_client.create_span") as mock_span,
):
mock_settings.app_id = 12345
mock_settings.private_key = TEST_PEM_KEY
mock_settings.otel_enabled = False
mock_integration = Mock()
mock_token = Mock()
mock_token.token = TEST_TOKEN
mock_integration.get_access_token.return_value = mock_token
mock_integration_cls.return_value = mock_integration
# Mock user
mock_user = Mock()
mock_user.login = "alice"
# Mock team that has alice as member
mock_team = Mock()
mock_team.has_in_members.return_value = True
# Mock org
mock_org = Mock()
mock_org.get_team_by_slug.return_value = mock_team
mock_github = Mock()
mock_github.get_organization.return_value = mock_org
mock_github.get_user.return_value = mock_user
mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
mock_github_cls.return_value = mock_github
mock_span.return_value.__enter__ = Mock(return_value=None)
mock_span.return_value.__exit__ = Mock(return_value=False)
from stampbot.github_client import GitHubAppClient
client = GitHubAppClient()
result = client.get_user_team_slugs(
123456, "acme", "alice", ["release-team", "deploy-team"]
)
> assert result == ["release-team", "deploy-team"]
E AssertionError: assert [] == ['release-tea...'deploy-team']
E
E Right contains 2 more items, first extra item: 'release-team'
E
E Full diff:
E + []
E - [
E - 'release-team',
E - 'deploy-team',
E - ]
tests/test_github_client.py:848: AssertionError
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetUserTeamSlugs::test_get_user_team_slugs_success - AssertionError: assert [] == ['release-tea...'deploy-team']
Right contains 2 more items, first extra item: 'release-team'
Full diff:
+ []
- [
- 'release-team',
- 'deploy-team',
- ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 64 passed, 3 deselected in 0.77s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Pow_Add, occurrence: 0
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -251,7 +251,7 @@
repo_settings = toml.loads(toml_content)
# Merge: repo settings override defaults
- merged = {**defaults, **repo_settings}
+ merged = {+defaults, **repo_settings}
import re
==================================== ERRORS ====================================
____________________ ERROR collecting tests/test_config.py _____________________
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/python.py:507: in importtestmodule
mod = import_path(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/pathlib.py:587: in import_path
importlib.import_module(module_name)
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/importlib/__init__.py:88: in import_module
return _bootstrap._gcd_import(name[level:], package, level)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
<frozen importlib._bootstrap>:1398: in _gcd_import
???
<frozen importlib._bootstrap>:1371: in _find_and_load
???
<frozen importlib._bootstrap>:1342: in _find_and_load_unlocked
???
<frozen importlib._bootstrap>:938: in _load_unlocked
???
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/assertion/rewrite.py:197: in exec_module
exec(co, module.__dict__)
tests/test_config.py:7: in <module>
from stampbot.config import RepoConfig, get_setting, is_configured
E File "/home/runner/work/stampbot/stampbot/stampbot/config.py", line 254
E merged = {+defaults, **repo_settings}
E ^^
E SyntaxError: invalid syntax
=========================== short test summary info ============================
ERROR tests/test_config.py
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 error in 0.14s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Pow_Add, occurrence: 1
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -251,7 +251,7 @@
repo_settings = toml.loads(toml_content)
# Merge: repo settings override defaults
- merged = {**defaults, **repo_settings}
+ merged = {**defaults, +repo_settings}
import re
==================================== ERRORS ====================================
____________________ ERROR collecting tests/test_config.py _____________________
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/python.py:507: in importtestmodule
mod = import_path(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/pathlib.py:587: in import_path
importlib.import_module(module_name)
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/importlib/__init__.py:88: in import_module
return _bootstrap._gcd_import(name[level:], package, level)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
<frozen importlib._bootstrap>:1398: in _gcd_import
???
<frozen importlib._bootstrap>:1371: in _find_and_load
???
<frozen importlib._bootstrap>:1342: in _find_and_load_unlocked
???
<frozen importlib._bootstrap>:938: in _load_unlocked
???
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/assertion/rewrite.py:197: in exec_module
exec(co, module.__dict__)
tests/test_config.py:7: in <module>
from stampbot.config import RepoConfig, get_setting, is_configured
E File "/home/runner/work/stampbot/stampbot/stampbot/config.py", line 254
E merged = {**defaults, +repo_settings}
E ^
E SyntaxError: ':' expected after dictionary key
=========================== short test summary info ============================
ERROR tests/test_config.py
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 error in 0.13s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Pow_Sub, occurrence: 0
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -251,7 +251,7 @@
repo_settings = toml.loads(toml_content)
# Merge: repo settings override defaults
- merged = {**defaults, **repo_settings}
+ merged = {-defaults, **repo_settings}
import re
==================================== ERRORS ====================================
____________________ ERROR collecting tests/test_config.py _____________________
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/python.py:507: in importtestmodule
mod = import_path(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/pathlib.py:587: in import_path
importlib.import_module(module_name)
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/importlib/__init__.py:88: in import_module
return _bootstrap._gcd_import(name[level:], package, level)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
<frozen importlib._bootstrap>:1398: in _gcd_import
???
<frozen importlib._bootstrap>:1371: in _find_and_load
???
<frozen importlib._bootstrap>:1342: in _find_and_load_unlocked
???
<frozen importlib._bootstrap>:938: in _load_unlocked
???
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/assertion/rewrite.py:197: in exec_module
exec(co, module.__dict__)
tests/test_config.py:7: in <module>
from stampbot.config import RepoConfig, get_setting, is_configured
E File "/home/runner/work/stampbot/stampbot/stampbot/config.py", line 254
E merged = {-defaults, **repo_settings}
E ^^
E SyntaxError: invalid syntax
=========================== short test summary info ============================
ERROR tests/test_config.py
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 error in 0.14s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Pow_Sub, occurrence: 1
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -251,7 +251,7 @@
repo_settings = toml.loads(toml_content)
# Merge: repo settings override defaults
- merged = {**defaults, **repo_settings}
+ merged = {**defaults, -repo_settings}
import re
==================================== ERRORS ====================================
____________________ ERROR collecting tests/test_config.py _____________________
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/python.py:507: in importtestmodule
mod = import_path(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/pathlib.py:587: in import_path
importlib.import_module(module_name)
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/importlib/__init__.py:88: in import_module
return _bootstrap._gcd_import(name[level:], package, level)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
<frozen importlib._bootstrap>:1398: in _gcd_import
???
<frozen importlib._bootstrap>:1371: in _find_and_load
???
<frozen importlib._bootstrap>:1342: in _find_and_load_unlocked
???
<frozen importlib._bootstrap>:938: in _load_unlocked
???
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/assertion/rewrite.py:197: in exec_module
exec(co, module.__dict__)
tests/test_config.py:7: in <module>
from stampbot.config import RepoConfig, get_setting, is_configured
E File "/home/runner/work/stampbot/stampbot/stampbot/config.py", line 254
E merged = {**defaults, -repo_settings}
E ^
E SyntaxError: ':' expected after dictionary key
=========================== short test summary info ============================
ERROR tests/test_config.py
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 error in 0.13s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Pow_Mul, occurrence: 0
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -251,7 +251,7 @@
repo_settings = toml.loads(toml_content)
# Merge: repo settings override defaults
- merged = {**defaults, **repo_settings}
+ merged = {*defaults, **repo_settings}
import re
==================================== ERRORS ====================================
____________________ ERROR collecting tests/test_config.py _____________________
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/python.py:507: in importtestmodule
mod = import_path(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/pathlib.py:587: in import_path
importlib.import_module(module_name)
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/importlib/__init__.py:88: in import_module
return _bootstrap._gcd_import(name[level:], package, level)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
<frozen importlib._bootstrap>:1398: in _gcd_import
???
<frozen importlib._bootstrap>:1371: in _find_and_load
???
<frozen importlib._bootstrap>:1342: in _find_and_load_unlocked
???
<frozen importlib._bootstrap>:938: in _load_unlocked
???
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/assertion/rewrite.py:197: in exec_module
exec(co, module.__dict__)
tests/test_config.py:7: in <module>
from stampbot.config import RepoConfig, get_setting, is_configured
E File "/home/runner/work/stampbot/stampbot/stampbot/config.py", line 254
E merged = {*defaults, **repo_settings}
E ^^
E SyntaxError: invalid syntax
=========================== short test summary info ============================
ERROR tests/test_config.py
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 error in 0.14s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Pow_Mul, occurrence: 1
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -251,7 +251,7 @@
repo_settings = toml.loads(toml_content)
# Merge: repo settings override defaults
- merged = {**defaults, **repo_settings}
+ merged = {**defaults, *repo_settings}
import re
==================================== ERRORS ====================================
____________________ ERROR collecting tests/test_config.py _____________________
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/python.py:507: in importtestmodule
mod = import_path(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/pathlib.py:587: in import_path
importlib.import_module(module_name)
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/importlib/__init__.py:88: in import_module
return _bootstrap._gcd_import(name[level:], package, level)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
<frozen importlib._bootstrap>:1398: in _gcd_import
???
<frozen importlib._bootstrap>:1371: in _find_and_load
???
<frozen importlib._bootstrap>:1342: in _find_and_load_unlocked
???
<frozen importlib._bootstrap>:938: in _load_unlocked
???
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/assertion/rewrite.py:197: in exec_module
exec(co, module.__dict__)
tests/test_config.py:7: in <module>
from stampbot.config import RepoConfig, get_setting, is_configured
E File "/home/runner/work/stampbot/stampbot/stampbot/config.py", line 254
E merged = {**defaults, *repo_settings}
E ^
E SyntaxError: invalid syntax
=========================== short test summary info ============================
ERROR tests/test_config.py
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 error in 0.14s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Pow_Div, occurrence: 0
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -251,7 +251,7 @@
repo_settings = toml.loads(toml_content)
# Merge: repo settings override defaults
- merged = {**defaults, **repo_settings}
+ merged = {/defaults, **repo_settings}
import re
==================================== ERRORS ====================================
____________________ ERROR collecting tests/test_config.py _____________________
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/python.py:507: in importtestmodule
mod = import_path(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/pathlib.py:587: in import_path
importlib.import_module(module_name)
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/importlib/__init__.py:88: in import_module
return _bootstrap._gcd_import(name[level:], package, level)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
<frozen importlib._bootstrap>:1398: in _gcd_import
???
<frozen importlib._bootstrap>:1371: in _find_and_load
???
<frozen importlib._bootstrap>:1342: in _find_and_load_unlocked
???
<frozen importlib._bootstrap>:938: in _load_unlocked
???
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/assertion/rewrite.py:197: in exec_module
exec(co, module.__dict__)
tests/test_config.py:7: in <module>
from stampbot.config import RepoConfig, get_setting, is_configured
E File "/home/runner/work/stampbot/stampbot/stampbot/config.py", line 254
E merged = {/defaults, **repo_settings}
E ^
E SyntaxError: invalid syntax
=========================== short test summary info ============================
ERROR tests/test_config.py
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 error in 0.14s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Pow_Div, occurrence: 1
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -251,7 +251,7 @@
repo_settings = toml.loads(toml_content)
# Merge: repo settings override defaults
- merged = {**defaults, **repo_settings}
+ merged = {**defaults, /repo_settings}
import re
==================================== ERRORS ====================================
____________________ ERROR collecting tests/test_config.py _____________________
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/python.py:507: in importtestmodule
mod = import_path(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/pathlib.py:587: in import_path
importlib.import_module(module_name)
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/importlib/__init__.py:88: in import_module
return _bootstrap._gcd_import(name[level:], package, level)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
<frozen importlib._bootstrap>:1398: in _gcd_import
???
<frozen importlib._bootstrap>:1371: in _find_and_load
???
<frozen importlib._bootstrap>:1342: in _find_and_load_unlocked
???
<frozen importlib._bootstrap>:938: in _load_unlocked
???
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/assertion/rewrite.py:197: in exec_module
exec(co, module.__dict__)
tests/test_config.py:7: in <module>
from stampbot.config import RepoConfig, get_setting, is_configured
E File "/home/runner/work/stampbot/stampbot/stampbot/config.py", line 254
E merged = {**defaults, /repo_settings}
E ^
E SyntaxError: invalid syntax
=========================== short test summary info ============================
ERROR tests/test_config.py
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 error in 0.14s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Pow_FloorDiv, occurrence: 0
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -251,7 +251,7 @@
repo_settings = toml.loads(toml_content)
# Merge: repo settings override defaults
- merged = {**defaults, **repo_settings}
+ merged = {//defaults, **repo_settings}
import re
==================================== ERRORS ====================================
____________________ ERROR collecting tests/test_config.py _____________________
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/python.py:507: in importtestmodule
mod = import_path(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/pathlib.py:587: in import_path
importlib.import_module(module_name)
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/importlib/__init__.py:88: in import_module
return _bootstrap._gcd_import(name[level:], package, level)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
<frozen importlib._bootstrap>:1398: in _gcd_import
???
<frozen importlib._bootstrap>:1371: in _find_and_load
???
<frozen importlib._bootstrap>:1342: in _find_and_load_unlocked
???
<frozen importlib._bootstrap>:938: in _load_unlocked
???
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/assertion/rewrite.py:197: in exec_module
exec(co, module.__dict__)
tests/test_config.py:7: in <module>
from stampbot.config import RepoConfig, get_setting, is_configured
E File "/home/runner/work/stampbot/stampbot/stampbot/config.py", line 254
E merged = {//defaults, **repo_settings}
E ^^
E SyntaxError: invalid syntax
=========================== short test summary info ============================
ERROR tests/test_config.py
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 error in 0.14s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Pow_FloorDiv, occurrence: 1
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -251,7 +251,7 @@
repo_settings = toml.loads(toml_content)
# Merge: repo settings override defaults
- merged = {**defaults, **repo_settings}
+ merged = {**defaults, //repo_settings}
import re
==================================== ERRORS ====================================
____________________ ERROR collecting tests/test_config.py _____________________
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/python.py:507: in importtestmodule
mod = import_path(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/pathlib.py:587: in import_path
importlib.import_module(module_name)
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/importlib/__init__.py:88: in import_module
return _bootstrap._gcd_import(name[level:], package, level)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
<frozen importlib._bootstrap>:1398: in _gcd_import
???
<frozen importlib._bootstrap>:1371: in _find_and_load
???
<frozen importlib._bootstrap>:1342: in _find_and_load_unlocked
???
<frozen importlib._bootstrap>:938: in _load_unlocked
???
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/assertion/rewrite.py:197: in exec_module
exec(co, module.__dict__)
tests/test_config.py:7: in <module>
from stampbot.config import RepoConfig, get_setting, is_configured
E File "/home/runner/work/stampbot/stampbot/stampbot/config.py", line 254
E merged = {**defaults, //repo_settings}
E ^^
E SyntaxError: invalid syntax
=========================== short test summary info ============================
ERROR tests/test_config.py
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 error in 0.14s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Pow_Mod, occurrence: 0
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -251,7 +251,7 @@
repo_settings = toml.loads(toml_content)
# Merge: repo settings override defaults
- merged = {**defaults, **repo_settings}
+ merged = {%defaults, **repo_settings}
import re
==================================== ERRORS ====================================
____________________ ERROR collecting tests/test_config.py _____________________
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/python.py:507: in importtestmodule
mod = import_path(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/pathlib.py:587: in import_path
importlib.import_module(module_name)
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/importlib/__init__.py:88: in import_module
return _bootstrap._gcd_import(name[level:], package, level)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
<frozen importlib._bootstrap>:1398: in _gcd_import
???
<frozen importlib._bootstrap>:1371: in _find_and_load
???
<frozen importlib._bootstrap>:1342: in _find_and_load_unlocked
???
<frozen importlib._bootstrap>:938: in _load_unlocked
???
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/assertion/rewrite.py:197: in exec_module
exec(co, module.__dict__)
tests/test_config.py:7: in <module>
from stampbot.config import RepoConfig, get_setting, is_configured
E File "/home/runner/work/stampbot/stampbot/stampbot/config.py", line 254
E merged = {%defaults, **repo_settings}
E ^
E SyntaxError: invalid syntax
=========================== short test summary info ============================
ERROR tests/test_config.py
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 error in 0.14s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Pow_Mod, occurrence: 1
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -251,7 +251,7 @@
repo_settings = toml.loads(toml_content)
# Merge: repo settings override defaults
- merged = {**defaults, **repo_settings}
+ merged = {**defaults, %repo_settings}
import re
==================================== ERRORS ====================================
____________________ ERROR collecting tests/test_config.py _____________________
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/python.py:507: in importtestmodule
mod = import_path(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/pathlib.py:587: in import_path
importlib.import_module(module_name)
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/importlib/__init__.py:88: in import_module
return _bootstrap._gcd_import(name[level:], package, level)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
<frozen importlib._bootstrap>:1398: in _gcd_import
???
<frozen importlib._bootstrap>:1371: in _find_and_load
???
<frozen importlib._bootstrap>:1342: in _find_and_load_unlocked
???
<frozen importlib._bootstrap>:938: in _load_unlocked
???
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/assertion/rewrite.py:197: in exec_module
exec(co, module.__dict__)
tests/test_config.py:7: in <module>
from stampbot.config import RepoConfig, get_setting, is_configured
E File "/home/runner/work/stampbot/stampbot/stampbot/config.py", line 254
E merged = {**defaults, %repo_settings}
E ^
E SyntaxError: invalid syntax
=========================== short test summary info ============================
ERROR tests/test_config.py
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 error in 0.13s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Pow_RShift, occurrence: 0
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -251,7 +251,7 @@
repo_settings = toml.loads(toml_content)
# Merge: repo settings override defaults
- merged = {**defaults, **repo_settings}
+ merged = {>>defaults, **repo_settings}
import re
==================================== ERRORS ====================================
____________________ ERROR collecting tests/test_config.py _____________________
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/python.py:507: in importtestmodule
mod = import_path(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/pathlib.py:587: in import_path
importlib.import_module(module_name)
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/importlib/__init__.py:88: in import_module
return _bootstrap._gcd_import(name[level:], package, level)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
<frozen importlib._bootstrap>:1398: in _gcd_import
???
<frozen importlib._bootstrap>:1371: in _find_and_load
???
<frozen importlib._bootstrap>:1342: in _find_and_load_unlocked
???
<frozen importlib._bootstrap>:938: in _load_unlocked
???
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/assertion/rewrite.py:197: in exec_module
exec(co, module.__dict__)
tests/test_config.py:7: in <module>
from stampbot.config import RepoConfig, get_setting, is_configured
E File "/home/runner/work/stampbot/stampbot/stampbot/config.py", line 254
E merged = {>>defaults, **repo_settings}
E ^^
E SyntaxError: invalid syntax
=========================== short test summary info ============================
ERROR tests/test_config.py
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 error in 0.14s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Pow_RShift, occurrence: 1
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -251,7 +251,7 @@
repo_settings = toml.loads(toml_content)
# Merge: repo settings override defaults
- merged = {**defaults, **repo_settings}
+ merged = {**defaults, >>repo_settings}
import re
==================================== ERRORS ====================================
____________________ ERROR collecting tests/test_config.py _____________________
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/python.py:507: in importtestmodule
mod = import_path(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/pathlib.py:587: in import_path
importlib.import_module(module_name)
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/importlib/__init__.py:88: in import_module
return _bootstrap._gcd_import(name[level:], package, level)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
<frozen importlib._bootstrap>:1398: in _gcd_import
???
<frozen importlib._bootstrap>:1371: in _find_and_load
???
<frozen importlib._bootstrap>:1342: in _find_and_load_unlocked
???
<frozen importlib._bootstrap>:938: in _load_unlocked
???
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/assertion/rewrite.py:197: in exec_module
exec(co, module.__dict__)
tests/test_config.py:7: in <module>
from stampbot.config import RepoConfig, get_setting, is_configured
E File "/home/runner/work/stampbot/stampbot/stampbot/config.py", line 254
E merged = {**defaults, >>repo_settings}
E ^^
E SyntaxError: invalid syntax
=========================== short test summary info ============================
ERROR tests/test_config.py
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 error in 0.14s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Pow_LShift, occurrence: 0
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -251,7 +251,7 @@
repo_settings = toml.loads(toml_content)
# Merge: repo settings override defaults
- merged = {**defaults, **repo_settings}
+ merged = {<<defaults, **repo_settings}
import re
==================================== ERRORS ====================================
____________________ ERROR collecting tests/test_config.py _____________________
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/python.py:507: in importtestmodule
mod = import_path(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/pathlib.py:587: in import_path
importlib.import_module(module_name)
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/importlib/__init__.py:88: in import_module
return _bootstrap._gcd_import(name[level:], package, level)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
<frozen importlib._bootstrap>:1398: in _gcd_import
???
<frozen importlib._bootstrap>:1371: in _find_and_load
???
<frozen importlib._bootstrap>:1342: in _find_and_load_unlocked
???
<frozen importlib._bootstrap>:938: in _load_unlocked
???
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/assertion/rewrite.py:197: in exec_module
exec(co, module.__dict__)
tests/test_config.py:7: in <module>
from stampbot.config import RepoConfig, get_setting, is_configured
E File "/home/runner/work/stampbot/stampbot/stampbot/config.py", line 254
E merged = {<<defaults, **repo_settings}
E ^^
E SyntaxError: invalid syntax
=========================== short test summary info ============================
ERROR tests/test_config.py
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 error in 0.14s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Pow_LShift, occurrence: 1
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -251,7 +251,7 @@
repo_settings = toml.loads(toml_content)
# Merge: repo settings override defaults
- merged = {**defaults, **repo_settings}
+ merged = {**defaults, <<repo_settings}
import re
==================================== ERRORS ====================================
____________________ ERROR collecting tests/test_config.py _____________________
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/python.py:507: in importtestmodule
mod = import_path(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/pathlib.py:587: in import_path
importlib.import_module(module_name)
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/importlib/__init__.py:88: in import_module
return _bootstrap._gcd_import(name[level:], package, level)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
<frozen importlib._bootstrap>:1398: in _gcd_import
???
<frozen importlib._bootstrap>:1371: in _find_and_load
???
<frozen importlib._bootstrap>:1342: in _find_and_load_unlocked
???
<frozen importlib._bootstrap>:938: in _load_unlocked
???
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/assertion/rewrite.py:197: in exec_module
exec(co, module.__dict__)
tests/test_config.py:7: in <module>
from stampbot.config import RepoConfig, get_setting, is_configured
E File "/home/runner/work/stampbot/stampbot/stampbot/config.py", line 254
E merged = {**defaults, <<repo_settings}
E ^^
E SyntaxError: invalid syntax
=========================== short test summary info ============================
ERROR tests/test_config.py
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 error in 0.14s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Pow_BitOr, occurrence: 0
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -251,7 +251,7 @@
repo_settings = toml.loads(toml_content)
# Merge: repo settings override defaults
- merged = {**defaults, **repo_settings}
+ merged = {|defaults, **repo_settings}
import re
==================================== ERRORS ====================================
____________________ ERROR collecting tests/test_config.py _____________________
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/python.py:507: in importtestmodule
mod = import_path(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/pathlib.py:587: in import_path
importlib.import_module(module_name)
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/importlib/__init__.py:88: in import_module
return _bootstrap._gcd_import(name[level:], package, level)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
<frozen importlib._bootstrap>:1398: in _gcd_import
???
<frozen importlib._bootstrap>:1371: in _find_and_load
???
<frozen importlib._bootstrap>:1342: in _find_and_load_unlocked
???
<frozen importlib._bootstrap>:938: in _load_unlocked
???
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/assertion/rewrite.py:197: in exec_module
exec(co, module.__dict__)
tests/test_config.py:7: in <module>
from stampbot.config import RepoConfig, get_setting, is_configured
E File "/home/runner/work/stampbot/stampbot/stampbot/config.py", line 254
E merged = {|defaults, **repo_settings}
E ^
E SyntaxError: invalid syntax
=========================== short test summary info ============================
ERROR tests/test_config.py
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 error in 0.14s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Pow_BitOr, occurrence: 1
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -251,7 +251,7 @@
repo_settings = toml.loads(toml_content)
# Merge: repo settings override defaults
- merged = {**defaults, **repo_settings}
+ merged = {**defaults, |repo_settings}
import re
==================================== ERRORS ====================================
____________________ ERROR collecting tests/test_config.py _____________________
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/python.py:507: in importtestmodule
mod = import_path(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/pathlib.py:587: in import_path
importlib.import_module(module_name)
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/importlib/__init__.py:88: in import_module
return _bootstrap._gcd_import(name[level:], package, level)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
<frozen importlib._bootstrap>:1398: in _gcd_import
???
<frozen importlib._bootstrap>:1371: in _find_and_load
???
<frozen importlib._bootstrap>:1342: in _find_and_load_unlocked
???
<frozen importlib._bootstrap>:938: in _load_unlocked
???
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/assertion/rewrite.py:197: in exec_module
exec(co, module.__dict__)
tests/test_config.py:7: in <module>
from stampbot.config import RepoConfig, get_setting, is_configured
E File "/home/runner/work/stampbot/stampbot/stampbot/config.py", line 254
E merged = {**defaults, |repo_settings}
E ^
E SyntaxError: invalid syntax
=========================== short test summary info ============================
ERROR tests/test_config.py
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 error in 0.14s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Pow_BitAnd, occurrence: 0
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -251,7 +251,7 @@
repo_settings = toml.loads(toml_content)
# Merge: repo settings override defaults
- merged = {**defaults, **repo_settings}
+ merged = {&defaults, **repo_settings}
import re
==================================== ERRORS ====================================
____________________ ERROR collecting tests/test_config.py _____________________
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/python.py:507: in importtestmodule
mod = import_path(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/pathlib.py:587: in import_path
importlib.import_module(module_name)
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/importlib/__init__.py:88: in import_module
return _bootstrap._gcd_import(name[level:], package, level)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
<frozen importlib._bootstrap>:1398: in _gcd_import
???
<frozen importlib._bootstrap>:1371: in _find_and_load
???
<frozen importlib._bootstrap>:1342: in _find_and_load_unlocked
???
<frozen importlib._bootstrap>:938: in _load_unlocked
???
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/assertion/rewrite.py:197: in exec_module
exec(co, module.__dict__)
tests/test_config.py:7: in <module>
from stampbot.config import RepoConfig, get_setting, is_configured
E File "/home/runner/work/stampbot/stampbot/stampbot/config.py", line 254
E merged = {&defaults, **repo_settings}
E ^
E SyntaxError: invalid syntax
=========================== short test summary info ============================
ERROR tests/test_config.py
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 error in 0.14s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Pow_BitAnd, occurrence: 1
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -251,7 +251,7 @@
repo_settings = toml.loads(toml_content)
# Merge: repo settings override defaults
- merged = {**defaults, **repo_settings}
+ merged = {**defaults, &repo_settings}
import re
==================================== ERRORS ====================================
____________________ ERROR collecting tests/test_config.py _____________________
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/python.py:507: in importtestmodule
mod = import_path(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/pathlib.py:587: in import_path
importlib.import_module(module_name)
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/importlib/__init__.py:88: in import_module
return _bootstrap._gcd_import(name[level:], package, level)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
<frozen importlib._bootstrap>:1398: in _gcd_import
???
<frozen importlib._bootstrap>:1371: in _find_and_load
???
<frozen importlib._bootstrap>:1342: in _find_and_load_unlocked
???
<frozen importlib._bootstrap>:938: in _load_unlocked
???
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/assertion/rewrite.py:197: in exec_module
exec(co, module.__dict__)
tests/test_config.py:7: in <module>
from stampbot.config import RepoConfig, get_setting, is_configured
E File "/home/runner/work/stampbot/stampbot/stampbot/config.py", line 254
E merged = {**defaults, &repo_settings}
E ^
E SyntaxError: invalid syntax
=========================== short test summary info ============================
ERROR tests/test_config.py
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 error in 0.14s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Pow_BitXor, occurrence: 0
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -251,7 +251,7 @@
repo_settings = toml.loads(toml_content)
# Merge: repo settings override defaults
- merged = {**defaults, **repo_settings}
+ merged = {^defaults, **repo_settings}
import re
==================================== ERRORS ====================================
____________________ ERROR collecting tests/test_config.py _____________________
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/python.py:507: in importtestmodule
mod = import_path(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/pathlib.py:587: in import_path
importlib.import_module(module_name)
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/importlib/__init__.py:88: in import_module
return _bootstrap._gcd_import(name[level:], package, level)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
<frozen importlib._bootstrap>:1398: in _gcd_import
???
<frozen importlib._bootstrap>:1371: in _find_and_load
???
<frozen importlib._bootstrap>:1342: in _find_and_load_unlocked
???
<frozen importlib._bootstrap>:938: in _load_unlocked
???
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/assertion/rewrite.py:197: in exec_module
exec(co, module.__dict__)
tests/test_config.py:7: in <module>
from stampbot.config import RepoConfig, get_setting, is_configured
E File "/home/runner/work/stampbot/stampbot/stampbot/config.py", line 254
E merged = {^defaults, **repo_settings}
E ^
E SyntaxError: invalid syntax
=========================== short test summary info ============================
ERROR tests/test_config.py
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 error in 0.14s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Pow_BitXor, occurrence: 1
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -251,7 +251,7 @@
repo_settings = toml.loads(toml_content)
# Merge: repo settings override defaults
- merged = {**defaults, **repo_settings}
+ merged = {**defaults, ^repo_settings}
import re
==================================== ERRORS ====================================
____________________ ERROR collecting tests/test_config.py _____________________
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/python.py:507: in importtestmodule
mod = import_path(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/pathlib.py:587: in import_path
importlib.import_module(module_name)
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/importlib/__init__.py:88: in import_module
return _bootstrap._gcd_import(name[level:], package, level)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
<frozen importlib._bootstrap>:1398: in _gcd_import
???
<frozen importlib._bootstrap>:1371: in _find_and_load
???
<frozen importlib._bootstrap>:1342: in _find_and_load_unlocked
???
<frozen importlib._bootstrap>:938: in _load_unlocked
???
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/assertion/rewrite.py:197: in exec_module
exec(co, module.__dict__)
tests/test_config.py:7: in <module>
from stampbot.config import RepoConfig, get_setting, is_configured
E File "/home/runner/work/stampbot/stampbot/stampbot/config.py", line 254
E merged = {**defaults, ^repo_settings}
E ^
E SyntaxError: invalid syntax
=========================== short test summary info ============================
ERROR tests/test_config.py
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 error in 0.14s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_Add, occurrence: 0
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -86,7 +86,7 @@
chatops_required_permission: str,
approve_commands: list[str],
unapprove_commands: list[str],
- required_labels: list[str] | None = None,
+ required_labels: list[str] + None = None,
required_title_patterns: list[str] | None = None,
allowed_users: list[str] | None = None,
allowed_teams: list[str] | None = None,........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.62s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_Add, occurrence: 1
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -87,7 +87,7 @@
approve_commands: list[str],
unapprove_commands: list[str],
required_labels: list[str] | None = None,
- required_title_patterns: list[str] | None = None,
+ required_title_patterns: list[str] + None = None,
allowed_users: list[str] | None = None,
allowed_teams: list[str] | None = None,
config_error: str | None = None,........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.62s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_Add, occurrence: 2
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -88,7 +88,7 @@
unapprove_commands: list[str],
required_labels: list[str] | None = None,
required_title_patterns: list[str] | None = None,
- allowed_users: list[str] | None = None,
+ allowed_users: list[str] + None = None,
allowed_teams: list[str] | None = None,
config_error: str | None = None,
):........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.60s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_Add, occurrence: 3
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -89,7 +89,7 @@
required_labels: list[str] | None = None,
required_title_patterns: list[str] | None = None,
allowed_users: list[str] | None = None,
- allowed_teams: list[str] | None = None,
+ allowed_teams: list[str] + None = None,
config_error: str | None = None,
):
"""Initialize repo configuration......................................................................... [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.63s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_Add, occurrence: 4
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -90,7 +90,7 @@
required_title_patterns: list[str] | None = None,
allowed_users: list[str] | None = None,
allowed_teams: list[str] | None = None,
- config_error: str | None = None,
+ config_error: str + None = None,
):
"""Initialize repo configuration.
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.60s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_Add, occurrence: 5
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -136,7 +136,7 @@
pr_labels: list[str],
pr_title: str,
pr_author: str,
- author_team_slugs: list[str] | None = None,
+ author_team_slugs: list[str] + None = None,
) -> tuple[bool, str | None]:
"""Check if a PR is eligible for auto-approval based on filters.
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_Add, occurrence: 6
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -137,7 +137,7 @@
pr_title: str,
pr_author: str,
author_team_slugs: list[str] | None = None,
- ) -> tuple[bool, str | None]:
+ ) -> tuple[bool, str + None]:
"""Check if a PR is eligible for auto-approval based on filters.
All configured filters must pass (AND logic between filter types)......................................................................... [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.66s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_Sub, occurrence: 0
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -86,7 +86,7 @@
chatops_required_permission: str,
approve_commands: list[str],
unapprove_commands: list[str],
- required_labels: list[str] | None = None,
+ required_labels: list[str] - None = None,
required_title_patterns: list[str] | None = None,
allowed_users: list[str] | None = None,
allowed_teams: list[str] | None = None,........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.69s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_Sub, occurrence: 1
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -87,7 +87,7 @@
approve_commands: list[str],
unapprove_commands: list[str],
required_labels: list[str] | None = None,
- required_title_patterns: list[str] | None = None,
+ required_title_patterns: list[str] - None = None,
allowed_users: list[str] | None = None,
allowed_teams: list[str] | None = None,
config_error: str | None = None,........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.63s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_Sub, occurrence: 2
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -88,7 +88,7 @@
unapprove_commands: list[str],
required_labels: list[str] | None = None,
required_title_patterns: list[str] | None = None,
- allowed_users: list[str] | None = None,
+ allowed_users: list[str] - None = None,
allowed_teams: list[str] | None = None,
config_error: str | None = None,
):........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.60s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_Sub, occurrence: 3
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -89,7 +89,7 @@
required_labels: list[str] | None = None,
required_title_patterns: list[str] | None = None,
allowed_users: list[str] | None = None,
- allowed_teams: list[str] | None = None,
+ allowed_teams: list[str] - None = None,
config_error: str | None = None,
):
"""Initialize repo configuration......................................................................... [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_Sub, occurrence: 4
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -90,7 +90,7 @@
required_title_patterns: list[str] | None = None,
allowed_users: list[str] | None = None,
allowed_teams: list[str] | None = None,
- config_error: str | None = None,
+ config_error: str - None = None,
):
"""Initialize repo configuration.
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.60s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_Sub, occurrence: 5
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -136,7 +136,7 @@
pr_labels: list[str],
pr_title: str,
pr_author: str,
- author_team_slugs: list[str] | None = None,
+ author_team_slugs: list[str] - None = None,
) -> tuple[bool, str | None]:
"""Check if a PR is eligible for auto-approval based on filters.
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_Sub, occurrence: 6
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -137,7 +137,7 @@
pr_title: str,
pr_author: str,
author_team_slugs: list[str] | None = None,
- ) -> tuple[bool, str | None]:
+ ) -> tuple[bool, str - None]:
"""Check if a PR is eligible for auto-approval based on filters.
All configured filters must pass (AND logic between filter types)......................................................................... [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_Mul, occurrence: 0
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -86,7 +86,7 @@
chatops_required_permission: str,
approve_commands: list[str],
unapprove_commands: list[str],
- required_labels: list[str] | None = None,
+ required_labels: list[str] * None = None,
required_title_patterns: list[str] | None = None,
allowed_users: list[str] | None = None,
allowed_teams: list[str] | None = None,........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.63s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_Mul, occurrence: 1
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -87,7 +87,7 @@
approve_commands: list[str],
unapprove_commands: list[str],
required_labels: list[str] | None = None,
- required_title_patterns: list[str] | None = None,
+ required_title_patterns: list[str] * None = None,
allowed_users: list[str] | None = None,
allowed_teams: list[str] | None = None,
config_error: str | None = None,........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.66s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_Mul, occurrence: 2
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -88,7 +88,7 @@
unapprove_commands: list[str],
required_labels: list[str] | None = None,
required_title_patterns: list[str] | None = None,
- allowed_users: list[str] | None = None,
+ allowed_users: list[str] * None = None,
allowed_teams: list[str] | None = None,
config_error: str | None = None,
):........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.62s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_Mul, occurrence: 3
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -89,7 +89,7 @@
required_labels: list[str] | None = None,
required_title_patterns: list[str] | None = None,
allowed_users: list[str] | None = None,
- allowed_teams: list[str] | None = None,
+ allowed_teams: list[str] * None = None,
config_error: str | None = None,
):
"""Initialize repo configuration......................................................................... [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_Mul, occurrence: 4
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -90,7 +90,7 @@
required_title_patterns: list[str] | None = None,
allowed_users: list[str] | None = None,
allowed_teams: list[str] | None = None,
- config_error: str | None = None,
+ config_error: str * None = None,
):
"""Initialize repo configuration.
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_Mul, occurrence: 5
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -136,7 +136,7 @@
pr_labels: list[str],
pr_title: str,
pr_author: str,
- author_team_slugs: list[str] | None = None,
+ author_team_slugs: list[str] * None = None,
) -> tuple[bool, str | None]:
"""Check if a PR is eligible for auto-approval based on filters.
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.60s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_Mul, occurrence: 6
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -137,7 +137,7 @@
pr_title: str,
pr_author: str,
author_team_slugs: list[str] | None = None,
- ) -> tuple[bool, str | None]:
+ ) -> tuple[bool, str * None]:
"""Check if a PR is eligible for auto-approval based on filters.
All configured filters must pass (AND logic between filter types)......................................................................... [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_Div, occurrence: 0
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -86,7 +86,7 @@
chatops_required_permission: str,
approve_commands: list[str],
unapprove_commands: list[str],
- required_labels: list[str] | None = None,
+ required_labels: list[str] / None = None,
required_title_patterns: list[str] | None = None,
allowed_users: list[str] | None = None,
allowed_teams: list[str] | None = None,........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.63s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_Div, occurrence: 1
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -87,7 +87,7 @@
approve_commands: list[str],
unapprove_commands: list[str],
required_labels: list[str] | None = None,
- required_title_patterns: list[str] | None = None,
+ required_title_patterns: list[str] / None = None,
allowed_users: list[str] | None = None,
allowed_teams: list[str] | None = None,
config_error: str | None = None,........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.64s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_Div, occurrence: 2
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -88,7 +88,7 @@
unapprove_commands: list[str],
required_labels: list[str] | None = None,
required_title_patterns: list[str] | None = None,
- allowed_users: list[str] | None = None,
+ allowed_users: list[str] / None = None,
allowed_teams: list[str] | None = None,
config_error: str | None = None,
):........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.62s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_Div, occurrence: 3
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -89,7 +89,7 @@
required_labels: list[str] | None = None,
required_title_patterns: list[str] | None = None,
allowed_users: list[str] | None = None,
- allowed_teams: list[str] | None = None,
+ allowed_teams: list[str] / None = None,
config_error: str | None = None,
):
"""Initialize repo configuration......................................................................... [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.67s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_Div, occurrence: 4
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -90,7 +90,7 @@
required_title_patterns: list[str] | None = None,
allowed_users: list[str] | None = None,
allowed_teams: list[str] | None = None,
- config_error: str | None = None,
+ config_error: str / None = None,
):
"""Initialize repo configuration.
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_Div, occurrence: 5
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -136,7 +136,7 @@
pr_labels: list[str],
pr_title: str,
pr_author: str,
- author_team_slugs: list[str] | None = None,
+ author_team_slugs: list[str] / None = None,
) -> tuple[bool, str | None]:
"""Check if a PR is eligible for auto-approval based on filters.
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.65s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_Div, occurrence: 6
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -137,7 +137,7 @@
pr_title: str,
pr_author: str,
author_team_slugs: list[str] | None = None,
- ) -> tuple[bool, str | None]:
+ ) -> tuple[bool, str / None]:
"""Check if a PR is eligible for auto-approval based on filters.
All configured filters must pass (AND logic between filter types)......................................................................... [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.60s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_FloorDiv, occurrence: 0
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -86,7 +86,7 @@
chatops_required_permission: str,
approve_commands: list[str],
unapprove_commands: list[str],
- required_labels: list[str] | None = None,
+ required_labels: list[str] // None = None,
required_title_patterns: list[str] | None = None,
allowed_users: list[str] | None = None,
allowed_teams: list[str] | None = None,........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.60s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_FloorDiv, occurrence: 1
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -87,7 +87,7 @@
approve_commands: list[str],
unapprove_commands: list[str],
required_labels: list[str] | None = None,
- required_title_patterns: list[str] | None = None,
+ required_title_patterns: list[str] // None = None,
allowed_users: list[str] | None = None,
allowed_teams: list[str] | None = None,
config_error: str | None = None,........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.60s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_FloorDiv, occurrence: 2
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -88,7 +88,7 @@
unapprove_commands: list[str],
required_labels: list[str] | None = None,
required_title_patterns: list[str] | None = None,
- allowed_users: list[str] | None = None,
+ allowed_users: list[str] // None = None,
allowed_teams: list[str] | None = None,
config_error: str | None = None,
):........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.62s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_FloorDiv, occurrence: 3
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -89,7 +89,7 @@
required_labels: list[str] | None = None,
required_title_patterns: list[str] | None = None,
allowed_users: list[str] | None = None,
- allowed_teams: list[str] | None = None,
+ allowed_teams: list[str] // None = None,
config_error: str | None = None,
):
"""Initialize repo configuration......................................................................... [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_FloorDiv, occurrence: 4
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -90,7 +90,7 @@
required_title_patterns: list[str] | None = None,
allowed_users: list[str] | None = None,
allowed_teams: list[str] | None = None,
- config_error: str | None = None,
+ config_error: str // None = None,
):
"""Initialize repo configuration.
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.62s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_FloorDiv, occurrence: 5
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -136,7 +136,7 @@
pr_labels: list[str],
pr_title: str,
pr_author: str,
- author_team_slugs: list[str] | None = None,
+ author_team_slugs: list[str] // None = None,
) -> tuple[bool, str | None]:
"""Check if a PR is eligible for auto-approval based on filters.
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.69s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_FloorDiv, occurrence: 6
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -137,7 +137,7 @@
pr_title: str,
pr_author: str,
author_team_slugs: list[str] | None = None,
- ) -> tuple[bool, str | None]:
+ ) -> tuple[bool, str // None]:
"""Check if a PR is eligible for auto-approval based on filters.
All configured filters must pass (AND logic between filter types)......................................................................... [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_Mod, occurrence: 0
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -86,7 +86,7 @@
chatops_required_permission: str,
approve_commands: list[str],
unapprove_commands: list[str],
- required_labels: list[str] | None = None,
+ required_labels: list[str] % None = None,
required_title_patterns: list[str] | None = None,
allowed_users: list[str] | None = None,
allowed_teams: list[str] | None = None,........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.69s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_Mod, occurrence: 1
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -87,7 +87,7 @@
approve_commands: list[str],
unapprove_commands: list[str],
required_labels: list[str] | None = None,
- required_title_patterns: list[str] | None = None,
+ required_title_patterns: list[str] % None = None,
allowed_users: list[str] | None = None,
allowed_teams: list[str] | None = None,
config_error: str | None = None,........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.63s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_Mod, occurrence: 2
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -88,7 +88,7 @@
unapprove_commands: list[str],
required_labels: list[str] | None = None,
required_title_patterns: list[str] | None = None,
- allowed_users: list[str] | None = None,
+ allowed_users: list[str] % None = None,
allowed_teams: list[str] | None = None,
config_error: str | None = None,
):........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.60s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_Mod, occurrence: 3
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -89,7 +89,7 @@
required_labels: list[str] | None = None,
required_title_patterns: list[str] | None = None,
allowed_users: list[str] | None = None,
- allowed_teams: list[str] | None = None,
+ allowed_teams: list[str] % None = None,
config_error: str | None = None,
):
"""Initialize repo configuration......................................................................... [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.63s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_Mod, occurrence: 4
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -90,7 +90,7 @@
required_title_patterns: list[str] | None = None,
allowed_users: list[str] | None = None,
allowed_teams: list[str] | None = None,
- config_error: str | None = None,
+ config_error: str % None = None,
):
"""Initialize repo configuration.
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.63s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_Mod, occurrence: 5
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -136,7 +136,7 @@
pr_labels: list[str],
pr_title: str,
pr_author: str,
- author_team_slugs: list[str] | None = None,
+ author_team_slugs: list[str] % None = None,
) -> tuple[bool, str | None]:
"""Check if a PR is eligible for auto-approval based on filters.
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_Mod, occurrence: 6
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -137,7 +137,7 @@
pr_title: str,
pr_author: str,
author_team_slugs: list[str] | None = None,
- ) -> tuple[bool, str | None]:
+ ) -> tuple[bool, str % None]:
"""Check if a PR is eligible for auto-approval based on filters.
All configured filters must pass (AND logic between filter types)......................................................................... [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_Pow, occurrence: 0
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -86,7 +86,7 @@
chatops_required_permission: str,
approve_commands: list[str],
unapprove_commands: list[str],
- required_labels: list[str] | None = None,
+ required_labels: list[str] ** None = None,
required_title_patterns: list[str] | None = None,
allowed_users: list[str] | None = None,
allowed_teams: list[str] | None = None,........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.60s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_Pow, occurrence: 1
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -87,7 +87,7 @@
approve_commands: list[str],
unapprove_commands: list[str],
required_labels: list[str] | None = None,
- required_title_patterns: list[str] | None = None,
+ required_title_patterns: list[str] ** None = None,
allowed_users: list[str] | None = None,
allowed_teams: list[str] | None = None,
config_error: str | None = None,........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.60s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_Pow, occurrence: 2
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -88,7 +88,7 @@
unapprove_commands: list[str],
required_labels: list[str] | None = None,
required_title_patterns: list[str] | None = None,
- allowed_users: list[str] | None = None,
+ allowed_users: list[str] ** None = None,
allowed_teams: list[str] | None = None,
config_error: str | None = None,
):........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_Pow, occurrence: 3
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -89,7 +89,7 @@
required_labels: list[str] | None = None,
required_title_patterns: list[str] | None = None,
allowed_users: list[str] | None = None,
- allowed_teams: list[str] | None = None,
+ allowed_teams: list[str] ** None = None,
config_error: str | None = None,
):
"""Initialize repo configuration......................................................................... [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_Pow, occurrence: 4
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -90,7 +90,7 @@
required_title_patterns: list[str] | None = None,
allowed_users: list[str] | None = None,
allowed_teams: list[str] | None = None,
- config_error: str | None = None,
+ config_error: str ** None = None,
):
"""Initialize repo configuration.
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_Pow, occurrence: 5
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -136,7 +136,7 @@
pr_labels: list[str],
pr_title: str,
pr_author: str,
- author_team_slugs: list[str] | None = None,
+ author_team_slugs: list[str] ** None = None,
) -> tuple[bool, str | None]:
"""Check if a PR is eligible for auto-approval based on filters.
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_Pow, occurrence: 6
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -137,7 +137,7 @@
pr_title: str,
pr_author: str,
author_team_slugs: list[str] | None = None,
- ) -> tuple[bool, str | None]:
+ ) -> tuple[bool, str ** None]:
"""Check if a PR is eligible for auto-approval based on filters.
All configured filters must pass (AND logic between filter types)......................................................................... [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_RShift, occurrence: 0
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -86,7 +86,7 @@
chatops_required_permission: str,
approve_commands: list[str],
unapprove_commands: list[str],
- required_labels: list[str] | None = None,
+ required_labels: list[str] >> None = None,
required_title_patterns: list[str] | None = None,
allowed_users: list[str] | None = None,
allowed_teams: list[str] | None = None,........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.60s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_RShift, occurrence: 1
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -87,7 +87,7 @@
approve_commands: list[str],
unapprove_commands: list[str],
required_labels: list[str] | None = None,
- required_title_patterns: list[str] | None = None,
+ required_title_patterns: list[str] >> None = None,
allowed_users: list[str] | None = None,
allowed_teams: list[str] | None = None,
config_error: str | None = None,........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.60s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_RShift, occurrence: 2
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -88,7 +88,7 @@
unapprove_commands: list[str],
required_labels: list[str] | None = None,
required_title_patterns: list[str] | None = None,
- allowed_users: list[str] | None = None,
+ allowed_users: list[str] >> None = None,
allowed_teams: list[str] | None = None,
config_error: str | None = None,
):........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_RShift, occurrence: 3
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -89,7 +89,7 @@
required_labels: list[str] | None = None,
required_title_patterns: list[str] | None = None,
allowed_users: list[str] | None = None,
- allowed_teams: list[str] | None = None,
+ allowed_teams: list[str] >> None = None,
config_error: str | None = None,
):
"""Initialize repo configuration......................................................................... [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.62s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_RShift, occurrence: 4
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -90,7 +90,7 @@
required_title_patterns: list[str] | None = None,
allowed_users: list[str] | None = None,
allowed_teams: list[str] | None = None,
- config_error: str | None = None,
+ config_error: str >> None = None,
):
"""Initialize repo configuration.
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.62s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_RShift, occurrence: 5
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -136,7 +136,7 @@
pr_labels: list[str],
pr_title: str,
pr_author: str,
- author_team_slugs: list[str] | None = None,
+ author_team_slugs: list[str] >> None = None,
) -> tuple[bool, str | None]:
"""Check if a PR is eligible for auto-approval based on filters.
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.62s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_RShift, occurrence: 6
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -137,7 +137,7 @@
pr_title: str,
pr_author: str,
author_team_slugs: list[str] | None = None,
- ) -> tuple[bool, str | None]:
+ ) -> tuple[bool, str >> None]:
"""Check if a PR is eligible for auto-approval based on filters.
All configured filters must pass (AND logic between filter types)......................................................................... [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.63s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_LShift, occurrence: 0
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -86,7 +86,7 @@
chatops_required_permission: str,
approve_commands: list[str],
unapprove_commands: list[str],
- required_labels: list[str] | None = None,
+ required_labels: list[str] << None = None,
required_title_patterns: list[str] | None = None,
allowed_users: list[str] | None = None,
allowed_teams: list[str] | None = None,........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_LShift, occurrence: 1
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -87,7 +87,7 @@
approve_commands: list[str],
unapprove_commands: list[str],
required_labels: list[str] | None = None,
- required_title_patterns: list[str] | None = None,
+ required_title_patterns: list[str] << None = None,
allowed_users: list[str] | None = None,
allowed_teams: list[str] | None = None,
config_error: str | None = None,........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.62s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_LShift, occurrence: 2
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -88,7 +88,7 @@
unapprove_commands: list[str],
required_labels: list[str] | None = None,
required_title_patterns: list[str] | None = None,
- allowed_users: list[str] | None = None,
+ allowed_users: list[str] << None = None,
allowed_teams: list[str] | None = None,
config_error: str | None = None,
):........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_LShift, occurrence: 3
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -89,7 +89,7 @@
required_labels: list[str] | None = None,
required_title_patterns: list[str] | None = None,
allowed_users: list[str] | None = None,
- allowed_teams: list[str] | None = None,
+ allowed_teams: list[str] << None = None,
config_error: str | None = None,
):
"""Initialize repo configuration......................................................................... [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_LShift, occurrence: 4
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -90,7 +90,7 @@
required_title_patterns: list[str] | None = None,
allowed_users: list[str] | None = None,
allowed_teams: list[str] | None = None,
- config_error: str | None = None,
+ config_error: str << None = None,
):
"""Initialize repo configuration.
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.64s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_LShift, occurrence: 5
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -136,7 +136,7 @@
pr_labels: list[str],
pr_title: str,
pr_author: str,
- author_team_slugs: list[str] | None = None,
+ author_team_slugs: list[str] << None = None,
) -> tuple[bool, str | None]:
"""Check if a PR is eligible for auto-approval based on filters.
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.62s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_LShift, occurrence: 6
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -137,7 +137,7 @@
pr_title: str,
pr_author: str,
author_team_slugs: list[str] | None = None,
- ) -> tuple[bool, str | None]:
+ ) -> tuple[bool, str << None]:
"""Check if a PR is eligible for auto-approval based on filters.
All configured filters must pass (AND logic between filter types)......................................................................... [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_BitAnd, occurrence: 0
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -86,7 +86,7 @@
chatops_required_permission: str,
approve_commands: list[str],
unapprove_commands: list[str],
- required_labels: list[str] | None = None,
+ required_labels: list[str] & None = None,
required_title_patterns: list[str] | None = None,
allowed_users: list[str] | None = None,
allowed_teams: list[str] | None = None,........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_BitAnd, occurrence: 1
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -87,7 +87,7 @@
approve_commands: list[str],
unapprove_commands: list[str],
required_labels: list[str] | None = None,
- required_title_patterns: list[str] | None = None,
+ required_title_patterns: list[str] & None = None,
allowed_users: list[str] | None = None,
allowed_teams: list[str] | None = None,
config_error: str | None = None,........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_BitAnd, occurrence: 2
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -88,7 +88,7 @@
unapprove_commands: list[str],
required_labels: list[str] | None = None,
required_title_patterns: list[str] | None = None,
- allowed_users: list[str] | None = None,
+ allowed_users: list[str] & None = None,
allowed_teams: list[str] | None = None,
config_error: str | None = None,
):........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.65s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_BitAnd, occurrence: 3
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -89,7 +89,7 @@
required_labels: list[str] | None = None,
required_title_patterns: list[str] | None = None,
allowed_users: list[str] | None = None,
- allowed_teams: list[str] | None = None,
+ allowed_teams: list[str] & None = None,
config_error: str | None = None,
):
"""Initialize repo configuration......................................................................... [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.62s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_BitAnd, occurrence: 4
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -90,7 +90,7 @@
required_title_patterns: list[str] | None = None,
allowed_users: list[str] | None = None,
allowed_teams: list[str] | None = None,
- config_error: str | None = None,
+ config_error: str & None = None,
):
"""Initialize repo configuration.
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_BitAnd, occurrence: 5
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -136,7 +136,7 @@
pr_labels: list[str],
pr_title: str,
pr_author: str,
- author_team_slugs: list[str] | None = None,
+ author_team_slugs: list[str] & None = None,
) -> tuple[bool, str | None]:
"""Check if a PR is eligible for auto-approval based on filters.
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.63s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_BitAnd, occurrence: 6
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -137,7 +137,7 @@
pr_title: str,
pr_author: str,
author_team_slugs: list[str] | None = None,
- ) -> tuple[bool, str | None]:
+ ) -> tuple[bool, str & None]:
"""Check if a PR is eligible for auto-approval based on filters.
All configured filters must pass (AND logic between filter types)......................................................................... [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_BitXor, occurrence: 0
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -86,7 +86,7 @@
chatops_required_permission: str,
approve_commands: list[str],
unapprove_commands: list[str],
- required_labels: list[str] | None = None,
+ required_labels: list[str] ^ None = None,
required_title_patterns: list[str] | None = None,
allowed_users: list[str] | None = None,
allowed_teams: list[str] | None = None,........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.62s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_BitXor, occurrence: 1
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -87,7 +87,7 @@
approve_commands: list[str],
unapprove_commands: list[str],
required_labels: list[str] | None = None,
- required_title_patterns: list[str] | None = None,
+ required_title_patterns: list[str] ^ None = None,
allowed_users: list[str] | None = None,
allowed_teams: list[str] | None = None,
config_error: str | None = None,........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.67s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_BitXor, occurrence: 2
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -88,7 +88,7 @@
unapprove_commands: list[str],
required_labels: list[str] | None = None,
required_title_patterns: list[str] | None = None,
- allowed_users: list[str] | None = None,
+ allowed_users: list[str] ^ None = None,
allowed_teams: list[str] | None = None,
config_error: str | None = None,
):........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_BitXor, occurrence: 3
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -89,7 +89,7 @@
required_labels: list[str] | None = None,
required_title_patterns: list[str] | None = None,
allowed_users: list[str] | None = None,
- allowed_teams: list[str] | None = None,
+ allowed_teams: list[str] ^ None = None,
config_error: str | None = None,
):
"""Initialize repo configuration......................................................................... [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.60s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_BitXor, occurrence: 4
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -90,7 +90,7 @@
required_title_patterns: list[str] | None = None,
allowed_users: list[str] | None = None,
allowed_teams: list[str] | None = None,
- config_error: str | None = None,
+ config_error: str ^ None = None,
):
"""Initialize repo configuration.
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_BitXor, occurrence: 5
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -136,7 +136,7 @@
pr_labels: list[str],
pr_title: str,
pr_author: str,
- author_team_slugs: list[str] | None = None,
+ author_team_slugs: list[str] ^ None = None,
) -> tuple[bool, str | None]:
"""Check if a PR is eligible for auto-approval based on filters.
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.60s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_BitXor, occurrence: 6
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -137,7 +137,7 @@
pr_title: str,
pr_author: str,
author_team_slugs: list[str] | None = None,
- ) -> tuple[bool, str | None]:
+ ) -> tuple[bool, str ^ None]:
"""Check if a PR is eligible for auto-approval based on filters.
All configured filters must pass (AND logic between filter types)......................................................................... [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceUnaryOperator_USub_UAdd, occurrence: 0
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -177,7 +177,7 @@
if self.allowed_teams and author_team_slugs:
for team in self.allowed_teams:
# Support both "org/team" and "team" formats
- team_slug = team.split("/")[-1] if "/" in team else team
+ team_slug = team.split("/")[+1] if "/" in team else team
if team_slug in author_team_slugs:
return True, None
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceUnaryOperator_USub_Invert, occurrence: 0
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -177,7 +177,7 @@
if self.allowed_teams and author_team_slugs:
for team in self.allowed_teams:
# Support both "org/team" and "team" formats
- team_slug = team.split("/")[-1] if "/" in team else team
+ team_slug = team.split("/")[~1] if "/" in team else team
if team_slug in author_team_slugs:
return True, None
..........................F
=================================== FAILURES ===================================
_______________________ test_is_pr_eligible_allowed_team _______________________
def test_is_pr_eligible_allowed_team():
"""Test is_pr_eligible returns True when author is in an allowed team."""
config = RepoConfig.from_toml('allowed_teams = ["my-org/release-team"]')
is_eligible, reason = config.is_pr_eligible(
[], "Update deps", "team-member", author_team_slugs=["release-team"]
)
> assert is_eligible is True
E assert False is True
tests/test_config.py:311: AssertionError
=========================== short test summary info ============================
FAILED tests/test_config.py::test_is_pr_eligible_allowed_team - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 26 passed, 3 deselected in 0.47s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceUnaryOperator_USub_Not, occurrence: 0
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -177,7 +177,7 @@
if self.allowed_teams and author_team_slugs:
for team in self.allowed_teams:
# Support both "org/team" and "team" formats
- team_slug = team.split("/")[-1] if "/" in team else team
+ team_slug = team.split("/")[not 1] if "/" in team else team
if team_slug in author_team_slugs:
return True, None
..........................F
=================================== FAILURES ===================================
_______________________ test_is_pr_eligible_allowed_team _______________________
def test_is_pr_eligible_allowed_team():
"""Test is_pr_eligible returns True when author is in an allowed team."""
config = RepoConfig.from_toml('allowed_teams = ["my-org/release-team"]')
is_eligible, reason = config.is_pr_eligible(
[], "Update deps", "team-member", author_team_slugs=["release-team"]
)
> assert is_eligible is True
E assert False is True
tests/test_config.py:311: AssertionError
=========================== short test summary info ============================
FAILED tests/test_config.py::test_is_pr_eligible_allowed_team - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 26 passed, 3 deselected in 0.48s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceUnaryOperator_Delete_USub, occurrence: 0
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -177,7 +177,7 @@
if self.allowed_teams and author_team_slugs:
for team in self.allowed_teams:
# Support both "org/team" and "team" formats
- team_slug = team.split("/")[-1] if "/" in team else team
+ team_slug = team.split("/")[1] if "/" in team else team
if team_slug in author_team_slugs:
return True, None
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.64s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceUnaryOperator_Delete_Not, occurrence: 0
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -156,7 +156,7 @@
# Check required labels filter
if self.required_labels:
- if not any(label in self.required_labels for label in pr_labels):
+ if any(label in self.required_labels for label in pr_labels):
return (
False,
f"PR missing required label (one of: {', '.join(self.required_labels)})",...............F
=================================== FAILURES ===================================
__________________ test_is_pr_eligible_required_label_present __________________
def test_is_pr_eligible_required_label_present():
"""Test is_pr_eligible returns True when required label is present."""
config = RepoConfig.from_toml('required_labels = ["dependencies", "automated"]')
is_eligible, reason = config.is_pr_eligible(["dependencies"], "PR title", "someuser")
> assert is_eligible is True
E assert False is True
tests/test_config.py:205: AssertionError
=========================== short test summary info ============================
FAILED tests/test_config.py::test_is_pr_eligible_required_label_present - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 15 passed, 3 deselected in 0.44s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceUnaryOperator_Delete_Not, occurrence: 1
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -164,7 +164,7 @@
# Check required title patterns filter
if self.required_title_patterns:
- if not any(re.search(pattern, pr_title) for pattern in self.required_title_patterns):
+ if any(re.search(pattern, pr_title) for pattern in self.required_title_patterns):
return False, "PR title does not match any required pattern"
# Check allowed users/teams filter (if either is configured)..................F
=================================== FAILURES ===================================
__________________ test_is_pr_eligible_title_pattern_matches ___________________
def test_is_pr_eligible_title_pattern_matches():
"""Test is_pr_eligible returns True when title matches pattern."""
config = RepoConfig.from_toml('required_title_patterns = ["^chore:", "^\\\\[bot\\\\]"]')
is_eligible, reason = config.is_pr_eligible([], "chore: update dependencies", "someuser")
> assert is_eligible is True
E assert False is True
tests/test_config.py:230: AssertionError
=========================== short test summary info ============================
FAILED tests/test_config.py::test_is_pr_eligible_title_pattern_matches - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 18 passed, 3 deselected in 0.44s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceUnaryOperator_Delete_Not, occurrence: 2
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -200,7 +200,7 @@
Returns:
True if team check is needed (author not in allowed_users but teams configured)
"""
- if not self.allowed_teams:
+ if self.allowed_teams:
return False
if self.allowed_users and pr_author in self.allowed_users:
return False................................F
=================================== FAILURES ===================================
__________________ test_needs_team_check_no_teams_configured ___________________
def test_needs_team_check_no_teams_configured():
"""Test needs_team_check returns False when no teams configured."""
config = RepoConfig.from_toml('allowed_users = ["some-user"]')
> assert config.needs_team_check("any-user") is False
E AssertionError: assert True is False
E + where True = needs_team_check('any-user')
E + where needs_team_check = <stampbot.config.RepoConfig object at 0x7f62aa764b50>.needs_team_check
tests/test_config.py:383: AssertionError
=========================== short test summary info ============================
FAILED tests/test_config.py::test_needs_team_check_no_teams_configured - AssertionError: assert True is False
+ where True = needs_team_check('any-user')
+ where needs_team_check = <stampbot.config.RepoConfig object at 0x7f62aa764b50>.needs_team_check
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 32 passed, 3 deselected in 0.47s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/AddNot, occurrence: 0
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -155,7 +155,7 @@
import re
# Check required labels filter
- if self.required_labels:
+ if not self.required_labels:
if not any(label in self.required_labels for label in pr_labels):
return (
False,..............F
=================================== FAILURES ===================================
________________________ test_is_pr_eligible_no_filters ________________________
def test_is_pr_eligible_no_filters():
"""Test is_pr_eligible returns True when no filters configured."""
config = RepoConfig.default()
is_eligible, reason = config.is_pr_eligible(["some-label"], "Some PR title", "someuser")
> assert is_eligible is True
E assert False is True
tests/test_config.py:197: AssertionError
=========================== short test summary info ============================
FAILED tests/test_config.py::test_is_pr_eligible_no_filters - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 14 passed, 3 deselected in 0.44s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/AddNot, occurrence: 1
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -156,7 +156,7 @@
# Check required labels filter
if self.required_labels:
- if not any(label in self.required_labels for label in pr_labels):
+ if not not any(label in self.required_labels for label in pr_labels):
return (
False,
f"PR missing required label (one of: {', '.join(self.required_labels)})",...............F
=================================== FAILURES ===================================
__________________ test_is_pr_eligible_required_label_present __________________
def test_is_pr_eligible_required_label_present():
"""Test is_pr_eligible returns True when required label is present."""
config = RepoConfig.from_toml('required_labels = ["dependencies", "automated"]')
is_eligible, reason = config.is_pr_eligible(["dependencies"], "PR title", "someuser")
> assert is_eligible is True
E assert False is True
tests/test_config.py:205: AssertionError
=========================== short test summary info ============================
FAILED tests/test_config.py::test_is_pr_eligible_required_label_present - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 15 passed, 3 deselected in 0.44s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/AddNot, occurrence: 2
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -163,7 +163,7 @@
)
# Check required title patterns filter
- if self.required_title_patterns:
+ if not self.required_title_patterns:
if not any(re.search(pattern, pr_title) for pattern in self.required_title_patterns):
return False, "PR title does not match any required pattern"
..............F
=================================== FAILURES ===================================
________________________ test_is_pr_eligible_no_filters ________________________
def test_is_pr_eligible_no_filters():
"""Test is_pr_eligible returns True when no filters configured."""
config = RepoConfig.default()
is_eligible, reason = config.is_pr_eligible(["some-label"], "Some PR title", "someuser")
> assert is_eligible is True
E assert False is True
tests/test_config.py:197: AssertionError
=========================== short test summary info ============================
FAILED tests/test_config.py::test_is_pr_eligible_no_filters - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 14 passed, 3 deselected in 0.45s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/AddNot, occurrence: 3
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -164,7 +164,7 @@
# Check required title patterns filter
if self.required_title_patterns:
- if not any(re.search(pattern, pr_title) for pattern in self.required_title_patterns):
+ if not not any(re.search(pattern, pr_title) for pattern in self.required_title_patterns):
return False, "PR title does not match any required pattern"
# Check allowed users/teams filter (if either is configured)..................F
=================================== FAILURES ===================================
__________________ test_is_pr_eligible_title_pattern_matches ___________________
def test_is_pr_eligible_title_pattern_matches():
"""Test is_pr_eligible returns True when title matches pattern."""
config = RepoConfig.from_toml('required_title_patterns = ["^chore:", "^\\\\[bot\\\\]"]')
is_eligible, reason = config.is_pr_eligible([], "chore: update dependencies", "someuser")
> assert is_eligible is True
E assert False is True
tests/test_config.py:230: AssertionError
=========================== short test summary info ============================
FAILED tests/test_config.py::test_is_pr_eligible_title_pattern_matches - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 18 passed, 3 deselected in 0.44s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/AddNot, occurrence: 4
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -168,7 +168,7 @@
return False, "PR title does not match any required pattern"
# Check allowed users/teams filter (if either is configured)
- if self.allowed_users or self.allowed_teams:
+ if not self.allowed_users or self.allowed_teams:
# User is allowed if they're in allowed_users
if pr_author in self.allowed_users:
return True, None..............F
=================================== FAILURES ===================================
________________________ test_is_pr_eligible_no_filters ________________________
def test_is_pr_eligible_no_filters():
"""Test is_pr_eligible returns True when no filters configured."""
config = RepoConfig.default()
is_eligible, reason = config.is_pr_eligible(["some-label"], "Some PR title", "someuser")
> assert is_eligible is True
E assert False is True
tests/test_config.py:197: AssertionError
=========================== short test summary info ============================
FAILED tests/test_config.py::test_is_pr_eligible_no_filters - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 14 passed, 3 deselected in 0.44s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/AddNot, occurrence: 5
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -170,7 +170,7 @@
# Check allowed users/teams filter (if either is configured)
if self.allowed_users or self.allowed_teams:
# User is allowed if they're in allowed_users
- if pr_author in self.allowed_users:
+ if not pr_author in self.allowed_users:
return True, None
# Or if they're a member of an allowed team........................F
=================================== FAILURES ===================================
_______________________ test_is_pr_eligible_allowed_user _______________________
def test_is_pr_eligible_allowed_user():
"""Test is_pr_eligible returns True when author is in allowed_users."""
config = RepoConfig.from_toml('allowed_users = ["dependabot[bot]", "renovate[bot]"]')
is_eligible, reason = config.is_pr_eligible([], "Update deps", "dependabot[bot]")
> assert is_eligible is True
E assert False is True
tests/test_config.py:293: AssertionError
=========================== short test summary info ============================
FAILED tests/test_config.py::test_is_pr_eligible_allowed_user - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 24 passed, 3 deselected in 0.47s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/AddNot, occurrence: 6
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -174,7 +174,7 @@
return True, None
# Or if they're a member of an allowed team
- if self.allowed_teams and author_team_slugs:
+ if not self.allowed_teams and author_team_slugs:
for team in self.allowed_teams:
# Support both "org/team" and "team" formats
team_slug = team.split("/")[-1] if "/" in team else team..........................F
=================================== FAILURES ===================================
_______________________ test_is_pr_eligible_allowed_team _______________________
def test_is_pr_eligible_allowed_team():
"""Test is_pr_eligible returns True when author is in an allowed team."""
config = RepoConfig.from_toml('allowed_teams = ["my-org/release-team"]')
is_eligible, reason = config.is_pr_eligible(
[], "Update deps", "team-member", author_team_slugs=["release-team"]
)
> assert is_eligible is True
E assert False is True
tests/test_config.py:311: AssertionError
=========================== short test summary info ============================
FAILED tests/test_config.py::test_is_pr_eligible_allowed_team - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 26 passed, 3 deselected in 0.45s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/AddNot, occurrence: 7
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -177,7 +177,7 @@
if self.allowed_teams and author_team_slugs:
for team in self.allowed_teams:
# Support both "org/team" and "team" formats
- team_slug = team.split("/")[-1] if "/" in team else team
+ team_slug = team.split("/")[-1] if not "/" in team else team
if team_slug in author_team_slugs:
return True, None
..........................F
=================================== FAILURES ===================================
_______________________ test_is_pr_eligible_allowed_team _______________________
def test_is_pr_eligible_allowed_team():
"""Test is_pr_eligible returns True when author is in an allowed team."""
config = RepoConfig.from_toml('allowed_teams = ["my-org/release-team"]')
is_eligible, reason = config.is_pr_eligible(
[], "Update deps", "team-member", author_team_slugs=["release-team"]
)
> assert is_eligible is True
E assert False is True
tests/test_config.py:311: AssertionError
=========================== short test summary info ============================
FAILED tests/test_config.py::test_is_pr_eligible_allowed_team - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 26 passed, 3 deselected in 0.45s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/AddNot, occurrence: 8
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -178,7 +178,7 @@
for team in self.allowed_teams:
# Support both "org/team" and "team" formats
team_slug = team.split("/")[-1] if "/" in team else team
- if team_slug in author_team_slugs:
+ if not team_slug in author_team_slugs:
return True, None
# Neither user nor team matched..........................F
=================================== FAILURES ===================================
_______________________ test_is_pr_eligible_allowed_team _______________________
def test_is_pr_eligible_allowed_team():
"""Test is_pr_eligible returns True when author is in an allowed team."""
config = RepoConfig.from_toml('allowed_teams = ["my-org/release-team"]')
is_eligible, reason = config.is_pr_eligible(
[], "Update deps", "team-member", author_team_slugs=["release-team"]
)
> assert is_eligible is True
E assert False is True
tests/test_config.py:311: AssertionError
=========================== short test summary info ============================
FAILED tests/test_config.py::test_is_pr_eligible_allowed_team - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 26 passed, 3 deselected in 0.47s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/AddNot, occurrence: 9
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -182,7 +182,7 @@
return True, None
# Neither user nor team matched
- if self.allowed_users and self.allowed_teams:
+ if not self.allowed_users and self.allowed_teams:
return False, "PR author not in allowed users or teams"
elif self.allowed_users:
return False, f"PR author not in allowed users: {', '.join(self.allowed_users)}"...........................F
=================================== FAILURES ===================================
_____________________ test_is_pr_eligible_team_not_matched _____________________
def test_is_pr_eligible_team_not_matched():
"""Test is_pr_eligible returns False when author is not in any allowed team."""
config = RepoConfig.from_toml('allowed_teams = ["my-org/release-team"]')
is_eligible, reason = config.is_pr_eligible(
[], "Update deps", "non-member", author_team_slugs=["other-team"]
)
assert is_eligible is False
> assert "not a member of any allowed team" in reason
E AssertionError: assert 'not a member of any allowed team' in 'PR author not in allowed users or teams'
tests/test_config.py:322: AssertionError
=========================== short test summary info ============================
FAILED tests/test_config.py::test_is_pr_eligible_team_not_matched - AssertionError: assert 'not a member of any allowed team' in 'PR author not in allowed users or teams'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 27 passed, 3 deselected in 0.45s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/AddNot, occurrence: 10
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -200,7 +200,7 @@
Returns:
True if team check is needed (author not in allowed_users but teams configured)
"""
- if not self.allowed_teams:
+ if not not self.allowed_teams:
return False
if self.allowed_users and pr_author in self.allowed_users:
return False................................F
=================================== FAILURES ===================================
__________________ test_needs_team_check_no_teams_configured ___________________
def test_needs_team_check_no_teams_configured():
"""Test needs_team_check returns False when no teams configured."""
config = RepoConfig.from_toml('allowed_users = ["some-user"]')
> assert config.needs_team_check("any-user") is False
E AssertionError: assert True is False
E + where True = needs_team_check('any-user')
E + where needs_team_check = <stampbot.config.RepoConfig object at 0x7f280738cb50>.needs_team_check
tests/test_config.py:383: AssertionError
=========================== short test summary info ============================
FAILED tests/test_config.py::test_needs_team_check_no_teams_configured - AssertionError: assert True is False
+ where True = needs_team_check('any-user')
+ where needs_team_check = <stampbot.config.RepoConfig object at 0x7f280738cb50>.needs_team_check
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 32 passed, 3 deselected in 0.48s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/AddNot, occurrence: 11
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -202,7 +202,7 @@
"""
if not self.allowed_teams:
return False
- if self.allowed_users and pr_author in self.allowed_users:
+ if not self.allowed_users and pr_author in self.allowed_users:
return False
return True
.................................F
=================================== FAILURES ===================================
_________________ test_needs_team_check_user_in_allowed_users __________________
def test_needs_team_check_user_in_allowed_users():
"""Test needs_team_check returns False when user is in allowed_users."""
toml_content = """
allowed_users = ["special-user"]
allowed_teams = ["my-org/release-team"]
"""
config = RepoConfig.from_toml(toml_content)
> assert config.needs_team_check("special-user") is False
E AssertionError: assert True is False
E + where True = needs_team_check('special-user')
E + where needs_team_check = <stampbot.config.RepoConfig object at 0x7f82b2f9cd60>.needs_team_check
tests/test_config.py:393: AssertionError
=========================== short test summary info ============================
FAILED tests/test_config.py::test_needs_team_check_user_in_allowed_users - AssertionError: assert True is False
+ where True = needs_team_check('special-user')
+ where needs_team_check = <stampbot.config.RepoConfig object at 0x7f82b2f9cd60>.needs_team_check
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 33 passed, 3 deselected in 0.46s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/AddNot, occurrence: 12
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -218,7 +218,7 @@
defaults = REPO_CONFIG_DEFAULTS.copy()
# Allow overriding defaults via settings.toml [defaults] section
- if settings.get("defaults"):
+ if not settings.get("defaults"):
settings_defaults = settings.defaults
for key in defaults:
if hasattr(settings_defaults, key):........F
=================================== FAILURES ===================================
_____________ test_repo_config_get_defaults_with_settings_override _____________
def test_repo_config_get_defaults_with_settings_override():
"""Test _get_defaults respects settings.toml overrides."""
with patch("stampbot.config.settings") as mock_settings:
# Create a mock defaults object with only specific attributes
# Using spec=[] means hasattr will return False for most attributes
mock_defaults = MagicMock(
spec=["approval_labels", "chatops_enabled", "chatops_required_permission"]
)
mock_defaults.approval_labels = ["custom-label"]
mock_defaults.chatops_enabled = False
mock_defaults.chatops_required_permission = "write"
mock_settings.get.return_value = mock_defaults
mock_settings.defaults = mock_defaults
defaults = RepoConfig._get_defaults()
# Should have the overridden values
> assert defaults["approval_labels"] == ["custom-label"]
E AssertionError: assert ['autoapprove', 'stamp'] == ['custom-label']
E
E At index 0 diff: 'autoapprove' != 'custom-label'
E Left contains one more item: 'stamp'
E
E Full diff:
E [
E - 'custom-label',
E + 'autoapprove',
E + 'stamp',
E ]
tests/test_config.py:122: AssertionError
=========================== short test summary info ============================
FAILED tests/test_config.py::test_repo_config_get_defaults_with_settings_override - AssertionError: assert ['autoapprove', 'stamp'] == ['custom-label']
At index 0 diff: 'autoapprove' != 'custom-label'
Left contains one more item: 'stamp'
Full diff:
[
- 'custom-label',
+ 'autoapprove',
+ 'stamp',
]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 8 passed, 3 deselected in 0.43s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/AddNot, occurrence: 13
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -221,7 +221,7 @@
if settings.get("defaults"):
settings_defaults = settings.defaults
for key in defaults:
- if hasattr(settings_defaults, key):
+ if not hasattr(settings_defaults, key):
defaults[key] = getattr(settings_defaults, key)
return defaultsF
=================================== FAILURES ===================================
__________________________ test_repo_config_from_toml __________________________
def test_repo_config_from_toml():
"""Test parsing repository config from TOML."""
toml_content = """
approval_labels = ["test", "autoapprove"]
auto_approve_on_label = true
chatops_enabled = true
chatops_required_permission = "write"
approve_commands = ["approve", "stamp"]
unapprove_commands = ["unapprove"]
"""
> config = RepoConfig.from_toml(toml_content)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_config.py:20:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
stampbot/config.py:246: in from_toml
defaults = cls._get_defaults()
^^^^^^^^^^^^^^^^^^^
stampbot/config.py:225: in _get_defaults
defaults[key] = getattr(settings_defaults, key)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/dynaconf/utils/boxing.py:22: in __getattr__
result = super().__getattr__(n_item, *args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
A = <Box: {'approval_labels': ['autoapprove', 'stamp'], 'auto_approve_on_label': True, 'chatops_enabled': True, 'chatops_r...red_permission': 'maintain', 'approve_commands': ['approve', 'stamp'], 'unapprove_commands': ['unapprove', 'unstamp']}>
item = 'required_labels'
def __getattr__(A,item):
B=item
try:
try:C=A.__getitem__(B,_ignore_default=_E)
except KeyError:C=object.__getattribute__(A,B)
except AttributeError as E:
if B=='__getstate__':raise BoxKeyError(B)from _A
if B==_H:raise BoxError('_box_config key must exist')from _A
if A._box_config[_F]:
D=A._safe_attr(B)
if D in A._box_config[_C]:return A.__getitem__(A._box_config[_C][D])
if A._box_config[_J]:return A.__get_default(B)
> raise BoxKeyError(str(E))from _A
E dynaconf.vendor.box.exceptions.BoxKeyError: "'DynaBox' object has no attribute 'required_labels'"
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/dynaconf/vendor/box/box.py:177: BoxKeyError
=========================== short test summary info ============================
FAILED tests/test_config.py::test_repo_config_from_toml - dynaconf.vendor.box.exceptions.BoxKeyError: "'DynaBox' object has no attribute 'required_labels'"
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 3 deselected in 0.44s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/AddNot, occurrence: 14
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -247,7 +247,7 @@
# Parse the repo-specific TOML
repo_settings: dict[str, Any] = {}
- if toml_content:
+ if not toml_content:
repo_settings = toml.loads(toml_content)
# Merge: repo settings override defaultsF
=================================== FAILURES ===================================
__________________________ test_repo_config_from_toml __________________________
def test_repo_config_from_toml():
"""Test parsing repository config from TOML."""
toml_content = """
approval_labels = ["test", "autoapprove"]
auto_approve_on_label = true
chatops_enabled = true
chatops_required_permission = "write"
approve_commands = ["approve", "stamp"]
unapprove_commands = ["unapprove"]
"""
config = RepoConfig.from_toml(toml_content)
> assert "test" in config.approval_labels
E AssertionError: assert 'test' in <BoxList: ['autoapprove', 'stamp']>
E + where <BoxList: ['autoapprove', 'stamp']> = <stampbot.config.RepoConfig object at 0x7f75a8906660>.approval_labels
tests/test_config.py:21: AssertionError
=========================== short test summary info ============================
FAILED tests/test_config.py::test_repo_config_from_toml - AssertionError: assert 'test' in <BoxList: ['autoapprove', 'stamp']>
+ where <BoxList: ['autoapprove', 'stamp']> = <stampbot.config.RepoConfig object at 0x7f75a8906660>.approval_labels
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 3 deselected in 0.42s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/AddNot, occurrence: 15
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -256,7 +256,7 @@
import re
required_permission = merged.get("chatops_required_permission", "maintain")
- if required_permission not in REPO_PERMISSION_LEVELS:
+ if not required_permission not in REPO_PERMISSION_LEVELS:
raise ValueError(
"Invalid chatops_required_permission: "
f"{required_permission}. "F
=================================== FAILURES ===================================
__________________________ test_repo_config_from_toml __________________________
def test_repo_config_from_toml():
"""Test parsing repository config from TOML."""
toml_content = """
approval_labels = ["test", "autoapprove"]
auto_approve_on_label = true
chatops_enabled = true
chatops_required_permission = "write"
approve_commands = ["approve", "stamp"]
unapprove_commands = ["unapprove"]
"""
> config = RepoConfig.from_toml(toml_content)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_config.py:20:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
cls = <class 'stampbot.config.RepoConfig'>
toml_content = '\napproval_labels = ["test", "autoapprove"]\nauto_approve_on_label = true\nchatops_enabled = true\nchatops_required_permission = "write"\napprove_commands = ["approve", "stamp"]\nunapprove_commands = ["unapprove"]\n'
@classmethod
def from_toml(cls, toml_content: str) -> RepoConfig:
"""Parse TOML content and merge with app defaults.
Uses dynaconf to properly merge repo-specific settings with
the app-level defaults, allowing repos to override only
the settings they need to change.
Args:
toml_content: Raw TOML content from repo's stampbot.toml
Returns:
RepoConfig with merged settings
"""
import toml
# Start with defaults
defaults = cls._get_defaults()
# Parse the repo-specific TOML
repo_settings: dict[str, Any] = {}
if toml_content:
repo_settings = toml.loads(toml_content)
# Merge: repo settings override defaults
merged = {**defaults, **repo_settings}
import re
required_permission = merged.get("chatops_required_permission", "maintain")
if not required_permission not in REPO_PERMISSION_LEVELS:
> raise ValueError(
"Invalid chatops_required_permission: "
f"{required_permission}. "
f"Valid values: {', '.join(REPO_PERMISSION_LEVELS)}"
)
E ValueError: Invalid chatops_required_permission: write. Valid values: none, read, triage, write, maintain, admin
stampbot/config.py:260: ValueError
=========================== short test summary info ============================
FAILED tests/test_config.py::test_repo_config_from_toml - ValueError: Invalid chatops_required_permission: write. Valid values: none, read, triage, write, maintain, admin
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 3 deselected in 0.42s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceTrueWithFalse, occurrence: 0
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -13,7 +13,7 @@
# These can be overridden in settings.toml or per-repo stampbot.toml
REPO_CONFIG_DEFAULTS = {
"approval_labels": ["autoapprove", "stamp"],
- "auto_approve_on_label": True,
+ "auto_approve_on_label": False,
"chatops_enabled": True,
"chatops_required_permission": "maintain",
"approve_commands": ["approve", "stamp"],........F
=================================== FAILURES ===================================
_____________ test_repo_config_get_defaults_with_settings_override _____________
def test_repo_config_get_defaults_with_settings_override():
"""Test _get_defaults respects settings.toml overrides."""
with patch("stampbot.config.settings") as mock_settings:
# Create a mock defaults object with only specific attributes
# Using spec=[] means hasattr will return False for most attributes
mock_defaults = MagicMock(
spec=["approval_labels", "chatops_enabled", "chatops_required_permission"]
)
mock_defaults.approval_labels = ["custom-label"]
mock_defaults.chatops_enabled = False
mock_defaults.chatops_required_permission = "write"
mock_settings.get.return_value = mock_defaults
mock_settings.defaults = mock_defaults
defaults = RepoConfig._get_defaults()
# Should have the overridden values
assert defaults["approval_labels"] == ["custom-label"]
assert defaults["chatops_enabled"] is False
assert defaults["chatops_required_permission"] == "write"
# Non-overridden values should use REPO_CONFIG_DEFAULTS
> assert defaults["auto_approve_on_label"] is True
E assert False is True
tests/test_config.py:126: AssertionError
=========================== short test summary info ============================
FAILED tests/test_config.py::test_repo_config_get_defaults_with_settings_override - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 8 passed, 3 deselected in 0.43s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceTrueWithFalse, occurrence: 1
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -14,7 +14,7 @@
REPO_CONFIG_DEFAULTS = {
"approval_labels": ["autoapprove", "stamp"],
"auto_approve_on_label": True,
- "chatops_enabled": True,
+ "chatops_enabled": False,
"chatops_required_permission": "maintain",
"approve_commands": ["approve", "stamp"],
"unapprove_commands": ["unapprove", "unstamp"],..........F
=================================== FAILURES ===================================
________________ test_repo_config_get_defaults_partial_override ________________
def test_repo_config_get_defaults_partial_override():
"""Test _get_defaults with partial settings override."""
with patch("stampbot.config.settings") as mock_settings:
# Create a mock defaults object with only some attributes
mock_defaults = MagicMock(spec=[]) # Empty spec means no attributes
# But we can still check hasattr behavior by setting one attribute
mock_defaults.approval_labels = ["override-only-this"]
mock_settings.get.return_value = mock_defaults
mock_settings.defaults = mock_defaults
defaults = RepoConfig._get_defaults()
# The overridden value
assert defaults["approval_labels"] == ["override-only-this"]
# Non-overridden values should use REPO_CONFIG_DEFAULTS
assert defaults["auto_approve_on_label"] is True
> assert defaults["chatops_enabled"] is True
E assert False is True
tests/test_config.py:160: AssertionError
=========================== short test summary info ============================
FAILED tests/test_config.py::test_repo_config_get_defaults_partial_override - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 10 passed, 3 deselected in 0.44s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceTrueWithFalse, occurrence: 2
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -39,7 +39,7 @@
envvar_prefix="STAMPBOT",
settings_files=["settings.toml", ".secrets.toml"],
environments=False, # We don't use [development]/[production] sections
- load_dotenv=True,
+ load_dotenv=False,
)
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceTrueWithFalse, occurrence: 3
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -171,7 +171,7 @@
if self.allowed_users or self.allowed_teams:
# User is allowed if they're in allowed_users
if pr_author in self.allowed_users:
- return True, None
+ return False, None
# Or if they're a member of an allowed team
if self.allowed_teams and author_team_slugs:........................F
=================================== FAILURES ===================================
_______________________ test_is_pr_eligible_allowed_user _______________________
def test_is_pr_eligible_allowed_user():
"""Test is_pr_eligible returns True when author is in allowed_users."""
config = RepoConfig.from_toml('allowed_users = ["dependabot[bot]", "renovate[bot]"]')
is_eligible, reason = config.is_pr_eligible([], "Update deps", "dependabot[bot]")
> assert is_eligible is True
E assert False is True
tests/test_config.py:293: AssertionError
=========================== short test summary info ============================
FAILED tests/test_config.py::test_is_pr_eligible_allowed_user - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 24 passed, 3 deselected in 0.45s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceTrueWithFalse, occurrence: 4
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -179,7 +179,7 @@
# Support both "org/team" and "team" formats
team_slug = team.split("/")[-1] if "/" in team else team
if team_slug in author_team_slugs:
- return True, None
+ return False, None
# Neither user nor team matched
if self.allowed_users and self.allowed_teams:..........................F
=================================== FAILURES ===================================
_______________________ test_is_pr_eligible_allowed_team _______________________
def test_is_pr_eligible_allowed_team():
"""Test is_pr_eligible returns True when author is in an allowed team."""
config = RepoConfig.from_toml('allowed_teams = ["my-org/release-team"]')
is_eligible, reason = config.is_pr_eligible(
[], "Update deps", "team-member", author_team_slugs=["release-team"]
)
> assert is_eligible is True
E assert False is True
tests/test_config.py:311: AssertionError
=========================== short test summary info ============================
FAILED tests/test_config.py::test_is_pr_eligible_allowed_team - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 26 passed, 3 deselected in 0.48s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceTrueWithFalse, occurrence: 5
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -189,7 +189,7 @@
else:
return False, "PR author not a member of any allowed team"
- return True, None
+ return False, None
def needs_team_check(self, pr_author: str) -> bool:
"""Check if team membership verification is needed for this author...............F
=================================== FAILURES ===================================
________________________ test_is_pr_eligible_no_filters ________________________
def test_is_pr_eligible_no_filters():
"""Test is_pr_eligible returns True when no filters configured."""
config = RepoConfig.default()
is_eligible, reason = config.is_pr_eligible(["some-label"], "Some PR title", "someuser")
> assert is_eligible is True
E assert False is True
tests/test_config.py:197: AssertionError
=========================== short test summary info ============================
FAILED tests/test_config.py::test_is_pr_eligible_no_filters - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 14 passed, 3 deselected in 0.44s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceTrueWithFalse, occurrence: 6
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -204,7 +204,7 @@
return False
if self.allowed_users and pr_author in self.allowed_users:
return False
- return True
+ return False
@classmethod
def _get_defaults(cls) -> dict[str, Any]:..................................F
=================================== FAILURES ===================================
_______________ test_needs_team_check_user_not_in_allowed_users ________________
def test_needs_team_check_user_not_in_allowed_users():
"""Test needs_team_check returns True when user not in allowed_users but teams configured."""
toml_content = """
allowed_users = ["special-user"]
allowed_teams = ["my-org/release-team"]
"""
config = RepoConfig.from_toml(toml_content)
> assert config.needs_team_check("other-user") is True
E AssertionError: assert False is True
E + where False = needs_team_check('other-user')
E + where needs_team_check = <stampbot.config.RepoConfig object at 0x7f2a03504f70>.needs_team_check
tests/test_config.py:403: AssertionError
=========================== short test summary info ============================
FAILED tests/test_config.py::test_needs_team_check_user_not_in_allowed_users - AssertionError: assert False is True
+ where False = needs_team_check('other-user')
+ where needs_team_check = <stampbot.config.RepoConfig object at 0x7f2a03504f70>.needs_team_check
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 34 passed, 3 deselected in 0.46s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceTrueWithFalse, occurrence: 7
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -273,7 +273,7 @@
return cls(
approval_labels=merged.get("approval_labels", []),
- auto_approve_on_label=merged.get("auto_approve_on_label", True),
+ auto_approve_on_label=merged.get("auto_approve_on_label", False),
chatops_enabled=merged.get("chatops_enabled", True),
chatops_required_permission=required_permission,
approve_commands=merged.get("approve_commands", ["approve", "stamp"]),........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.67s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceTrueWithFalse, occurrence: 8
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -274,7 +274,7 @@
return cls(
approval_labels=merged.get("approval_labels", []),
auto_approve_on_label=merged.get("auto_approve_on_label", True),
- chatops_enabled=merged.get("chatops_enabled", True),
+ chatops_enabled=merged.get("chatops_enabled", False),
chatops_required_permission=required_permission,
approve_commands=merged.get("approve_commands", ["approve", "stamp"]),
unapprove_commands=merged.get("unapprove_commands", ["unapprove", "unstamp"]),........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceFalseWithTrue, occurrence: 0
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -38,7 +38,7 @@
settings = Dynaconf(
envvar_prefix="STAMPBOT",
settings_files=["settings.toml", ".secrets.toml"],
- environments=False, # We don't use [development]/[production] sections
+ environments=True, # We don't use [development]/[production] sections
load_dotenv=True,
)
........................................................................ [ 34%]
...................F
=================================== FAILURES ===================================
__________________ test_lifespan_startup_shutdown_configured ___________________
def test_lifespan_startup_shutdown_configured():
"""Test app lifespan startup and shutdown when configured."""
> from stampbot.main import app
tests/test_main.py:15:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
stampbot/main.py:41: in <module>
configure_logging()
stampbot/logger.py:58: in configure_logging
log_level_int = logging.getLevelName(settings.log_level)
^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/dynaconf/base.py:144: in __getattr__
value = getattr(self._wrapped, name)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <dynaconf.base.Settings object at 0x7f844c30d550>, name = 'LOG_LEVEL'
def __getattribute__(self, name):
if (
name.startswith("__")
or name in RESERVED_ATTRS + UPPER_DEFAULT_SETTINGS
):
return super().__getattribute__(name)
# This is to keep the only upper case mode working
# self._store has Lazy values already evaluated
if (
name.islower()
and self._store.get("LOWERCASE_READ_FOR_DYNACONF", empty) is False
):
try:
# only matches exact casing, first levels always upper
return self._store.__getattribute__(name)
except KeyError:
return super().__getattribute__(name)
# then go to the regular .get which triggers hooks among other things
value = self.get(name, default=empty)
if value is empty:
> return super().__getattribute__(name)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E AttributeError: 'Settings' object has no attribute 'LOG_LEVEL'
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/dynaconf/base.py:325: AttributeError
=========================== short test summary info ============================
FAILED tests/test_main.py::test_lifespan_startup_shutdown_configured - AttributeError: 'Settings' object has no attribute 'LOG_LEVEL'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 91 passed, 3 deselected in 0.84s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceFalseWithTrue, occurrence: 1
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -158,7 +158,7 @@
if self.required_labels:
if not any(label in self.required_labels for label in pr_labels):
return (
- False,
+ True,
f"PR missing required label (one of: {', '.join(self.required_labels)})",
)
................F
=================================== FAILURES ===================================
__________________ test_is_pr_eligible_required_label_missing __________________
def test_is_pr_eligible_required_label_missing():
"""Test is_pr_eligible returns False when required label is missing."""
config = RepoConfig.from_toml('required_labels = ["dependencies", "automated"]')
is_eligible, reason = config.is_pr_eligible(["other-label"], "PR title", "someuser")
> assert is_eligible is False
E assert True is False
tests/test_config.py:213: AssertionError
=========================== short test summary info ============================
FAILED tests/test_config.py::test_is_pr_eligible_required_label_missing - assert True is False
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 16 passed, 3 deselected in 0.44s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceFalseWithTrue, occurrence: 2
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -165,7 +165,7 @@
# Check required title patterns filter
if self.required_title_patterns:
if not any(re.search(pattern, pr_title) for pattern in self.required_title_patterns):
- return False, "PR title does not match any required pattern"
+ return True, "PR title does not match any required pattern"
# Check allowed users/teams filter (if either is configured)
if self.allowed_users or self.allowed_teams:...................F
=================================== FAILURES ===================================
__________________ test_is_pr_eligible_title_pattern_no_match __________________
def test_is_pr_eligible_title_pattern_no_match():
"""Test is_pr_eligible returns False when title doesn't match any pattern."""
config = RepoConfig.from_toml('required_title_patterns = ["^chore:", "^\\\\[bot\\\\]"]')
is_eligible, reason = config.is_pr_eligible([], "feat: add new feature", "someuser")
> assert is_eligible is False
E assert True is False
tests/test_config.py:238: AssertionError
=========================== short test summary info ============================
FAILED tests/test_config.py::test_is_pr_eligible_title_pattern_no_match - assert True is False
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 19 passed, 3 deselected in 0.44s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceFalseWithTrue, occurrence: 3
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -183,7 +183,7 @@
# Neither user nor team matched
if self.allowed_users and self.allowed_teams:
- return False, "PR author not in allowed users or teams"
+ return True, "PR author not in allowed users or teams"
elif self.allowed_users:
return False, f"PR author not in allowed users: {', '.join(self.allowed_users)}"
else:.............................F
=================================== FAILURES ===================================
__________________ test_is_pr_eligible_neither_user_nor_team ___________________
def test_is_pr_eligible_neither_user_nor_team():
"""Test is_pr_eligible returns False when neither user nor team matches."""
toml_content = """
allowed_users = ["special-user"]
allowed_teams = ["my-org/release-team"]
"""
config = RepoConfig.from_toml(toml_content)
is_eligible, reason = config.is_pr_eligible(
[], "Update deps", "random-user", author_team_slugs=["other-team"]
)
> assert is_eligible is False
E assert True is False
tests/test_config.py:348: AssertionError
=========================== short test summary info ============================
FAILED tests/test_config.py::test_is_pr_eligible_neither_user_nor_team - assert True is False
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 29 passed, 3 deselected in 0.45s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceFalseWithTrue, occurrence: 4
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -185,7 +185,7 @@
if self.allowed_users and self.allowed_teams:
return False, "PR author not in allowed users or teams"
elif self.allowed_users:
- return False, f"PR author not in allowed users: {', '.join(self.allowed_users)}"
+ return True, f"PR author not in allowed users: {', '.join(self.allowed_users)}"
else:
return False, "PR author not a member of any allowed team"
.........................F
=================================== FAILURES ===================================
_____________________ test_is_pr_eligible_user_not_allowed _____________________
def test_is_pr_eligible_user_not_allowed():
"""Test is_pr_eligible returns False when author is not in allowed_users."""
config = RepoConfig.from_toml('allowed_users = ["dependabot[bot]", "renovate[bot]"]')
is_eligible, reason = config.is_pr_eligible([], "Update deps", "random-user")
> assert is_eligible is False
E assert True is False
tests/test_config.py:301: AssertionError
=========================== short test summary info ============================
FAILED tests/test_config.py::test_is_pr_eligible_user_not_allowed - assert True is False
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 25 passed, 3 deselected in 0.46s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceFalseWithTrue, occurrence: 5
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -187,7 +187,7 @@
elif self.allowed_users:
return False, f"PR author not in allowed users: {', '.join(self.allowed_users)}"
else:
- return False, "PR author not a member of any allowed team"
+ return True, "PR author not a member of any allowed team"
return True, None
...........................F
=================================== FAILURES ===================================
_____________________ test_is_pr_eligible_team_not_matched _____________________
def test_is_pr_eligible_team_not_matched():
"""Test is_pr_eligible returns False when author is not in any allowed team."""
config = RepoConfig.from_toml('allowed_teams = ["my-org/release-team"]')
is_eligible, reason = config.is_pr_eligible(
[], "Update deps", "non-member", author_team_slugs=["other-team"]
)
> assert is_eligible is False
E assert True is False
tests/test_config.py:321: AssertionError
=========================== short test summary info ============================
FAILED tests/test_config.py::test_is_pr_eligible_team_not_matched - assert True is False
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 27 passed, 3 deselected in 0.46s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceFalseWithTrue, occurrence: 6
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -201,7 +201,7 @@
True if team check is needed (author not in allowed_users but teams configured)
"""
if not self.allowed_teams:
- return False
+ return True
if self.allowed_users and pr_author in self.allowed_users:
return False
return True................................F
=================================== FAILURES ===================================
__________________ test_needs_team_check_no_teams_configured ___________________
def test_needs_team_check_no_teams_configured():
"""Test needs_team_check returns False when no teams configured."""
config = RepoConfig.from_toml('allowed_users = ["some-user"]')
> assert config.needs_team_check("any-user") is False
E AssertionError: assert True is False
E + where True = needs_team_check('any-user')
E + where needs_team_check = <stampbot.config.RepoConfig object at 0x7fe0e0cfcb50>.needs_team_check
tests/test_config.py:383: AssertionError
=========================== short test summary info ============================
FAILED tests/test_config.py::test_needs_team_check_no_teams_configured - AssertionError: assert True is False
+ where True = needs_team_check('any-user')
+ where needs_team_check = <stampbot.config.RepoConfig object at 0x7fe0e0cfcb50>.needs_team_check
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 32 passed, 3 deselected in 0.49s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceFalseWithTrue, occurrence: 7
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -203,7 +203,7 @@
if not self.allowed_teams:
return False
if self.allowed_users and pr_author in self.allowed_users:
- return False
+ return True
return True
@classmethod.................................F
=================================== FAILURES ===================================
_________________ test_needs_team_check_user_in_allowed_users __________________
def test_needs_team_check_user_in_allowed_users():
"""Test needs_team_check returns False when user is in allowed_users."""
toml_content = """
allowed_users = ["special-user"]
allowed_teams = ["my-org/release-team"]
"""
config = RepoConfig.from_toml(toml_content)
> assert config.needs_team_check("special-user") is False
E AssertionError: assert True is False
E + where True = needs_team_check('special-user')
E + where needs_team_check = <stampbot.config.RepoConfig object at 0x7fb4b7e54d60>.needs_team_check
tests/test_config.py:393: AssertionError
=========================== short test summary info ============================
FAILED tests/test_config.py::test_needs_team_check_user_in_allowed_users - AssertionError: assert True is False
+ where True = needs_team_check('special-user')
+ where needs_team_check = <stampbot.config.RepoConfig object at 0x7fb4b7e54d60>.needs_team_check
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 33 passed, 3 deselected in 0.48s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceAndWithOr, occurrence: 0
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -174,7 +174,7 @@
return True, None
# Or if they're a member of an allowed team
- if self.allowed_teams and author_team_slugs:
+ if self.allowed_teams or author_team_slugs:
for team in self.allowed_teams:
# Support both "org/team" and "team" formats
team_slug = team.split("/")[-1] if "/" in team else team........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.69s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceAndWithOr, occurrence: 1
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -182,7 +182,7 @@
return True, None
# Neither user nor team matched
- if self.allowed_users and self.allowed_teams:
+ if self.allowed_users or self.allowed_teams:
return False, "PR author not in allowed users or teams"
elif self.allowed_users:
return False, f"PR author not in allowed users: {', '.join(self.allowed_users)}"...........................F
=================================== FAILURES ===================================
_____________________ test_is_pr_eligible_team_not_matched _____________________
def test_is_pr_eligible_team_not_matched():
"""Test is_pr_eligible returns False when author is not in any allowed team."""
config = RepoConfig.from_toml('allowed_teams = ["my-org/release-team"]')
is_eligible, reason = config.is_pr_eligible(
[], "Update deps", "non-member", author_team_slugs=["other-team"]
)
assert is_eligible is False
> assert "not a member of any allowed team" in reason
E AssertionError: assert 'not a member of any allowed team' in 'PR author not in allowed users or teams'
tests/test_config.py:322: AssertionError
=========================== short test summary info ============================
FAILED tests/test_config.py::test_is_pr_eligible_team_not_matched - AssertionError: assert 'not a member of any allowed team' in 'PR author not in allowed users or teams'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 27 passed, 3 deselected in 0.46s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceAndWithOr, occurrence: 2
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -202,7 +202,7 @@
"""
if not self.allowed_teams:
return False
- if self.allowed_users and pr_author in self.allowed_users:
+ if self.allowed_users or pr_author in self.allowed_users:
return False
return True
..................................F
=================================== FAILURES ===================================
_______________ test_needs_team_check_user_not_in_allowed_users ________________
def test_needs_team_check_user_not_in_allowed_users():
"""Test needs_team_check returns True when user not in allowed_users but teams configured."""
toml_content = """
allowed_users = ["special-user"]
allowed_teams = ["my-org/release-team"]
"""
config = RepoConfig.from_toml(toml_content)
> assert config.needs_team_check("other-user") is True
E AssertionError: assert False is True
E + where False = needs_team_check('other-user')
E + where needs_team_check = <stampbot.config.RepoConfig object at 0x7f989c80cf70>.needs_team_check
tests/test_config.py:403: AssertionError
=========================== short test summary info ============================
FAILED tests/test_config.py::test_needs_team_check_user_not_in_allowed_users - AssertionError: assert False is True
+ where False = needs_team_check('other-user')
+ where needs_team_check = <stampbot.config.RepoConfig object at 0x7f989c80cf70>.needs_team_check
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 34 passed, 3 deselected in 0.45s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceOrWithAnd, occurrence: 0
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -113,7 +113,7 @@
self.chatops_required_permission = chatops_required_permission
self.approve_commands = approve_commands
self.unapprove_commands = unapprove_commands
- self.required_labels = required_labels or []
+ self.required_labels = required_labels and []
self.required_title_patterns = required_title_patterns or []
self.allowed_users = allowed_users or []
self.allowed_teams = allowed_teams or []...........F
=================================== FAILURES ===================================
____________________ test_repo_config_with_required_labels _____________________
def test_repo_config_with_required_labels():
"""Test parsing repository config with required labels."""
toml_content = """
required_labels = ["dependencies", "automated"]
"""
config = RepoConfig.from_toml(toml_content)
> assert config.required_labels == ["dependencies", "automated"]
E AssertionError: assert [] == ['dependencies', 'automated']
E
E Right contains 2 more items, first extra item: 'dependencies'
E
E Full diff:
E + []
E - [
E - 'dependencies',
E - 'automated',
E - ]
tests/test_config.py:170: AssertionError
=========================== short test summary info ============================
FAILED tests/test_config.py::test_repo_config_with_required_labels - AssertionError: assert [] == ['dependencies', 'automated']
Right contains 2 more items, first extra item: 'dependencies'
Full diff:
+ []
- [
- 'dependencies',
- 'automated',
- ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 11 passed, 3 deselected in 0.44s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceOrWithAnd, occurrence: 1
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -114,7 +114,7 @@
self.approve_commands = approve_commands
self.unapprove_commands = unapprove_commands
self.required_labels = required_labels or []
- self.required_title_patterns = required_title_patterns or []
+ self.required_title_patterns = required_title_patterns and []
self.allowed_users = allowed_users or []
self.allowed_teams = allowed_teams or []
self.config_error = config_error............F
=================================== FAILURES ===================================
________________ test_repo_config_with_required_title_patterns _________________
def test_repo_config_with_required_title_patterns():
"""Test parsing repository config with required title patterns."""
toml_content = """
required_title_patterns = ["^\\\\[bot\\\\]", "^chore:"]
"""
config = RepoConfig.from_toml(toml_content)
> assert len(config.required_title_patterns) == 2
E assert 0 == 2
E + where 0 = len([])
E + where [] = <stampbot.config.RepoConfig object at 0x7f0c07ef9220>.required_title_patterns
tests/test_config.py:179: AssertionError
=========================== short test summary info ============================
FAILED tests/test_config.py::test_repo_config_with_required_title_patterns - assert 0 == 2
+ where 0 = len([])
+ where [] = <stampbot.config.RepoConfig object at 0x7f0c07ef9220>.required_title_patterns
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 12 passed, 3 deselected in 0.43s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceOrWithAnd, occurrence: 2
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -115,7 +115,7 @@
self.unapprove_commands = unapprove_commands
self.required_labels = required_labels or []
self.required_title_patterns = required_title_patterns or []
- self.allowed_users = allowed_users or []
+ self.allowed_users = allowed_users and []
self.allowed_teams = allowed_teams or []
self.config_error = config_error
.........................F
=================================== FAILURES ===================================
_____________________ test_is_pr_eligible_user_not_allowed _____________________
def test_is_pr_eligible_user_not_allowed():
"""Test is_pr_eligible returns False when author is not in allowed_users."""
config = RepoConfig.from_toml('allowed_users = ["dependabot[bot]", "renovate[bot]"]')
is_eligible, reason = config.is_pr_eligible([], "Update deps", "random-user")
> assert is_eligible is False
E assert True is False
tests/test_config.py:301: AssertionError
=========================== short test summary info ============================
FAILED tests/test_config.py::test_is_pr_eligible_user_not_allowed - assert True is False
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 25 passed, 3 deselected in 0.46s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceOrWithAnd, occurrence: 3
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -116,7 +116,7 @@
self.required_labels = required_labels or []
self.required_title_patterns = required_title_patterns or []
self.allowed_users = allowed_users or []
- self.allowed_teams = allowed_teams or []
+ self.allowed_teams = allowed_teams and []
self.config_error = config_error
def with_config_error(self, message: str) -> RepoConfig:...........................F
=================================== FAILURES ===================================
_____________________ test_is_pr_eligible_team_not_matched _____________________
def test_is_pr_eligible_team_not_matched():
"""Test is_pr_eligible returns False when author is not in any allowed team."""
config = RepoConfig.from_toml('allowed_teams = ["my-org/release-team"]')
is_eligible, reason = config.is_pr_eligible(
[], "Update deps", "non-member", author_team_slugs=["other-team"]
)
> assert is_eligible is False
E assert True is False
tests/test_config.py:321: AssertionError
=========================== short test summary info ============================
FAILED tests/test_config.py::test_is_pr_eligible_team_not_matched - assert True is False
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 27 passed, 3 deselected in 0.45s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceOrWithAnd, occurrence: 4
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -168,7 +168,7 @@
return False, "PR title does not match any required pattern"
# Check allowed users/teams filter (if either is configured)
- if self.allowed_users or self.allowed_teams:
+ if self.allowed_users and self.allowed_teams:
# User is allowed if they're in allowed_users
if pr_author in self.allowed_users:
return True, None.........................F
=================================== FAILURES ===================================
_____________________ test_is_pr_eligible_user_not_allowed _____________________
def test_is_pr_eligible_user_not_allowed():
"""Test is_pr_eligible returns False when author is not in allowed_users."""
config = RepoConfig.from_toml('allowed_users = ["dependabot[bot]", "renovate[bot]"]')
is_eligible, reason = config.is_pr_eligible([], "Update deps", "random-user")
> assert is_eligible is False
E assert True is False
tests/test_config.py:301: AssertionError
=========================== short test summary info ============================
FAILED tests/test_config.py::test_is_pr_eligible_user_not_allowed - assert True is False
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 25 passed, 3 deselected in 0.45s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ExceptionReplacer, occurrence: 0
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -268,7 +268,7 @@
for pattern in title_patterns:
try:
re.compile(pattern)
- except re.error as e:
+ except reCosmicRayTestingExceptionerror as e:
raise ValueError(f"Invalid regex pattern '{pattern}': {e}") from e
return cls(.............F
=================================== FAILURES ===================================
____________________ test_repo_config_invalid_regex_pattern ____________________
cls = <class 'stampbot.config.RepoConfig'>
toml_content = '\nrequired_title_patterns = ["[invalid"]\n'
@classmethod
def from_toml(cls, toml_content: str) -> RepoConfig:
"""Parse TOML content and merge with app defaults.
Uses dynaconf to properly merge repo-specific settings with
the app-level defaults, allowing repos to override only
the settings they need to change.
Args:
toml_content: Raw TOML content from repo's stampbot.toml
Returns:
RepoConfig with merged settings
"""
import toml
# Start with defaults
defaults = cls._get_defaults()
# Parse the repo-specific TOML
repo_settings: dict[str, Any] = {}
if toml_content:
repo_settings = toml.loads(toml_content)
# Merge: repo settings override defaults
merged = {**defaults, **repo_settings}
import re
required_permission = merged.get("chatops_required_permission", "maintain")
if required_permission not in REPO_PERMISSION_LEVELS:
raise ValueError(
"Invalid chatops_required_permission: "
f"{required_permission}. "
f"Valid values: {', '.join(REPO_PERMISSION_LEVELS)}"
)
# Validate regex patterns
title_patterns = merged.get("required_title_patterns", [])
for pattern in title_patterns:
try:
> re.compile(pattern)
stampbot/config.py:270:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/re/__init__.py:289: in compile
return _compile(pattern, flags)
^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/re/__init__.py:350: in _compile
p = _compiler.compile(pattern, flags)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/re/_compiler.py:762: in compile
p = _parser.parse(p, flags)
^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/re/_parser.py:973: in parse
p = _parse_sub(source, state, flags & SRE_FLAG_VERBOSE, 0)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/re/_parser.py:460: in _parse_sub
itemsappend(_parse(source, state, verbose, nested + 1,
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
source = <re._parser.Tokenizer object at 0x7fc301b59470>
state = <re._parser.State object at 0x7fc3016064d0>, verbose = 0, nested = 1
first = True
def _parse(source, state, verbose, nested, first=False):
# parse a simple pattern
subpattern = SubPattern(state)
# precompute constants into local variables
subpatternappend = subpattern.append
sourceget = source.get
sourcematch = source.match
_len = len
_ord = ord
while True:
this = source.next
if this is None:
break # end of pattern
if this in "|)":
break # end of subpattern
sourceget()
if verbose:
# skip whitespace and comments
if this in WHITESPACE:
continue
if this == "#":
while True:
this = sourceget()
if this is None or this == "\n":
break
continue
if this[0] == "\\":
code = _escape(source, this, state)
subpatternappend(code)
elif this not in SPECIAL_CHARS:
subpatternappend((LITERAL, _ord(this)))
elif this == "[":
here = source.tell() - 1
# character set
set = []
setappend = set.append
## if sourcematch(":"):
## pass # handle character classes
if source.next == '[':
import warnings
warnings.warn(
'Possible nested set at position %d' % source.tell(),
FutureWarning, stacklevel=nested + 6
)
negate = sourcematch("^")
# check remaining characters
while True:
this = sourceget()
if this is None:
> raise source.error("unterminated character set",
source.tell() - here)
E re.PatternError: unterminated character set at position 0
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/re/_parser.py:568: PatternError
During handling of the above exception, another exception occurred:
def test_repo_config_invalid_regex_pattern():
"""Test invalid regex pattern raises a ValueError."""
toml_content = """
required_title_patterns = ["[invalid"]
"""
with pytest.raises(ValueError, match="Invalid regex pattern"):
> RepoConfig.from_toml(toml_content)
tests/test_config.py:190:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
cls = <class 'stampbot.config.RepoConfig'>
toml_content = '\nrequired_title_patterns = ["[invalid"]\n'
@classmethod
def from_toml(cls, toml_content: str) -> RepoConfig:
"""Parse TOML content and merge with app defaults.
Uses dynaconf to properly merge repo-specific settings with
the app-level defaults, allowing repos to override only
the settings they need to change.
Args:
toml_content: Raw TOML content from repo's stampbot.toml
Returns:
RepoConfig with merged settings
"""
import toml
# Start with defaults
defaults = cls._get_defaults()
# Parse the repo-specific TOML
repo_settings: dict[str, Any] = {}
if toml_content:
repo_settings = toml.loads(toml_content)
# Merge: repo settings override defaults
merged = {**defaults, **repo_settings}
import re
required_permission = merged.get("chatops_required_permission", "maintain")
if required_permission not in REPO_PERMISSION_LEVELS:
raise ValueError(
"Invalid chatops_required_permission: "
f"{required_permission}. "
f"Valid values: {', '.join(REPO_PERMISSION_LEVELS)}"
)
# Validate regex patterns
title_patterns = merged.get("required_title_patterns", [])
for pattern in title_patterns:
try:
re.compile(pattern)
> except reCosmicRayTestingExceptionerror as e:
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E NameError: name 'reCosmicRayTestingExceptionerror' is not defined
stampbot/config.py:271: NameError
=========================== short test summary info ============================
FAILED tests/test_config.py::test_repo_config_invalid_regex_pattern - NameError: name 'reCosmicRayTestingExceptionerror' is not defined
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 13 passed, 3 deselected in 0.51s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/NumberReplacer, occurrence: 0
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -177,7 +177,7 @@
if self.allowed_teams and author_team_slugs:
for team in self.allowed_teams:
# Support both "org/team" and "team" formats
- team_slug = team.split("/")[-1] if "/" in team else team
+ team_slug = team.split("/")[- 2] if "/" in team else team
if team_slug in author_team_slugs:
return True, None
..........................F
=================================== FAILURES ===================================
_______________________ test_is_pr_eligible_allowed_team _______________________
def test_is_pr_eligible_allowed_team():
"""Test is_pr_eligible returns True when author is in an allowed team."""
config = RepoConfig.from_toml('allowed_teams = ["my-org/release-team"]')
is_eligible, reason = config.is_pr_eligible(
[], "Update deps", "team-member", author_team_slugs=["release-team"]
)
> assert is_eligible is True
E assert False is True
tests/test_config.py:311: AssertionError
=========================== short test summary info ============================
FAILED tests/test_config.py::test_is_pr_eligible_allowed_team - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 26 passed, 3 deselected in 0.45s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/NumberReplacer, occurrence: 1
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -177,7 +177,7 @@
if self.allowed_teams and author_team_slugs:
for team in self.allowed_teams:
# Support both "org/team" and "team" formats
- team_slug = team.split("/")[-1] if "/" in team else team
+ team_slug = team.split("/")[- 0] if "/" in team else team
if team_slug in author_team_slugs:
return True, None
..........................F
=================================== FAILURES ===================================
_______________________ test_is_pr_eligible_allowed_team _______________________
def test_is_pr_eligible_allowed_team():
"""Test is_pr_eligible returns True when author is in an allowed team."""
config = RepoConfig.from_toml('allowed_teams = ["my-org/release-team"]')
is_eligible, reason = config.is_pr_eligible(
[], "Update deps", "team-member", author_team_slugs=["release-team"]
)
> assert is_eligible is True
E assert False is True
tests/test_config.py:311: AssertionError
=========================== short test summary info ============================
FAILED tests/test_config.py::test_is_pr_eligible_allowed_team - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 26 passed, 3 deselected in 0.45s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/RemoveDecorator, occurrence: 0
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -205,8 +205,6 @@
if self.allowed_users and pr_author in self.allowed_users:
return False
return True
-
- @classmethod
def _get_defaults(cls) -> dict[str, Any]:
"""Get default repo config values.
F
=================================== FAILURES ===================================
__________________________ test_repo_config_from_toml __________________________
def test_repo_config_from_toml():
"""Test parsing repository config from TOML."""
toml_content = """
approval_labels = ["test", "autoapprove"]
auto_approve_on_label = true
chatops_enabled = true
chatops_required_permission = "write"
approve_commands = ["approve", "stamp"]
unapprove_commands = ["unapprove"]
"""
> config = RepoConfig.from_toml(toml_content)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_config.py:20:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
cls = <class 'stampbot.config.RepoConfig'>
toml_content = '\napproval_labels = ["test", "autoapprove"]\nauto_approve_on_label = true\nchatops_enabled = true\nchatops_required_permission = "write"\napprove_commands = ["approve", "stamp"]\nunapprove_commands = ["unapprove"]\n'
@classmethod
def from_toml(cls, toml_content: str) -> RepoConfig:
"""Parse TOML content and merge with app defaults.
Uses dynaconf to properly merge repo-specific settings with
the app-level defaults, allowing repos to override only
the settings they need to change.
Args:
toml_content: Raw TOML content from repo's stampbot.toml
Returns:
RepoConfig with merged settings
"""
import toml
# Start with defaults
> defaults = cls._get_defaults()
^^^^^^^^^^^^^^^^^^^
E TypeError: RepoConfig._get_defaults() missing 1 required positional argument: 'cls'
stampbot/config.py:244: TypeError
=========================== short test summary info ============================
FAILED tests/test_config.py::test_repo_config_from_toml - TypeError: RepoConfig._get_defaults() missing 1 required positional argument: 'cls'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 3 deselected in 0.40s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/RemoveDecorator, occurrence: 1
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -225,8 +225,6 @@
defaults[key] = getattr(settings_defaults, key)
return defaults
-
- @classmethod
def from_toml(cls, toml_content: str) -> RepoConfig:
"""Parse TOML content and merge with app defaults.
F
=================================== FAILURES ===================================
__________________________ test_repo_config_from_toml __________________________
def test_repo_config_from_toml():
"""Test parsing repository config from TOML."""
toml_content = """
approval_labels = ["test", "autoapprove"]
auto_approve_on_label = true
chatops_enabled = true
chatops_required_permission = "write"
approve_commands = ["approve", "stamp"]
unapprove_commands = ["unapprove"]
"""
> config = RepoConfig.from_toml(toml_content)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E TypeError: RepoConfig.from_toml() missing 1 required positional argument: 'toml_content'
tests/test_config.py:20: TypeError
=========================== short test summary info ============================
FAILED tests/test_config.py::test_repo_config_from_toml - TypeError: RepoConfig.from_toml() missing 1 required positional argument: 'toml_content'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 3 deselected in 0.39s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/RemoveDecorator, occurrence: 2
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -283,8 +283,6 @@
allowed_users=merged.get("allowed_users", []),
allowed_teams=merged.get("allowed_teams", []),
)
-
- @classmethod
def default(cls) -> RepoConfig:
"""Return default configuration from app settings.
.F
=================================== FAILURES ===================================
___________________________ test_repo_config_default ___________________________
def test_repo_config_default():
"""Test default repository config."""
> config = RepoConfig.default()
^^^^^^^^^^^^^^^^^^^^
E TypeError: RepoConfig.default() missing 1 required positional argument: 'cls'
tests/test_config.py:32: TypeError
=========================== short test summary info ============================
FAILED tests/test_config.py::test_repo_config_default - TypeError: RepoConfig.default() missing 1 required positional argument: 'cls'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 1 passed, 3 deselected in 0.43s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ZeroIterationForLoop, occurrence: 0
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -175,7 +175,7 @@
# Or if they're a member of an allowed team
if self.allowed_teams and author_team_slugs:
- for team in self.allowed_teams:
+ for team in []:
# Support both "org/team" and "team" formats
team_slug = team.split("/")[-1] if "/" in team else team
if team_slug in author_team_slugs:..........................F
=================================== FAILURES ===================================
_______________________ test_is_pr_eligible_allowed_team _______________________
def test_is_pr_eligible_allowed_team():
"""Test is_pr_eligible returns True when author is in an allowed team."""
config = RepoConfig.from_toml('allowed_teams = ["my-org/release-team"]')
is_eligible, reason = config.is_pr_eligible(
[], "Update deps", "team-member", author_team_slugs=["release-team"]
)
> assert is_eligible is True
E assert False is True
tests/test_config.py:311: AssertionError
=========================== short test summary info ============================
FAILED tests/test_config.py::test_is_pr_eligible_allowed_team - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 26 passed, 3 deselected in 0.45s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ZeroIterationForLoop, occurrence: 1
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -220,7 +220,7 @@
# Allow overriding defaults via settings.toml [defaults] section
if settings.get("defaults"):
settings_defaults = settings.defaults
- for key in defaults:
+ for key in []:
if hasattr(settings_defaults, key):
defaults[key] = getattr(settings_defaults, key)
........F
=================================== FAILURES ===================================
_____________ test_repo_config_get_defaults_with_settings_override _____________
def test_repo_config_get_defaults_with_settings_override():
"""Test _get_defaults respects settings.toml overrides."""
with patch("stampbot.config.settings") as mock_settings:
# Create a mock defaults object with only specific attributes
# Using spec=[] means hasattr will return False for most attributes
mock_defaults = MagicMock(
spec=["approval_labels", "chatops_enabled", "chatops_required_permission"]
)
mock_defaults.approval_labels = ["custom-label"]
mock_defaults.chatops_enabled = False
mock_defaults.chatops_required_permission = "write"
mock_settings.get.return_value = mock_defaults
mock_settings.defaults = mock_defaults
defaults = RepoConfig._get_defaults()
# Should have the overridden values
> assert defaults["approval_labels"] == ["custom-label"]
E AssertionError: assert ['autoapprove', 'stamp'] == ['custom-label']
E
E At index 0 diff: 'autoapprove' != 'custom-label'
E Left contains one more item: 'stamp'
E
E Full diff:
E [
E - 'custom-label',
E + 'autoapprove',
E + 'stamp',
E ]
tests/test_config.py:122: AssertionError
=========================== short test summary info ============================
FAILED tests/test_config.py::test_repo_config_get_defaults_with_settings_override - AssertionError: assert ['autoapprove', 'stamp'] == ['custom-label']
At index 0 diff: 'autoapprove' != 'custom-label'
Left contains one more item: 'stamp'
Full diff:
[
- 'custom-label',
+ 'autoapprove',
+ 'stamp',
]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 8 passed, 3 deselected in 0.44s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ZeroIterationForLoop, occurrence: 2
--- mutation diff ---
--- astampbot/config.py
+++ bstampbot/config.py
@@ -265,7 +265,7 @@
# Validate regex patterns
title_patterns = merged.get("required_title_patterns", [])
- for pattern in title_patterns:
+ for pattern in []:
try:
re.compile(pattern)
except re.error as e:.............F
=================================== FAILURES ===================================
____________________ test_repo_config_invalid_regex_pattern ____________________
def test_repo_config_invalid_regex_pattern():
"""Test invalid regex pattern raises a ValueError."""
toml_content = """
required_title_patterns = ["[invalid"]
"""
> with pytest.raises(ValueError, match="Invalid regex pattern"):
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E Failed: DID NOT RAISE <class 'ValueError'>
tests/test_config.py:189: Failed
=========================== short test summary info ============================
FAILED tests/test_config.py::test_repo_config_invalid_regex_pattern - Failed: DID NOT RAISE <class 'ValueError'>
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 13 passed, 3 deselected in 0.43s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_Add, occurrence: 0
--- mutation diff ---
--- astampbot/manifest.py
+++ bstampbot/manifest.py
@@ -63,7 +63,7 @@
def create_manifest(
redirect_url: str,
- webhook_url: str | None = None,
+ webhook_url: str + None = None,
app_name: str = "Stampbot",
app_description: str = "GitHub PR Auto-Approval Bot",
) -> dict[str, Any]:........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.60s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_Sub, occurrence: 0
--- mutation diff ---
--- astampbot/manifest.py
+++ bstampbot/manifest.py
@@ -63,7 +63,7 @@
def create_manifest(
redirect_url: str,
- webhook_url: str | None = None,
+ webhook_url: str - None = None,
app_name: str = "Stampbot",
app_description: str = "GitHub PR Auto-Approval Bot",
) -> dict[str, Any]:........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.64s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_Mul, occurrence: 0
--- mutation diff ---
--- astampbot/manifest.py
+++ bstampbot/manifest.py
@@ -63,7 +63,7 @@
def create_manifest(
redirect_url: str,
- webhook_url: str | None = None,
+ webhook_url: str * None = None,
app_name: str = "Stampbot",
app_description: str = "GitHub PR Auto-Approval Bot",
) -> dict[str, Any]:........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.66s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_Div, occurrence: 0
--- mutation diff ---
--- astampbot/manifest.py
+++ bstampbot/manifest.py
@@ -63,7 +63,7 @@
def create_manifest(
redirect_url: str,
- webhook_url: str | None = None,
+ webhook_url: str / None = None,
app_name: str = "Stampbot",
app_description: str = "GitHub PR Auto-Approval Bot",
) -> dict[str, Any]:........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.60s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_FloorDiv, occurrence: 0
--- mutation diff ---
--- astampbot/manifest.py
+++ bstampbot/manifest.py
@@ -63,7 +63,7 @@
def create_manifest(
redirect_url: str,
- webhook_url: str | None = None,
+ webhook_url: str // None = None,
app_name: str = "Stampbot",
app_description: str = "GitHub PR Auto-Approval Bot",
) -> dict[str, Any]:........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_Mod, occurrence: 0
--- mutation diff ---
--- astampbot/manifest.py
+++ bstampbot/manifest.py
@@ -63,7 +63,7 @@
def create_manifest(
redirect_url: str,
- webhook_url: str | None = None,
+ webhook_url: str % None = None,
app_name: str = "Stampbot",
app_description: str = "GitHub PR Auto-Approval Bot",
) -> dict[str, Any]:........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_Pow, occurrence: 0
--- mutation diff ---
--- astampbot/manifest.py
+++ bstampbot/manifest.py
@@ -63,7 +63,7 @@
def create_manifest(
redirect_url: str,
- webhook_url: str | None = None,
+ webhook_url: str ** None = None,
app_name: str = "Stampbot",
app_description: str = "GitHub PR Auto-Approval Bot",
) -> dict[str, Any]:........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.60s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_RShift, occurrence: 0
--- mutation diff ---
--- astampbot/manifest.py
+++ bstampbot/manifest.py
@@ -63,7 +63,7 @@
def create_manifest(
redirect_url: str,
- webhook_url: str | None = None,
+ webhook_url: str >> None = None,
app_name: str = "Stampbot",
app_description: str = "GitHub PR Auto-Approval Bot",
) -> dict[str, Any]:........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.60s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_LShift, occurrence: 0
--- mutation diff ---
--- astampbot/manifest.py
+++ bstampbot/manifest.py
@@ -63,7 +63,7 @@
def create_manifest(
redirect_url: str,
- webhook_url: str | None = None,
+ webhook_url: str << None = None,
app_name: str = "Stampbot",
app_description: str = "GitHub PR Auto-Approval Bot",
) -> dict[str, Any]:........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.67s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_BitAnd, occurrence: 0
--- mutation diff ---
--- astampbot/manifest.py
+++ bstampbot/manifest.py
@@ -63,7 +63,7 @@
def create_manifest(
redirect_url: str,
- webhook_url: str | None = None,
+ webhook_url: str & None = None,
app_name: str = "Stampbot",
app_description: str = "GitHub PR Auto-Approval Bot",
) -> dict[str, Any]:........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.62s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_BitXor, occurrence: 0
--- mutation diff ---
--- astampbot/manifest.py
+++ bstampbot/manifest.py
@@ -63,7 +63,7 @@
def create_manifest(
redirect_url: str,
- webhook_url: str | None = None,
+ webhook_url: str ^ None = None,
app_name: str = "Stampbot",
app_description: str = "GitHub PR Auto-Approval Bot",
) -> dict[str, Any]:........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.65s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceComparisonOperator_Eq_NotEq, occurrence: 0
--- mutation diff ---
--- astampbot/manifest.py
+++ bstampbot/manifest.py
@@ -31,7 +31,7 @@
raise ValueError(f"Invalid {name}: must be a complete URL")
# Allow http only for localhost (development)
- if parsed.scheme == "http":
+ if parsed.scheme != "http":
if parsed.hostname not in ("localhost", "127.0.0.1", "::1"):
raise ValueError(f"Invalid {name}: must use HTTPS (HTTP only allowed for localhost)")
elif parsed.scheme != "https":........................................................................ [ 34%]
.......................................F
=================================== FAILURES ===================================
_________________ TestUrlValidation.test_allows_http_localhost _________________
self = <tests.test_manifest.TestUrlValidation object at 0x7fb3d4a91450>
def test_allows_http_localhost(self):
"""Test that HTTP URLs for localhost are allowed."""
> manifest = create_manifest(
redirect_url="http://localhost:8000/setup/callback",
webhook_url="http://localhost:8000/webhook",
)
tests/test_manifest.py:32:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
stampbot/manifest.py:85: in create_manifest
_validate_url(redirect_url, "redirect_url")
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
url = 'http://localhost:8000/setup/callback', name = 'redirect_url'
def _validate_url(url: str, name: str) -> None:
"""Validate that a URL is well-formed and uses HTTPS (or localhost).
Args:
url: URL to validate
name: Name of the URL parameter for error messages
Raises:
ValueError: If URL is invalid or doesn't use HTTPS
"""
parsed = urlparse(url)
if not parsed.scheme or not parsed.netloc:
raise ValueError(f"Invalid {name}: must be a complete URL")
# Allow http only for localhost (development)
if parsed.scheme != "http":
if parsed.hostname not in ("localhost", "127.0.0.1", "::1"):
raise ValueError(f"Invalid {name}: must use HTTPS (HTTP only allowed for localhost)")
elif parsed.scheme != "https":
> raise ValueError(f"Invalid {name}: must use HTTPS")
E ValueError: Invalid redirect_url: must use HTTPS
stampbot/manifest.py:38: ValueError
=========================== short test summary info ============================
FAILED tests/test_manifest.py::TestUrlValidation::test_allows_http_localhost - ValueError: Invalid redirect_url: must use HTTPS
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 111 passed, 3 deselected in 0.89s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceComparisonOperator_Eq_Lt, occurrence: 0
--- mutation diff ---
--- astampbot/manifest.py
+++ bstampbot/manifest.py
@@ -31,7 +31,7 @@
raise ValueError(f"Invalid {name}: must be a complete URL")
# Allow http only for localhost (development)
- if parsed.scheme == "http":
+ if parsed.scheme < "http":
if parsed.hostname not in ("localhost", "127.0.0.1", "::1"):
raise ValueError(f"Invalid {name}: must use HTTPS (HTTP only allowed for localhost)")
elif parsed.scheme != "https":........................................................................ [ 34%]
.......................................F
=================================== FAILURES ===================================
_________________ TestUrlValidation.test_allows_http_localhost _________________
self = <tests.test_manifest.TestUrlValidation object at 0x7f994bf01450>
def test_allows_http_localhost(self):
"""Test that HTTP URLs for localhost are allowed."""
> manifest = create_manifest(
redirect_url="http://localhost:8000/setup/callback",
webhook_url="http://localhost:8000/webhook",
)
tests/test_manifest.py:32:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
stampbot/manifest.py:85: in create_manifest
_validate_url(redirect_url, "redirect_url")
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
url = 'http://localhost:8000/setup/callback', name = 'redirect_url'
def _validate_url(url: str, name: str) -> None:
"""Validate that a URL is well-formed and uses HTTPS (or localhost).
Args:
url: URL to validate
name: Name of the URL parameter for error messages
Raises:
ValueError: If URL is invalid or doesn't use HTTPS
"""
parsed = urlparse(url)
if not parsed.scheme or not parsed.netloc:
raise ValueError(f"Invalid {name}: must be a complete URL")
# Allow http only for localhost (development)
if parsed.scheme < "http":
if parsed.hostname not in ("localhost", "127.0.0.1", "::1"):
raise ValueError(f"Invalid {name}: must use HTTPS (HTTP only allowed for localhost)")
elif parsed.scheme != "https":
> raise ValueError(f"Invalid {name}: must use HTTPS")
E ValueError: Invalid redirect_url: must use HTTPS
stampbot/manifest.py:38: ValueError
=========================== short test summary info ============================
FAILED tests/test_manifest.py::TestUrlValidation::test_allows_http_localhost - ValueError: Invalid redirect_url: must use HTTPS
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 111 passed, 3 deselected in 0.91s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceComparisonOperator_Eq_LtE, occurrence: 0
--- mutation diff ---
--- astampbot/manifest.py
+++ bstampbot/manifest.py
@@ -31,7 +31,7 @@
raise ValueError(f"Invalid {name}: must be a complete URL")
# Allow http only for localhost (development)
- if parsed.scheme == "http":
+ if parsed.scheme <= "http":
if parsed.hostname not in ("localhost", "127.0.0.1", "::1"):
raise ValueError(f"Invalid {name}: must use HTTPS (HTTP only allowed for localhost)")
elif parsed.scheme != "https":........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceComparisonOperator_Eq_Gt, occurrence: 0
--- mutation diff ---
--- astampbot/manifest.py
+++ bstampbot/manifest.py
@@ -31,7 +31,7 @@
raise ValueError(f"Invalid {name}: must be a complete URL")
# Allow http only for localhost (development)
- if parsed.scheme == "http":
+ if parsed.scheme > "http":
if parsed.hostname not in ("localhost", "127.0.0.1", "::1"):
raise ValueError(f"Invalid {name}: must use HTTPS (HTTP only allowed for localhost)")
elif parsed.scheme != "https":........................................................................ [ 34%]
.......................................F
=================================== FAILURES ===================================
_________________ TestUrlValidation.test_allows_http_localhost _________________
self = <tests.test_manifest.TestUrlValidation object at 0x7f7bac961450>
def test_allows_http_localhost(self):
"""Test that HTTP URLs for localhost are allowed."""
> manifest = create_manifest(
redirect_url="http://localhost:8000/setup/callback",
webhook_url="http://localhost:8000/webhook",
)
tests/test_manifest.py:32:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
stampbot/manifest.py:85: in create_manifest
_validate_url(redirect_url, "redirect_url")
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
url = 'http://localhost:8000/setup/callback', name = 'redirect_url'
def _validate_url(url: str, name: str) -> None:
"""Validate that a URL is well-formed and uses HTTPS (or localhost).
Args:
url: URL to validate
name: Name of the URL parameter for error messages
Raises:
ValueError: If URL is invalid or doesn't use HTTPS
"""
parsed = urlparse(url)
if not parsed.scheme or not parsed.netloc:
raise ValueError(f"Invalid {name}: must be a complete URL")
# Allow http only for localhost (development)
if parsed.scheme > "http":
if parsed.hostname not in ("localhost", "127.0.0.1", "::1"):
raise ValueError(f"Invalid {name}: must use HTTPS (HTTP only allowed for localhost)")
elif parsed.scheme != "https":
> raise ValueError(f"Invalid {name}: must use HTTPS")
E ValueError: Invalid redirect_url: must use HTTPS
stampbot/manifest.py:38: ValueError
=========================== short test summary info ============================
FAILED tests/test_manifest.py::TestUrlValidation::test_allows_http_localhost - ValueError: Invalid redirect_url: must use HTTPS
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 111 passed, 3 deselected in 0.88s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceComparisonOperator_Eq_GtE, occurrence: 0
--- mutation diff ---
--- astampbot/manifest.py
+++ bstampbot/manifest.py
@@ -31,7 +31,7 @@
raise ValueError(f"Invalid {name}: must be a complete URL")
# Allow http only for localhost (development)
- if parsed.scheme == "http":
+ if parsed.scheme >= "http":
if parsed.hostname not in ("localhost", "127.0.0.1", "::1"):
raise ValueError(f"Invalid {name}: must use HTTPS (HTTP only allowed for localhost)")
elif parsed.scheme != "https":........................................................................ [ 34%]
..........................................F
=================================== FAILURES ===================================
________ TestCreateManifest.test_manifest_contains_required_permissions ________
self = <tests.test_manifest.TestCreateManifest object at 0x7f6948459810>
def test_manifest_contains_required_permissions(self):
"""Test manifest contains required permissions."""
> manifest = create_manifest(
redirect_url="https://example.com/setup/callback",
)
tests/test_manifest.py:63:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
stampbot/manifest.py:85: in create_manifest
_validate_url(redirect_url, "redirect_url")
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
url = 'https://example.com/setup/callback', name = 'redirect_url'
def _validate_url(url: str, name: str) -> None:
"""Validate that a URL is well-formed and uses HTTPS (or localhost).
Args:
url: URL to validate
name: Name of the URL parameter for error messages
Raises:
ValueError: If URL is invalid or doesn't use HTTPS
"""
parsed = urlparse(url)
if not parsed.scheme or not parsed.netloc:
raise ValueError(f"Invalid {name}: must be a complete URL")
# Allow http only for localhost (development)
if parsed.scheme >= "http":
if parsed.hostname not in ("localhost", "127.0.0.1", "::1"):
> raise ValueError(f"Invalid {name}: must use HTTPS (HTTP only allowed for localhost)")
E ValueError: Invalid redirect_url: must use HTTPS (HTTP only allowed for localhost)
stampbot/manifest.py:36: ValueError
=========================== short test summary info ============================
FAILED tests/test_manifest.py::TestCreateManifest::test_manifest_contains_required_permissions - ValueError: Invalid redirect_url: must use HTTPS (HTTP only allowed for localhost)
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 114 passed, 3 deselected in 0.87s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceComparisonOperator_Eq_Is, occurrence: 0
--- mutation diff ---
--- astampbot/manifest.py
+++ bstampbot/manifest.py
@@ -31,7 +31,7 @@
raise ValueError(f"Invalid {name}: must be a complete URL")
# Allow http only for localhost (development)
- if parsed.scheme == "http":
+ if parsed.scheme is "http":
if parsed.hostname not in ("localhost", "127.0.0.1", "::1"):
raise ValueError(f"Invalid {name}: must use HTTPS (HTTP only allowed for localhost)")
elif parsed.scheme != "https":........................................................................ [ 34%]
.......................................F
=================================== FAILURES ===================================
_________________ TestUrlValidation.test_allows_http_localhost _________________
self = <tests.test_manifest.TestUrlValidation object at 0x7f7966d31450>
def test_allows_http_localhost(self):
"""Test that HTTP URLs for localhost are allowed."""
> manifest = create_manifest(
redirect_url="http://localhost:8000/setup/callback",
webhook_url="http://localhost:8000/webhook",
)
tests/test_manifest.py:32:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
stampbot/manifest.py:85: in create_manifest
_validate_url(redirect_url, "redirect_url")
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
url = 'http://localhost:8000/setup/callback', name = 'redirect_url'
def _validate_url(url: str, name: str) -> None:
"""Validate that a URL is well-formed and uses HTTPS (or localhost).
Args:
url: URL to validate
name: Name of the URL parameter for error messages
Raises:
ValueError: If URL is invalid or doesn't use HTTPS
"""
parsed = urlparse(url)
if not parsed.scheme or not parsed.netloc:
raise ValueError(f"Invalid {name}: must be a complete URL")
# Allow http only for localhost (development)
if parsed.scheme is "http":
if parsed.hostname not in ("localhost", "127.0.0.1", "::1"):
raise ValueError(f"Invalid {name}: must use HTTPS (HTTP only allowed for localhost)")
elif parsed.scheme != "https":
> raise ValueError(f"Invalid {name}: must use HTTPS")
E ValueError: Invalid redirect_url: must use HTTPS
stampbot/manifest.py:38: ValueError
=============================== warnings summary ===============================
stampbot/manifest.py:34
/home/runner/work/stampbot/stampbot/stampbot/manifest.py:34: SyntaxWarning: "is" with 'str' literal. Did you mean "=="?
if parsed.scheme is "http":
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_manifest.py::TestUrlValidation::test_allows_http_localhost - ValueError: Invalid redirect_url: must use HTTPS
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 111 passed, 3 deselected, 1 warning in 0.90s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceComparisonOperator_Eq_IsNot, occurrence: 0
--- mutation diff ---
--- astampbot/manifest.py
+++ bstampbot/manifest.py
@@ -31,7 +31,7 @@
raise ValueError(f"Invalid {name}: must be a complete URL")
# Allow http only for localhost (development)
- if parsed.scheme == "http":
+ if parsed.scheme is not "http":
if parsed.hostname not in ("localhost", "127.0.0.1", "::1"):
raise ValueError(f"Invalid {name}: must use HTTPS (HTTP only allowed for localhost)")
elif parsed.scheme != "https":........................................................................ [ 34%]
..........................................F
=================================== FAILURES ===================================
________ TestCreateManifest.test_manifest_contains_required_permissions ________
self = <tests.test_manifest.TestCreateManifest object at 0x7f7dfcec9810>
def test_manifest_contains_required_permissions(self):
"""Test manifest contains required permissions."""
> manifest = create_manifest(
redirect_url="https://example.com/setup/callback",
)
tests/test_manifest.py:63:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
stampbot/manifest.py:85: in create_manifest
_validate_url(redirect_url, "redirect_url")
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
url = 'https://example.com/setup/callback', name = 'redirect_url'
def _validate_url(url: str, name: str) -> None:
"""Validate that a URL is well-formed and uses HTTPS (or localhost).
Args:
url: URL to validate
name: Name of the URL parameter for error messages
Raises:
ValueError: If URL is invalid or doesn't use HTTPS
"""
parsed = urlparse(url)
if not parsed.scheme or not parsed.netloc:
raise ValueError(f"Invalid {name}: must be a complete URL")
# Allow http only for localhost (development)
if parsed.scheme is not "http":
if parsed.hostname not in ("localhost", "127.0.0.1", "::1"):
> raise ValueError(f"Invalid {name}: must use HTTPS (HTTP only allowed for localhost)")
E ValueError: Invalid redirect_url: must use HTTPS (HTTP only allowed for localhost)
stampbot/manifest.py:36: ValueError
=============================== warnings summary ===============================
stampbot/manifest.py:34
/home/runner/work/stampbot/stampbot/stampbot/manifest.py:34: SyntaxWarning: "is not" with 'str' literal. Did you mean "!="?
if parsed.scheme is not "http":
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_manifest.py::TestCreateManifest::test_manifest_contains_required_permissions - ValueError: Invalid redirect_url: must use HTTPS (HTTP only allowed for localhost)
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 114 passed, 3 deselected, 1 warning in 0.90s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceComparisonOperator_NotEq_Eq, occurrence: 0
--- mutation diff ---
--- astampbot/manifest.py
+++ bstampbot/manifest.py
@@ -34,7 +34,7 @@
if parsed.scheme == "http":
if parsed.hostname not in ("localhost", "127.0.0.1", "::1"):
raise ValueError(f"Invalid {name}: must use HTTPS (HTTP only allowed for localhost)")
- elif parsed.scheme != "https":
+ elif parsed.scheme == "https":
raise ValueError(f"Invalid {name}: must use HTTPS")
........................................................................ [ 34%]
..........................................F
=================================== FAILURES ===================================
________ TestCreateManifest.test_manifest_contains_required_permissions ________
self = <tests.test_manifest.TestCreateManifest object at 0x7ff8a21cd810>
def test_manifest_contains_required_permissions(self):
"""Test manifest contains required permissions."""
> manifest = create_manifest(
redirect_url="https://example.com/setup/callback",
)
tests/test_manifest.py:63:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
stampbot/manifest.py:85: in create_manifest
_validate_url(redirect_url, "redirect_url")
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
url = 'https://example.com/setup/callback', name = 'redirect_url'
def _validate_url(url: str, name: str) -> None:
"""Validate that a URL is well-formed and uses HTTPS (or localhost).
Args:
url: URL to validate
name: Name of the URL parameter for error messages
Raises:
ValueError: If URL is invalid or doesn't use HTTPS
"""
parsed = urlparse(url)
if not parsed.scheme or not parsed.netloc:
raise ValueError(f"Invalid {name}: must be a complete URL")
# Allow http only for localhost (development)
if parsed.scheme == "http":
if parsed.hostname not in ("localhost", "127.0.0.1", "::1"):
raise ValueError(f"Invalid {name}: must use HTTPS (HTTP only allowed for localhost)")
elif parsed.scheme == "https":
> raise ValueError(f"Invalid {name}: must use HTTPS")
E ValueError: Invalid redirect_url: must use HTTPS
stampbot/manifest.py:38: ValueError
=========================== short test summary info ============================
FAILED tests/test_manifest.py::TestCreateManifest::test_manifest_contains_required_permissions - ValueError: Invalid redirect_url: must use HTTPS
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 114 passed, 3 deselected in 0.87s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceComparisonOperator_NotEq_Lt, occurrence: 0
--- mutation diff ---
--- astampbot/manifest.py
+++ bstampbot/manifest.py
@@ -34,7 +34,7 @@
if parsed.scheme == "http":
if parsed.hostname not in ("localhost", "127.0.0.1", "::1"):
raise ValueError(f"Invalid {name}: must use HTTPS (HTTP only allowed for localhost)")
- elif parsed.scheme != "https":
+ elif parsed.scheme < "https":
raise ValueError(f"Invalid {name}: must use HTTPS")
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.62s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceComparisonOperator_NotEq_LtE, occurrence: 0
--- mutation diff ---
--- astampbot/manifest.py
+++ bstampbot/manifest.py
@@ -34,7 +34,7 @@
if parsed.scheme == "http":
if parsed.hostname not in ("localhost", "127.0.0.1", "::1"):
raise ValueError(f"Invalid {name}: must use HTTPS (HTTP only allowed for localhost)")
- elif parsed.scheme != "https":
+ elif parsed.scheme <= "https":
raise ValueError(f"Invalid {name}: must use HTTPS")
........................................................................ [ 34%]
..........................................F
=================================== FAILURES ===================================
________ TestCreateManifest.test_manifest_contains_required_permissions ________
self = <tests.test_manifest.TestCreateManifest object at 0x7fc61100d810>
def test_manifest_contains_required_permissions(self):
"""Test manifest contains required permissions."""
> manifest = create_manifest(
redirect_url="https://example.com/setup/callback",
)
tests/test_manifest.py:63:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
stampbot/manifest.py:85: in create_manifest
_validate_url(redirect_url, "redirect_url")
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
url = 'https://example.com/setup/callback', name = 'redirect_url'
def _validate_url(url: str, name: str) -> None:
"""Validate that a URL is well-formed and uses HTTPS (or localhost).
Args:
url: URL to validate
name: Name of the URL parameter for error messages
Raises:
ValueError: If URL is invalid or doesn't use HTTPS
"""
parsed = urlparse(url)
if not parsed.scheme or not parsed.netloc:
raise ValueError(f"Invalid {name}: must be a complete URL")
# Allow http only for localhost (development)
if parsed.scheme == "http":
if parsed.hostname not in ("localhost", "127.0.0.1", "::1"):
raise ValueError(f"Invalid {name}: must use HTTPS (HTTP only allowed for localhost)")
elif parsed.scheme <= "https":
> raise ValueError(f"Invalid {name}: must use HTTPS")
E ValueError: Invalid redirect_url: must use HTTPS
stampbot/manifest.py:38: ValueError
=========================== short test summary info ============================
FAILED tests/test_manifest.py::TestCreateManifest::test_manifest_contains_required_permissions - ValueError: Invalid redirect_url: must use HTTPS
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 114 passed, 3 deselected in 0.88s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceComparisonOperator_NotEq_Gt, occurrence: 0
--- mutation diff ---
--- astampbot/manifest.py
+++ bstampbot/manifest.py
@@ -34,7 +34,7 @@
if parsed.scheme == "http":
if parsed.hostname not in ("localhost", "127.0.0.1", "::1"):
raise ValueError(f"Invalid {name}: must use HTTPS (HTTP only allowed for localhost)")
- elif parsed.scheme != "https":
+ elif parsed.scheme > "https":
raise ValueError(f"Invalid {name}: must use HTTPS")
........................................................................ [ 34%]
.........................................F
=================================== FAILURES ===================================
_______________ TestUrlValidation.test_rejects_non_https_scheme ________________
self = <tests.test_manifest.TestUrlValidation object at 0x7f78a3f7e780>
def test_rejects_non_https_scheme(self):
"""Test that non-HTTPS schemes are rejected for webhook URLs."""
import pytest
> with pytest.raises(ValueError, match="must use HTTPS"):
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E Failed: DID NOT RAISE <class 'ValueError'>
tests/test_manifest.py:51: Failed
=========================== short test summary info ============================
FAILED tests/test_manifest.py::TestUrlValidation::test_rejects_non_https_scheme - Failed: DID NOT RAISE <class 'ValueError'>
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 113 passed, 3 deselected in 0.88s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceComparisonOperator_NotEq_GtE, occurrence: 0
--- mutation diff ---
--- astampbot/manifest.py
+++ bstampbot/manifest.py
@@ -34,7 +34,7 @@
if parsed.scheme == "http":
if parsed.hostname not in ("localhost", "127.0.0.1", "::1"):
raise ValueError(f"Invalid {name}: must use HTTPS (HTTP only allowed for localhost)")
- elif parsed.scheme != "https":
+ elif parsed.scheme >= "https":
raise ValueError(f"Invalid {name}: must use HTTPS")
........................................................................ [ 34%]
..........................................F
=================================== FAILURES ===================================
________ TestCreateManifest.test_manifest_contains_required_permissions ________
self = <tests.test_manifest.TestCreateManifest object at 0x7f7e61171810>
def test_manifest_contains_required_permissions(self):
"""Test manifest contains required permissions."""
> manifest = create_manifest(
redirect_url="https://example.com/setup/callback",
)
tests/test_manifest.py:63:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
stampbot/manifest.py:85: in create_manifest
_validate_url(redirect_url, "redirect_url")
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
url = 'https://example.com/setup/callback', name = 'redirect_url'
def _validate_url(url: str, name: str) -> None:
"""Validate that a URL is well-formed and uses HTTPS (or localhost).
Args:
url: URL to validate
name: Name of the URL parameter for error messages
Raises:
ValueError: If URL is invalid or doesn't use HTTPS
"""
parsed = urlparse(url)
if not parsed.scheme or not parsed.netloc:
raise ValueError(f"Invalid {name}: must be a complete URL")
# Allow http only for localhost (development)
if parsed.scheme == "http":
if parsed.hostname not in ("localhost", "127.0.0.1", "::1"):
raise ValueError(f"Invalid {name}: must use HTTPS (HTTP only allowed for localhost)")
elif parsed.scheme >= "https":
> raise ValueError(f"Invalid {name}: must use HTTPS")
E ValueError: Invalid redirect_url: must use HTTPS
stampbot/manifest.py:38: ValueError
=========================== short test summary info ============================
FAILED tests/test_manifest.py::TestCreateManifest::test_manifest_contains_required_permissions - ValueError: Invalid redirect_url: must use HTTPS
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 114 passed, 3 deselected in 0.88s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceComparisonOperator_NotEq_Is, occurrence: 0
--- mutation diff ---
--- astampbot/manifest.py
+++ bstampbot/manifest.py
@@ -34,7 +34,7 @@
if parsed.scheme == "http":
if parsed.hostname not in ("localhost", "127.0.0.1", "::1"):
raise ValueError(f"Invalid {name}: must use HTTPS (HTTP only allowed for localhost)")
- elif parsed.scheme != "https":
+ elif parsed.scheme is "https":
raise ValueError(f"Invalid {name}: must use HTTPS")
........................................................................ [ 34%]
.........................................F
=================================== FAILURES ===================================
_______________ TestUrlValidation.test_rejects_non_https_scheme ________________
self = <tests.test_manifest.TestUrlValidation object at 0x7fb294082780>
def test_rejects_non_https_scheme(self):
"""Test that non-HTTPS schemes are rejected for webhook URLs."""
import pytest
> with pytest.raises(ValueError, match="must use HTTPS"):
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E Failed: DID NOT RAISE <class 'ValueError'>
tests/test_manifest.py:51: Failed
=============================== warnings summary ===============================
stampbot/manifest.py:37
/home/runner/work/stampbot/stampbot/stampbot/manifest.py:37: SyntaxWarning: "is" with 'str' literal. Did you mean "=="?
elif parsed.scheme is "https":
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_manifest.py::TestUrlValidation::test_rejects_non_https_scheme - Failed: DID NOT RAISE <class 'ValueError'>
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 113 passed, 3 deselected, 1 warning in 0.88s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceComparisonOperator_NotEq_IsNot, occurrence: 0
--- mutation diff ---
--- astampbot/manifest.py
+++ bstampbot/manifest.py
@@ -34,7 +34,7 @@
if parsed.scheme == "http":
if parsed.hostname not in ("localhost", "127.0.0.1", "::1"):
raise ValueError(f"Invalid {name}: must use HTTPS (HTTP only allowed for localhost)")
- elif parsed.scheme != "https":
+ elif parsed.scheme is not "https":
raise ValueError(f"Invalid {name}: must use HTTPS")
........................................................................ [ 34%]
..........................................F
=================================== FAILURES ===================================
________ TestCreateManifest.test_manifest_contains_required_permissions ________
self = <tests.test_manifest.TestCreateManifest object at 0x7fb2358f5810>
def test_manifest_contains_required_permissions(self):
"""Test manifest contains required permissions."""
> manifest = create_manifest(
redirect_url="https://example.com/setup/callback",
)
tests/test_manifest.py:63:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
stampbot/manifest.py:85: in create_manifest
_validate_url(redirect_url, "redirect_url")
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
url = 'https://example.com/setup/callback', name = 'redirect_url'
def _validate_url(url: str, name: str) -> None:
"""Validate that a URL is well-formed and uses HTTPS (or localhost).
Args:
url: URL to validate
name: Name of the URL parameter for error messages
Raises:
ValueError: If URL is invalid or doesn't use HTTPS
"""
parsed = urlparse(url)
if not parsed.scheme or not parsed.netloc:
raise ValueError(f"Invalid {name}: must be a complete URL")
# Allow http only for localhost (development)
if parsed.scheme == "http":
if parsed.hostname not in ("localhost", "127.0.0.1", "::1"):
raise ValueError(f"Invalid {name}: must use HTTPS (HTTP only allowed for localhost)")
elif parsed.scheme is not "https":
> raise ValueError(f"Invalid {name}: must use HTTPS")
E ValueError: Invalid redirect_url: must use HTTPS
stampbot/manifest.py:38: ValueError
=============================== warnings summary ===============================
stampbot/manifest.py:37
/home/runner/work/stampbot/stampbot/stampbot/manifest.py:37: SyntaxWarning: "is not" with 'str' literal. Did you mean "!="?
elif parsed.scheme is not "https":
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_manifest.py::TestCreateManifest::test_manifest_contains_required_permissions - ValueError: Invalid redirect_url: must use HTTPS
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 114 passed, 3 deselected, 1 warning in 0.89s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceUnaryOperator_Delete_Not, occurrence: 0
--- mutation diff ---
--- astampbot/manifest.py
+++ bstampbot/manifest.py
@@ -27,7 +27,7 @@
"""
parsed = urlparse(url)
- if not parsed.scheme or not parsed.netloc:
+ if parsed.scheme or not parsed.netloc:
raise ValueError(f"Invalid {name}: must be a complete URL")
# Allow http only for localhost (development)........................................................................ [ 34%]
......................................F
=================================== FAILURES ===================================
__________ TestUrlValidation.test_rejects_http_non_localhost_redirect __________
self = <tests.test_manifest.TestUrlValidation object at 0x7f21d876ed50>
def test_rejects_http_non_localhost_redirect(self):
"""Test that HTTP redirect URLs for non-localhost are rejected."""
import pytest
with pytest.raises(ValueError, match="must use HTTPS"):
> create_manifest(
redirect_url="http://example.com/setup/callback",
)
tests/test_manifest.py:26:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
stampbot/manifest.py:85: in create_manifest
_validate_url(redirect_url, "redirect_url")
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
url = 'http://example.com/setup/callback', name = 'redirect_url'
def _validate_url(url: str, name: str) -> None:
"""Validate that a URL is well-formed and uses HTTPS (or localhost).
Args:
url: URL to validate
name: Name of the URL parameter for error messages
Raises:
ValueError: If URL is invalid or doesn't use HTTPS
"""
parsed = urlparse(url)
if parsed.scheme or not parsed.netloc:
> raise ValueError(f"Invalid {name}: must be a complete URL")
E ValueError: Invalid redirect_url: must be a complete URL
stampbot/manifest.py:31: ValueError
During handling of the above exception, another exception occurred:
self = <tests.test_manifest.TestUrlValidation object at 0x7f21d876ed50>
def test_rejects_http_non_localhost_redirect(self):
"""Test that HTTP redirect URLs for non-localhost are rejected."""
import pytest
> with pytest.raises(ValueError, match="must use HTTPS"):
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E AssertionError: Regex pattern did not match.
E Expected regex: 'must use HTTPS'
E Actual message: 'Invalid redirect_url: must be a complete URL'
tests/test_manifest.py:25: AssertionError
=========================== short test summary info ============================
FAILED tests/test_manifest.py::TestUrlValidation::test_rejects_http_non_localhost_redirect - AssertionError: Regex pattern did not match.
Expected regex: 'must use HTTPS'
Actual message: 'Invalid redirect_url: must be a complete URL'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 110 passed, 3 deselected in 0.89s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceUnaryOperator_Delete_Not, occurrence: 1
--- mutation diff ---
--- astampbot/manifest.py
+++ bstampbot/manifest.py
@@ -27,7 +27,7 @@
"""
parsed = urlparse(url)
- if not parsed.scheme or not parsed.netloc:
+ if not parsed.scheme or parsed.netloc:
raise ValueError(f"Invalid {name}: must be a complete URL")
# Allow http only for localhost (development)........................................................................ [ 34%]
......................................F
=================================== FAILURES ===================================
__________ TestUrlValidation.test_rejects_http_non_localhost_redirect __________
self = <tests.test_manifest.TestUrlValidation object at 0x7fef9c4a6d50>
def test_rejects_http_non_localhost_redirect(self):
"""Test that HTTP redirect URLs for non-localhost are rejected."""
import pytest
with pytest.raises(ValueError, match="must use HTTPS"):
> create_manifest(
redirect_url="http://example.com/setup/callback",
)
tests/test_manifest.py:26:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
stampbot/manifest.py:85: in create_manifest
_validate_url(redirect_url, "redirect_url")
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
url = 'http://example.com/setup/callback', name = 'redirect_url'
def _validate_url(url: str, name: str) -> None:
"""Validate that a URL is well-formed and uses HTTPS (or localhost).
Args:
url: URL to validate
name: Name of the URL parameter for error messages
Raises:
ValueError: If URL is invalid or doesn't use HTTPS
"""
parsed = urlparse(url)
if not parsed.scheme or parsed.netloc:
> raise ValueError(f"Invalid {name}: must be a complete URL")
E ValueError: Invalid redirect_url: must be a complete URL
stampbot/manifest.py:31: ValueError
During handling of the above exception, another exception occurred:
self = <tests.test_manifest.TestUrlValidation object at 0x7fef9c4a6d50>
def test_rejects_http_non_localhost_redirect(self):
"""Test that HTTP redirect URLs for non-localhost are rejected."""
import pytest
> with pytest.raises(ValueError, match="must use HTTPS"):
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E AssertionError: Regex pattern did not match.
E Expected regex: 'must use HTTPS'
E Actual message: 'Invalid redirect_url: must be a complete URL'
tests/test_manifest.py:25: AssertionError
=========================== short test summary info ============================
FAILED tests/test_manifest.py::TestUrlValidation::test_rejects_http_non_localhost_redirect - AssertionError: Regex pattern did not match.
Expected regex: 'must use HTTPS'
Actual message: 'Invalid redirect_url: must be a complete URL'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 110 passed, 3 deselected in 0.93s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceUnaryOperator_Delete_Not, occurrence: 2
--- mutation diff ---
--- astampbot/manifest.py
+++ bstampbot/manifest.py
@@ -147,7 +147,7 @@
ValueError: If code contains invalid characters
"""
# Validate code to prevent SSRF - GitHub codes are alphanumeric
- if not code or not code.isalnum():
+ if code or not code.isalnum():
raise ValueError("Invalid manifest code format")
url = GITHUB_MANIFEST_CONVERSION_URL.format(code=code)........................................................................ [ 34%]
.....................................................F
=================================== FAILURES ===================================
_________________ TestExchangeCode.test_exchange_code_success __________________
self = <tests.test_manifest.TestExchangeCode object at 0x7f91f4b7f390>
@pytest.mark.asyncio
async def test_exchange_code_success(self):
"""Test successful code exchange."""
import httpx
mock_response_data = {
"id": 12345,
"pem": "-----BEGIN RSA PRIVATE KEY-----\ntest\n-----END RSA PRIVATE KEY-----",
"webhook_secret": "test-secret",
"slug": "my-stampbot",
"name": "My Stampbot",
"client_id": "client123",
"client_secret": "secret456",
}
# Create a proper mock response with request attached
mock_request = httpx.Request(
"POST", "https://api.github.com/app-manifests/testcode123/conversions"
)
mock_response = httpx.Response(200, json=mock_response_data, request=mock_request)
with patch("httpx.AsyncClient.post", new_callable=AsyncMock) as mock_post:
mock_post.return_value = mock_response
> result = await exchange_code_for_credentials("testcode123")
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_manifest.py:204:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
code = 'testcode123'
async def exchange_code_for_credentials(code: str) -> dict[str, Any]:
"""Exchange the temporary code for app credentials.
After a user creates a GitHub App from a manifest, GitHub redirects
to the callback URL with a temporary code. This function exchanges
that code for the actual app credentials.
Args:
code: Temporary code from GitHub callback
Returns:
Dictionary with app credentials:
- id: App ID
- pem: Private key (PEM format)
- webhook_secret: Webhook secret
- slug: App slug
- name: App name
- client_id: OAuth client ID
- client_secret: OAuth client secret
Raises:
httpx.HTTPStatusError: If API call fails
ValueError: If code contains invalid characters
"""
# Validate code to prevent SSRF - GitHub codes are alphanumeric
if code or not code.isalnum():
> raise ValueError("Invalid manifest code format")
E ValueError: Invalid manifest code format
stampbot/manifest.py:151: ValueError
=========================== short test summary info ============================
FAILED tests/test_manifest.py::TestExchangeCode::test_exchange_code_success - ValueError: Invalid manifest code format
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 125 passed, 3 deselected in 0.91s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceUnaryOperator_Delete_Not, occurrence: 3
--- mutation diff ---
--- astampbot/manifest.py
+++ bstampbot/manifest.py
@@ -147,7 +147,7 @@
ValueError: If code contains invalid characters
"""
# Validate code to prevent SSRF - GitHub codes are alphanumeric
- if not code or not code.isalnum():
+ if not code or code.isalnum():
raise ValueError("Invalid manifest code format")
url = GITHUB_MANIFEST_CONVERSION_URL.format(code=code)........................................................................ [ 34%]
.....................................................F
=================================== FAILURES ===================================
_________________ TestExchangeCode.test_exchange_code_success __________________
self = <tests.test_manifest.TestExchangeCode object at 0x7f933e73b390>
@pytest.mark.asyncio
async def test_exchange_code_success(self):
"""Test successful code exchange."""
import httpx
mock_response_data = {
"id": 12345,
"pem": "-----BEGIN RSA PRIVATE KEY-----\ntest\n-----END RSA PRIVATE KEY-----",
"webhook_secret": "test-secret",
"slug": "my-stampbot",
"name": "My Stampbot",
"client_id": "client123",
"client_secret": "secret456",
}
# Create a proper mock response with request attached
mock_request = httpx.Request(
"POST", "https://api.github.com/app-manifests/testcode123/conversions"
)
mock_response = httpx.Response(200, json=mock_response_data, request=mock_request)
with patch("httpx.AsyncClient.post", new_callable=AsyncMock) as mock_post:
mock_post.return_value = mock_response
> result = await exchange_code_for_credentials("testcode123")
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_manifest.py:204:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
code = 'testcode123'
async def exchange_code_for_credentials(code: str) -> dict[str, Any]:
"""Exchange the temporary code for app credentials.
After a user creates a GitHub App from a manifest, GitHub redirects
to the callback URL with a temporary code. This function exchanges
that code for the actual app credentials.
Args:
code: Temporary code from GitHub callback
Returns:
Dictionary with app credentials:
- id: App ID
- pem: Private key (PEM format)
- webhook_secret: Webhook secret
- slug: App slug
- name: App name
- client_id: OAuth client ID
- client_secret: OAuth client secret
Raises:
httpx.HTTPStatusError: If API call fails
ValueError: If code contains invalid characters
"""
# Validate code to prevent SSRF - GitHub codes are alphanumeric
if not code or code.isalnum():
> raise ValueError("Invalid manifest code format")
E ValueError: Invalid manifest code format
stampbot/manifest.py:151: ValueError
=========================== short test summary info ============================
FAILED tests/test_manifest.py::TestExchangeCode::test_exchange_code_success - ValueError: Invalid manifest code format
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 125 passed, 3 deselected in 0.90s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/AddNot, occurrence: 0
--- mutation diff ---
--- astampbot/manifest.py
+++ bstampbot/manifest.py
@@ -27,7 +27,7 @@
"""
parsed = urlparse(url)
- if not parsed.scheme or not parsed.netloc:
+ if not not parsed.scheme or not parsed.netloc:
raise ValueError(f"Invalid {name}: must be a complete URL")
# Allow http only for localhost (development)........................................................................ [ 34%]
......................................F
=================================== FAILURES ===================================
__________ TestUrlValidation.test_rejects_http_non_localhost_redirect __________
self = <tests.test_manifest.TestUrlValidation object at 0x7f032d1f6d50>
def test_rejects_http_non_localhost_redirect(self):
"""Test that HTTP redirect URLs for non-localhost are rejected."""
import pytest
with pytest.raises(ValueError, match="must use HTTPS"):
> create_manifest(
redirect_url="http://example.com/setup/callback",
)
tests/test_manifest.py:26:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
stampbot/manifest.py:85: in create_manifest
_validate_url(redirect_url, "redirect_url")
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
url = 'http://example.com/setup/callback', name = 'redirect_url'
def _validate_url(url: str, name: str) -> None:
"""Validate that a URL is well-formed and uses HTTPS (or localhost).
Args:
url: URL to validate
name: Name of the URL parameter for error messages
Raises:
ValueError: If URL is invalid or doesn't use HTTPS
"""
parsed = urlparse(url)
if not not parsed.scheme or not parsed.netloc:
> raise ValueError(f"Invalid {name}: must be a complete URL")
E ValueError: Invalid redirect_url: must be a complete URL
stampbot/manifest.py:31: ValueError
During handling of the above exception, another exception occurred:
self = <tests.test_manifest.TestUrlValidation object at 0x7f032d1f6d50>
def test_rejects_http_non_localhost_redirect(self):
"""Test that HTTP redirect URLs for non-localhost are rejected."""
import pytest
> with pytest.raises(ValueError, match="must use HTTPS"):
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E AssertionError: Regex pattern did not match.
E Expected regex: 'must use HTTPS'
E Actual message: 'Invalid redirect_url: must be a complete URL'
tests/test_manifest.py:25: AssertionError
=========================== short test summary info ============================
FAILED tests/test_manifest.py::TestUrlValidation::test_rejects_http_non_localhost_redirect - AssertionError: Regex pattern did not match.
Expected regex: 'must use HTTPS'
Actual message: 'Invalid redirect_url: must be a complete URL'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 110 passed, 3 deselected in 0.89s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/AddNot, occurrence: 1
--- mutation diff ---
--- astampbot/manifest.py
+++ bstampbot/manifest.py
@@ -31,7 +31,7 @@
raise ValueError(f"Invalid {name}: must be a complete URL")
# Allow http only for localhost (development)
- if parsed.scheme == "http":
+ if not parsed.scheme == "http":
if parsed.hostname not in ("localhost", "127.0.0.1", "::1"):
raise ValueError(f"Invalid {name}: must use HTTPS (HTTP only allowed for localhost)")
elif parsed.scheme != "https":........................................................................ [ 34%]
.......................................F
=================================== FAILURES ===================================
_________________ TestUrlValidation.test_allows_http_localhost _________________
self = <tests.test_manifest.TestUrlValidation object at 0x7fb5359c5450>
def test_allows_http_localhost(self):
"""Test that HTTP URLs for localhost are allowed."""
> manifest = create_manifest(
redirect_url="http://localhost:8000/setup/callback",
webhook_url="http://localhost:8000/webhook",
)
tests/test_manifest.py:32:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
stampbot/manifest.py:85: in create_manifest
_validate_url(redirect_url, "redirect_url")
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
url = 'http://localhost:8000/setup/callback', name = 'redirect_url'
def _validate_url(url: str, name: str) -> None:
"""Validate that a URL is well-formed and uses HTTPS (or localhost).
Args:
url: URL to validate
name: Name of the URL parameter for error messages
Raises:
ValueError: If URL is invalid or doesn't use HTTPS
"""
parsed = urlparse(url)
if not parsed.scheme or not parsed.netloc:
raise ValueError(f"Invalid {name}: must be a complete URL")
# Allow http only for localhost (development)
if not parsed.scheme == "http":
if parsed.hostname not in ("localhost", "127.0.0.1", "::1"):
raise ValueError(f"Invalid {name}: must use HTTPS (HTTP only allowed for localhost)")
elif parsed.scheme != "https":
> raise ValueError(f"Invalid {name}: must use HTTPS")
E ValueError: Invalid redirect_url: must use HTTPS
stampbot/manifest.py:38: ValueError
=========================== short test summary info ============================
FAILED tests/test_manifest.py::TestUrlValidation::test_allows_http_localhost - ValueError: Invalid redirect_url: must use HTTPS
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 111 passed, 3 deselected in 0.89s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/AddNot, occurrence: 2
--- mutation diff ---
--- astampbot/manifest.py
+++ bstampbot/manifest.py
@@ -32,7 +32,7 @@
# Allow http only for localhost (development)
if parsed.scheme == "http":
- if parsed.hostname not in ("localhost", "127.0.0.1", "::1"):
+ if not parsed.hostname not in ("localhost", "127.0.0.1", "::1"):
raise ValueError(f"Invalid {name}: must use HTTPS (HTTP only allowed for localhost)")
elif parsed.scheme != "https":
raise ValueError(f"Invalid {name}: must use HTTPS")........................................................................ [ 34%]
......................................F
=================================== FAILURES ===================================
__________ TestUrlValidation.test_rejects_http_non_localhost_redirect __________
self = <tests.test_manifest.TestUrlValidation object at 0x7f630b2f2d50>
def test_rejects_http_non_localhost_redirect(self):
"""Test that HTTP redirect URLs for non-localhost are rejected."""
import pytest
> with pytest.raises(ValueError, match="must use HTTPS"):
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E Failed: DID NOT RAISE <class 'ValueError'>
tests/test_manifest.py:25: Failed
=========================== short test summary info ============================
FAILED tests/test_manifest.py::TestUrlValidation::test_rejects_http_non_localhost_redirect - Failed: DID NOT RAISE <class 'ValueError'>
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 110 passed, 3 deselected in 0.89s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/AddNot, occurrence: 3
--- mutation diff ---
--- astampbot/manifest.py
+++ bstampbot/manifest.py
@@ -98,7 +98,7 @@
}
# Only include webhook URL if provided; GitHub will prompt for it otherwise
- if webhook_url:
+ if not webhook_url:
_validate_url(webhook_url, "webhook_url")
manifest["hook_attributes"] = {
"url": webhook_url,........................................................................ [ 34%]
.......................................F
=================================== FAILURES ===================================
_________________ TestUrlValidation.test_allows_http_localhost _________________
self = <tests.test_manifest.TestUrlValidation object at 0x7fa8dbf7d450>
def test_allows_http_localhost(self):
"""Test that HTTP URLs for localhost are allowed."""
manifest = create_manifest(
redirect_url="http://localhost:8000/setup/callback",
webhook_url="http://localhost:8000/webhook",
)
> assert manifest["hook_attributes"]["url"] == "http://localhost:8000/webhook"
^^^^^^^^^^^^^^^^^^^^^^^^^^^
E KeyError: 'hook_attributes'
tests/test_manifest.py:36: KeyError
=========================== short test summary info ============================
FAILED tests/test_manifest.py::TestUrlValidation::test_allows_http_localhost - KeyError: 'hook_attributes'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 111 passed, 3 deselected in 0.88s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/AddNot, occurrence: 4
--- mutation diff ---
--- astampbot/manifest.py
+++ bstampbot/manifest.py
@@ -147,7 +147,7 @@
ValueError: If code contains invalid characters
"""
# Validate code to prevent SSRF - GitHub codes are alphanumeric
- if not code or not code.isalnum():
+ if not not code or not code.isalnum():
raise ValueError("Invalid manifest code format")
url = GITHUB_MANIFEST_CONVERSION_URL.format(code=code)........................................................................ [ 34%]
.....................................................F
=================================== FAILURES ===================================
_________________ TestExchangeCode.test_exchange_code_success __________________
self = <tests.test_manifest.TestExchangeCode object at 0x7f15b148f390>
@pytest.mark.asyncio
async def test_exchange_code_success(self):
"""Test successful code exchange."""
import httpx
mock_response_data = {
"id": 12345,
"pem": "-----BEGIN RSA PRIVATE KEY-----\ntest\n-----END RSA PRIVATE KEY-----",
"webhook_secret": "test-secret",
"slug": "my-stampbot",
"name": "My Stampbot",
"client_id": "client123",
"client_secret": "secret456",
}
# Create a proper mock response with request attached
mock_request = httpx.Request(
"POST", "https://api.github.com/app-manifests/testcode123/conversions"
)
mock_response = httpx.Response(200, json=mock_response_data, request=mock_request)
with patch("httpx.AsyncClient.post", new_callable=AsyncMock) as mock_post:
mock_post.return_value = mock_response
> result = await exchange_code_for_credentials("testcode123")
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_manifest.py:204:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
code = 'testcode123'
async def exchange_code_for_credentials(code: str) -> dict[str, Any]:
"""Exchange the temporary code for app credentials.
After a user creates a GitHub App from a manifest, GitHub redirects
to the callback URL with a temporary code. This function exchanges
that code for the actual app credentials.
Args:
code: Temporary code from GitHub callback
Returns:
Dictionary with app credentials:
- id: App ID
- pem: Private key (PEM format)
- webhook_secret: Webhook secret
- slug: App slug
- name: App name
- client_id: OAuth client ID
- client_secret: OAuth client secret
Raises:
httpx.HTTPStatusError: If API call fails
ValueError: If code contains invalid characters
"""
# Validate code to prevent SSRF - GitHub codes are alphanumeric
if not not code or not code.isalnum():
> raise ValueError("Invalid manifest code format")
E ValueError: Invalid manifest code format
stampbot/manifest.py:151: ValueError
=========================== short test summary info ============================
FAILED tests/test_manifest.py::TestExchangeCode::test_exchange_code_success - ValueError: Invalid manifest code format
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 125 passed, 3 deselected in 0.89s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceTrueWithFalse, occurrence: 0
--- mutation diff ---
--- astampbot/manifest.py
+++ bstampbot/manifest.py
@@ -102,7 +102,7 @@
_validate_url(webhook_url, "webhook_url")
manifest["hook_attributes"] = {
"url": webhook_url,
- "active": True,
+ "active": False,
}
return manifest........................................................................ [ 34%]
............................................F
=================================== FAILURES ===================================
__________ TestCreateManifest.test_manifest_webhook_url_when_provided __________
self = <tests.test_manifest.TestCreateManifest object at 0x7f4ce95da060>
def test_manifest_webhook_url_when_provided(self):
"""Test manifest contains webhook URL when provided."""
manifest = create_manifest(
redirect_url="https://stampbot.example.com/setup/callback",
webhook_url="https://stampbot.example.com/webhook",
)
assert manifest["hook_attributes"]["url"] == "https://stampbot.example.com/webhook"
> assert manifest["hook_attributes"]["active"] is True
E assert False is True
tests/test_manifest.py:92: AssertionError
=========================== short test summary info ============================
FAILED tests/test_manifest.py::TestCreateManifest::test_manifest_webhook_url_when_provided - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 116 passed, 3 deselected in 0.92s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceFalseWithTrue, occurrence: 0
--- mutation diff ---
--- astampbot/manifest.py
+++ bstampbot/manifest.py
@@ -92,7 +92,7 @@
"description": app_description,
"url": base_url,
"redirect_url": redirect_url,
- "public": False,
+ "public": True,
"default_permissions": MANIFEST_PERMISSIONS,
"default_events": MANIFEST_EVENTS,
}........................................................................ [ 34%]
................................................F
=================================== FAILURES ===================================
_________________ TestCreateManifest.test_manifest_is_private __________________
self = <tests.test_manifest.TestCreateManifest object at 0x7fb31bf9e580>
def test_manifest_is_private(self):
"""Test manifest creates private app."""
manifest = create_manifest(
redirect_url="https://example.com/setup/callback",
)
> assert manifest["public"] is False
E assert True is False
tests/test_manifest.py:124: AssertionError
=========================== short test summary info ============================
FAILED tests/test_manifest.py::TestCreateManifest::test_manifest_is_private - assert True is False
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 120 passed, 3 deselected in 0.88s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceOrWithAnd, occurrence: 0
--- mutation diff ---
--- astampbot/manifest.py
+++ bstampbot/manifest.py
@@ -27,7 +27,7 @@
"""
parsed = urlparse(url)
- if not parsed.scheme or not parsed.netloc:
+ if not parsed.scheme and not parsed.netloc:
raise ValueError(f"Invalid {name}: must be a complete URL")
# Allow http only for localhost (development)........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceOrWithAnd, occurrence: 1
--- mutation diff ---
--- astampbot/manifest.py
+++ bstampbot/manifest.py
@@ -147,7 +147,7 @@
ValueError: If code contains invalid characters
"""
# Validate code to prevent SSRF - GitHub codes are alphanumeric
- if not code or not code.isalnum():
+ if not code and not code.isalnum():
raise ValueError("Invalid manifest code format")
url = GITHUB_MANIFEST_CONVERSION_URL.format(code=code)........................................................................ [ 34%]
........................................................F
=================================== FAILURES ===================================
________ TestExchangeCode.test_exchange_code_rejects_invalid_characters ________
self = <tests.test_manifest.TestExchangeCode object at 0x7f3b46ad6d70>
@pytest.mark.asyncio
async def test_exchange_code_rejects_invalid_characters(self):
"""Test that code with special characters raises ValueError to prevent SSRF."""
with pytest.raises(ValueError, match="Invalid manifest code format"):
> await exchange_code_for_credentials("../../../etc/passwd")
tests/test_manifest.py:243:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
stampbot/manifest.py:165: in exchange_code_for_credentials
response.raise_for_status()
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <Response [404 Not Found]>
def raise_for_status(self) -> Response:
"""
Raise the `HTTPStatusError` if one occurred.
"""
request = self._request
if request is None:
raise RuntimeError(
"Cannot call `raise_for_status` as the request "
"instance has not been set on this response."
)
if self.is_success:
return self
if self.has_redirect_location:
message = (
"{error_type} '{0.status_code} {0.reason_phrase}' for url '{0.url}'\n"
"Redirect location: '{0.headers[location]}'\n"
"For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/{0.status_code}"
)
else:
message = (
"{error_type} '{0.status_code} {0.reason_phrase}' for url '{0.url}'\n"
"For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/{0.status_code}"
)
status_class = self.status_code // 100
error_types = {
1: "Informational response",
3: "Redirect response",
4: "Client error",
5: "Server error",
}
error_type = error_types.get(status_class, "Invalid status code")
message = message.format(self, error_type=error_type)
> raise HTTPStatusError(message, request=request, response=self)
E httpx.HTTPStatusError: Client error '404 Not Found' for url 'https://api.github.com/etc/passwd/conversions'
E For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:829: HTTPStatusError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:24:34.519826Z[0m [[32m[1minfo [0m] [1mExchanging manifest code for credentials[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.manifest (INFO)>[0m [36m_name[0m=[35minfo[0m
[2m2026-05-16T19:24:34.661614Z[0m [[32m[1minfo [0m] [1mHTTP Request: POST https://api.github.com/etc/passwd/conversions "HTTP/1.1 404 Not Found"[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.manifest:manifest.py:155 {'event': 'Exchanging manifest code for credentials', 'level': 'info', 'timestamp': '2026-05-16T19:24:34.519826Z'}
INFO httpx:_client.py:1740 HTTP Request: POST https://api.github.com/etc/passwd/conversions "HTTP/1.1 404 Not Found"
=========================== short test summary info ============================
FAILED tests/test_manifest.py::TestExchangeCode::test_exchange_code_rejects_invalid_characters - httpx.HTTPStatusError: Client error '404 Not Found' for url 'https://api.github.com/etc/passwd/conversions'
For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 128 passed, 3 deselected in 1.13s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/NumberReplacer, occurrence: 0
--- mutation diff ---
--- astampbot/manifest.py
+++ bstampbot/manifest.py
@@ -41,7 +41,7 @@
# GitHub App Manifest URLs
GITHUB_MANIFEST_URL = "https://github.com/settings/apps/new"
GITHUB_MANIFEST_CONVERSION_URL = "https://api.github.com/app-manifests/{code}/conversions"
-GITHUB_MANIFEST_TIMEOUT_SECONDS = 10.0
+GITHUB_MANIFEST_TIMEOUT_SECONDS = 11.0
# Required permissions for stampbot
MANIFEST_PERMISSIONS = {........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.62s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/NumberReplacer, occurrence: 1
--- mutation diff ---
--- astampbot/manifest.py
+++ bstampbot/manifest.py
@@ -41,7 +41,7 @@
# GitHub App Manifest URLs
GITHUB_MANIFEST_URL = "https://github.com/settings/apps/new"
GITHUB_MANIFEST_CONVERSION_URL = "https://api.github.com/app-manifests/{code}/conversions"
-GITHUB_MANIFEST_TIMEOUT_SECONDS = 10.0
+GITHUB_MANIFEST_TIMEOUT_SECONDS = 9.0
# Required permissions for stampbot
MANIFEST_PERMISSIONS = {........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.60s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/NumberReplacer, occurrence: 2
--- mutation diff ---
--- astampbot/manifest.py
+++ bstampbot/manifest.py
@@ -85,7 +85,7 @@
_validate_url(redirect_url, "redirect_url")
# Extract base URL from redirect URL
- base_url = redirect_url.rsplit("/setup/callback", 1)[0]
+ base_url = redirect_url.rsplit("/setup/callback", 2)[0]
manifest: dict[str, Any] = {
"name": app_name,........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.60s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/NumberReplacer, occurrence: 3
--- mutation diff ---
--- astampbot/manifest.py
+++ bstampbot/manifest.py
@@ -85,7 +85,7 @@
_validate_url(redirect_url, "redirect_url")
# Extract base URL from redirect URL
- base_url = redirect_url.rsplit("/setup/callback", 1)[0]
+ base_url = redirect_url.rsplit("/setup/callback", 0)[0]
manifest: dict[str, Any] = {
"name": app_name,........................................................................ [ 34%]
...............................................F
=================================== FAILURES ===================================
__________________ TestCreateManifest.test_manifest_base_url ___________________
self = <tests.test_manifest.TestCreateManifest object at 0x7fec8a3e7df0>
def test_manifest_base_url(self):
"""Test manifest contains correct base URL derived from redirect URL."""
manifest = create_manifest(
redirect_url="https://stampbot.example.com/setup/callback",
)
> assert manifest["url"] == "https://stampbot.example.com"
E AssertionError: assert 'https://stam...etup/callback' == 'https://stampbot.example.com'
E
E - https://stampbot.example.com
E + https://stampbot.example.com/setup/callback
E ? +++++++++++++++
tests/test_manifest.py:116: AssertionError
=========================== short test summary info ============================
FAILED tests/test_manifest.py::TestCreateManifest::test_manifest_base_url - AssertionError: assert 'https://stam...etup/callback' == 'https://stampbot.example.com'
- https://stampbot.example.com
+ https://stampbot.example.com/setup/callback
? +++++++++++++++
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 119 passed, 3 deselected in 0.88s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/NumberReplacer, occurrence: 4
--- mutation diff ---
--- astampbot/manifest.py
+++ bstampbot/manifest.py
@@ -85,7 +85,7 @@
_validate_url(redirect_url, "redirect_url")
# Extract base URL from redirect URL
- base_url = redirect_url.rsplit("/setup/callback", 1)[0]
+ base_url = redirect_url.rsplit("/setup/callback", 1)[ 1]
manifest: dict[str, Any] = {
"name": app_name,........................................................................ [ 34%]
...............................................F
=================================== FAILURES ===================================
__________________ TestCreateManifest.test_manifest_base_url ___________________
self = <tests.test_manifest.TestCreateManifest object at 0x7f45e7ffbdf0>
def test_manifest_base_url(self):
"""Test manifest contains correct base URL derived from redirect URL."""
manifest = create_manifest(
redirect_url="https://stampbot.example.com/setup/callback",
)
> assert manifest["url"] == "https://stampbot.example.com"
E AssertionError: assert '' == 'https://stampbot.example.com'
E
E - https://stampbot.example.com
tests/test_manifest.py:116: AssertionError
=========================== short test summary info ============================
FAILED tests/test_manifest.py::TestCreateManifest::test_manifest_base_url - AssertionError: assert '' == 'https://stampbot.example.com'
- https://stampbot.example.com
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 119 passed, 3 deselected in 0.89s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/NumberReplacer, occurrence: 5
--- mutation diff ---
--- astampbot/manifest.py
+++ bstampbot/manifest.py
@@ -85,7 +85,7 @@
_validate_url(redirect_url, "redirect_url")
# Extract base URL from redirect URL
- base_url = redirect_url.rsplit("/setup/callback", 1)[0]
+ base_url = redirect_url.rsplit("/setup/callback", 1)[ -1]
manifest: dict[str, Any] = {
"name": app_name,........................................................................ [ 34%]
...............................................F
=================================== FAILURES ===================================
__________________ TestCreateManifest.test_manifest_base_url ___________________
self = <tests.test_manifest.TestCreateManifest object at 0x7f223997bdf0>
def test_manifest_base_url(self):
"""Test manifest contains correct base URL derived from redirect URL."""
manifest = create_manifest(
redirect_url="https://stampbot.example.com/setup/callback",
)
> assert manifest["url"] == "https://stampbot.example.com"
E AssertionError: assert '' == 'https://stampbot.example.com'
E
E - https://stampbot.example.com
tests/test_manifest.py:116: AssertionError
=========================== short test summary info ============================
FAILED tests/test_manifest.py::TestCreateManifest::test_manifest_base_url - AssertionError: assert '' == 'https://stampbot.example.com'
- https://stampbot.example.com
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 119 passed, 3 deselected in 0.88s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Add_Sub, occurrence: 0
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -71,7 +71,7 @@
return False
expected_signature = (
- "sha256=" + hmac.new(self.webhook_secret, payload, hashlib.sha256).hexdigest()
+ "sha256=" - hmac.new(self.webhook_secret, payload, hashlib.sha256).hexdigest()
)
return hmac.compare_digest(expected_signature, signature)........................................................................ [ 34%]
........................F
=================================== FAILURES ===================================
_______________________ test_request_with_content_length _______________________
test_client = <starlette.testclient.TestClient object at 0x7efe811b3770>
def test_request_with_content_length(test_client: TestClient):
"""Test that requests with Content-Length header are tracked."""
# POST with a body will have Content-Length set automatically
> response = test_client.post(
"/webhook",
content=b'{"test": "data"}',
headers={
"Content-Type": "application/json",
"X-GitHub-Event": "ping",
"X-Hub-Signature-256": "sha256=invalid",
},
)
tests/test_main.py:69:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:546: in post
return super().post(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1144: in post
return self.request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:445: in request
return super().request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:825: in request
return self.send(request, auth=auth, follow_redirects=follow_redirects)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:914: in send
response = self._send_handling_auth(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:942: in _send_handling_auth
response = self._send_handling_redirects(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:979: in _send_handling_redirects
response = self._send_single_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1014: in _send_single_request
response = transport.handle_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:348: in handle_request
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:345: in handle_request
portal.call(self.app, scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:334: in call
return cast(T_Retval, self.start_task_soon(func, *args).result())
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:443: in result
return self.__get_result()
^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:395: in __get_result
raise self._exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:259: in _call_func
retval = await retval_or_awaitable
^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1135: in __call__
await super().__call__(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:107: in __call__
await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:186: in __call__
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:164: in __call__
await self.app(scope, receive, _send)
stampbot/main.py:205: in __call__
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:191: in __call__
with recv_stream, send_stream, collapse_excgroups():
^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/contextlib.py:162: in __exit__
self.gen.throw(value)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_utils.py:85: in collapse_excgroups
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:193: in __call__
response = await self.dispatch_func(request, call_next)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:120: in metrics_middleware
response = await call_next(request)
^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:168: in call_next
raise app_exc from app_exc.__cause__ or app_exc.__context__
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:144: in coro
await self.app(scope, receive_or_disconnect, send_no_error)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/exceptions.py:63: in __call__
await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/middleware/asyncexitstack.py:18: in __call__
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:716: in __call__
await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:736: in app
await route.handle(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:290: in handle
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:115: in app
await wrap_app_handling_exceptions(app, request)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:101: in app
response = await f(request)
^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:355: in app
raw_response = await run_endpoint_function(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:243: in run_endpoint_function
return await dependant.call(**values)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:296: in webhook
if not webhook_handler.verify_signature(body, x_hub_signature_256):
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.webhook_handler.WebhookHandler object at 0x7efe811b6e40>
payload = b'{"test": "data"}', signature = 'sha256=invalid'
def verify_signature(self, payload: bytes, signature: str) -> bool:
"""Verify GitHub webhook signature.
Args:
payload: Request payload
signature: X-Hub-Signature-256 header value
Returns:
True if signature is valid
"""
if not signature:
return False
expected_signature = (
> "sha256=" - hmac.new(self.webhook_secret, payload, hashlib.sha256).hexdigest()
)
E TypeError: unsupported operand type(s) for -: 'str' and 'str'
stampbot/webhook_handler.py:74: TypeError
=========================== short test summary info ============================
FAILED tests/test_main.py::test_request_with_content_length - TypeError: unsupported operand type(s) for -: 'str' and 'str'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 96 passed, 3 deselected in 1.21s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Add_Sub, occurrence: 1
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -451,7 +451,7 @@
command = command_match.group(1).lower()
add_span_attributes(span, {"chatops.command": command})
- if command in repo_config.approve_commands + repo_config.unapprove_commands:
+ if command in repo_config.approve_commands - repo_config.unapprove_commands:
has_permission = await run_in_threadpool(
github_client.user_has_permission,
installation_id,........................................................................ [ 34%]
........................................................................ [ 68%]
.................................F
=================================== FAILURES ===================================
__________________________ test_issue_comment_approve __________________________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7f70f0f5dff0>
mock_github_client = <MagicMock name='github_client' id='140123056205712'>
@pytest.mark.asyncio
async def test_issue_comment_approve(webhook_handler, mock_github_client):
"""Test @stampbot approve command."""
payload = load_fixture("issue_comment_approve")
> result = await webhook_handler.handle_event("issue_comment", payload)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_webhook_handler.py:257:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
stampbot/webhook_handler.py:119: in handle_event
result = await self._handle_pr_comment(payload)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.webhook_handler.WebhookHandler object at 0x7f70f0f5dff0>
payload = {'action': 'created', 'comment': {'body': '@stampbot approve\n\nLGTM, approving this PR.', 'created_at': '2025-01-10T1...U='}, 'issue': {'assignee': None, 'assignees': [], 'body': 'This PR adds a new feature.', 'closed_at': None, ...}, ...}
async def _handle_pr_comment(self, payload: dict[str, Any]) -> dict[str, Any]:
"""Handle PR comment events for chatops.
Args:
payload: Event payload
Returns:
Response dictionary
"""
comment = payload.get("comment", {})
comment_body = comment.get("body", "")
# Security: limit comment length to prevent DoS
if len(comment_body) > MAX_COMMENT_LENGTH:
return {"status": "ignored", "message": "Comment too long"}
comment_body = comment_body.lower().strip()
# Check if bot is mentioned
if "@stampbot" not in comment_body:
return {"status": "ignored", "message": "Bot not mentioned"}
# Extract PR info
if "pull_request" in payload.get("issue", {}):
pr_url = payload["issue"]["pull_request"]["url"]
# Extract PR number from URL
pr_number = int(pr_url.split("/")[-1])
elif "pull_request" in payload:
pr_number = payload["pull_request"]["number"]
else:
return {"status": "error", "message": "Not a PR comment"}
repo = payload.get("repository", {})
repo_full_name = repo.get("full_name")
repo_default_branch = repo.get("default_branch") or "main"
repo_owner = repo.get("owner", {})
owner_login = repo_owner.get("login")
owner_type = repo_owner.get("type")
installation_id = payload.get("installation", {}).get("id")
commenter = comment.get("user", {}).get("login", "unknown")
with create_span(
"webhook.handle_chatops",
{
"github.repo": repo_full_name or "unknown",
"github.pr_number": pr_number,
"chatops.commenter": commenter,
},
) as span:
if not all([pr_number, repo_full_name, installation_id]):
logger.warning("Missing required fields in comment event")
add_span_attributes(span, {"chatops.result": "missing_fields"})
set_span_ok(span)
return {"status": "error", "message": "Missing required fields"}
# Get repository configuration
repo_config = await self._get_repo_config(
installation_id,
repo_full_name,
repo_default_branch,
owner_login,
owner_type,
)
if repo_config.config_error:
add_span_attributes(
span,
{
"chatops.result": "config_error",
"chatops.config_error": repo_config.config_error,
},
)
set_span_ok(span)
return {
"status": "error",
"message": "Invalid repository configuration",
}
if not repo_config.chatops_enabled:
add_span_attributes(span, {"chatops.result": "disabled"})
set_span_ok(span)
return {"status": "ignored", "message": "Chatops not enabled"}
# Parse command
command_match = re.search(r"@stampbot\s+(\w+)", comment_body)
if not command_match:
chatops_commands_total.labels(command="none", status="ignored").inc()
add_span_attributes(span, {"chatops.result": "no_command"})
set_span_ok(span)
return {"status": "ignored", "message": "No command found"}
command = command_match.group(1).lower()
add_span_attributes(span, {"chatops.command": command})
> if command in repo_config.approve_commands - repo_config.unapprove_commands:
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E TypeError: unsupported operand type(s) for -: 'BoxList' and 'BoxList'
stampbot/webhook_handler.py:454: TypeError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:43:56.440930Z[0m [[32m[1minfo [0m] [1mReceived issue_comment event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'issue_comment', 'action': 'created'}[0m
[2m2026-05-16T19:43:56.441480Z[0m [[32m[1minfo [0m] [1mNo stampbot.toml found in octocat/hello-world, using defaults[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'org_repo': None}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'level': 'info', 'timestamp': '2026-05-16T19:43:56.440930Z'}
INFO stampbot.webhook_handler:webhook_handler.py:593 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'level': 'info', 'timestamp': '2026-05-16T19:43:56.441480Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_issue_comment_approve - TypeError: unsupported operand type(s) for -: 'BoxList' and 'BoxList'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 177 passed, 3 deselected in 1.17s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Add_Mul, occurrence: 0
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -71,7 +71,7 @@
return False
expected_signature = (
- "sha256=" + hmac.new(self.webhook_secret, payload, hashlib.sha256).hexdigest()
+ "sha256=" * hmac.new(self.webhook_secret, payload, hashlib.sha256).hexdigest()
)
return hmac.compare_digest(expected_signature, signature)........................................................................ [ 34%]
........................F
=================================== FAILURES ===================================
_______________________ test_request_with_content_length _______________________
test_client = <starlette.testclient.TestClient object at 0x7ff6526b7770>
def test_request_with_content_length(test_client: TestClient):
"""Test that requests with Content-Length header are tracked."""
# POST with a body will have Content-Length set automatically
> response = test_client.post(
"/webhook",
content=b'{"test": "data"}',
headers={
"Content-Type": "application/json",
"X-GitHub-Event": "ping",
"X-Hub-Signature-256": "sha256=invalid",
},
)
tests/test_main.py:69:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:546: in post
return super().post(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1144: in post
return self.request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:445: in request
return super().request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:825: in request
return self.send(request, auth=auth, follow_redirects=follow_redirects)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:914: in send
response = self._send_handling_auth(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:942: in _send_handling_auth
response = self._send_handling_redirects(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:979: in _send_handling_redirects
response = self._send_single_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1014: in _send_single_request
response = transport.handle_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:348: in handle_request
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:345: in handle_request
portal.call(self.app, scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:334: in call
return cast(T_Retval, self.start_task_soon(func, *args).result())
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:443: in result
return self.__get_result()
^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:395: in __get_result
raise self._exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:259: in _call_func
retval = await retval_or_awaitable
^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1135: in __call__
await super().__call__(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:107: in __call__
await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:186: in __call__
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:164: in __call__
await self.app(scope, receive, _send)
stampbot/main.py:205: in __call__
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:191: in __call__
with recv_stream, send_stream, collapse_excgroups():
^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/contextlib.py:162: in __exit__
self.gen.throw(value)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_utils.py:85: in collapse_excgroups
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:193: in __call__
response = await self.dispatch_func(request, call_next)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:120: in metrics_middleware
response = await call_next(request)
^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:168: in call_next
raise app_exc from app_exc.__cause__ or app_exc.__context__
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:144: in coro
await self.app(scope, receive_or_disconnect, send_no_error)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/exceptions.py:63: in __call__
await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/middleware/asyncexitstack.py:18: in __call__
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:716: in __call__
await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:736: in app
await route.handle(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:290: in handle
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:115: in app
await wrap_app_handling_exceptions(app, request)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:101: in app
response = await f(request)
^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:355: in app
raw_response = await run_endpoint_function(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:243: in run_endpoint_function
return await dependant.call(**values)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:296: in webhook
if not webhook_handler.verify_signature(body, x_hub_signature_256):
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.webhook_handler.WebhookHandler object at 0x7ff6526bae40>
payload = b'{"test": "data"}', signature = 'sha256=invalid'
def verify_signature(self, payload: bytes, signature: str) -> bool:
"""Verify GitHub webhook signature.
Args:
payload: Request payload
signature: X-Hub-Signature-256 header value
Returns:
True if signature is valid
"""
if not signature:
return False
expected_signature = (
> "sha256=" * hmac.new(self.webhook_secret, payload, hashlib.sha256).hexdigest()
)
E TypeError: can't multiply sequence by non-int of type 'str'
stampbot/webhook_handler.py:74: TypeError
=========================== short test summary info ============================
FAILED tests/test_main.py::test_request_with_content_length - TypeError: can't multiply sequence by non-int of type 'str'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 96 passed, 3 deselected in 1.22s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Add_Mul, occurrence: 1
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -451,7 +451,7 @@
command = command_match.group(1).lower()
add_span_attributes(span, {"chatops.command": command})
- if command in repo_config.approve_commands + repo_config.unapprove_commands:
+ if command in repo_config.approve_commands * repo_config.unapprove_commands:
has_permission = await run_in_threadpool(
github_client.user_has_permission,
installation_id,........................................................................ [ 34%]
........................................................................ [ 68%]
.................................F
=================================== FAILURES ===================================
__________________________ test_issue_comment_approve __________________________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7f21ac75dff0>
mock_github_client = <MagicMock name='github_client' id='139782604550032'>
@pytest.mark.asyncio
async def test_issue_comment_approve(webhook_handler, mock_github_client):
"""Test @stampbot approve command."""
payload = load_fixture("issue_comment_approve")
> result = await webhook_handler.handle_event("issue_comment", payload)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_webhook_handler.py:257:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
stampbot/webhook_handler.py:119: in handle_event
result = await self._handle_pr_comment(payload)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.webhook_handler.WebhookHandler object at 0x7f21ac75dff0>
payload = {'action': 'created', 'comment': {'body': '@stampbot approve\n\nLGTM, approving this PR.', 'created_at': '2025-01-10T1...U='}, 'issue': {'assignee': None, 'assignees': [], 'body': 'This PR adds a new feature.', 'closed_at': None, ...}, ...}
async def _handle_pr_comment(self, payload: dict[str, Any]) -> dict[str, Any]:
"""Handle PR comment events for chatops.
Args:
payload: Event payload
Returns:
Response dictionary
"""
comment = payload.get("comment", {})
comment_body = comment.get("body", "")
# Security: limit comment length to prevent DoS
if len(comment_body) > MAX_COMMENT_LENGTH:
return {"status": "ignored", "message": "Comment too long"}
comment_body = comment_body.lower().strip()
# Check if bot is mentioned
if "@stampbot" not in comment_body:
return {"status": "ignored", "message": "Bot not mentioned"}
# Extract PR info
if "pull_request" in payload.get("issue", {}):
pr_url = payload["issue"]["pull_request"]["url"]
# Extract PR number from URL
pr_number = int(pr_url.split("/")[-1])
elif "pull_request" in payload:
pr_number = payload["pull_request"]["number"]
else:
return {"status": "error", "message": "Not a PR comment"}
repo = payload.get("repository", {})
repo_full_name = repo.get("full_name")
repo_default_branch = repo.get("default_branch") or "main"
repo_owner = repo.get("owner", {})
owner_login = repo_owner.get("login")
owner_type = repo_owner.get("type")
installation_id = payload.get("installation", {}).get("id")
commenter = comment.get("user", {}).get("login", "unknown")
with create_span(
"webhook.handle_chatops",
{
"github.repo": repo_full_name or "unknown",
"github.pr_number": pr_number,
"chatops.commenter": commenter,
},
) as span:
if not all([pr_number, repo_full_name, installation_id]):
logger.warning("Missing required fields in comment event")
add_span_attributes(span, {"chatops.result": "missing_fields"})
set_span_ok(span)
return {"status": "error", "message": "Missing required fields"}
# Get repository configuration
repo_config = await self._get_repo_config(
installation_id,
repo_full_name,
repo_default_branch,
owner_login,
owner_type,
)
if repo_config.config_error:
add_span_attributes(
span,
{
"chatops.result": "config_error",
"chatops.config_error": repo_config.config_error,
},
)
set_span_ok(span)
return {
"status": "error",
"message": "Invalid repository configuration",
}
if not repo_config.chatops_enabled:
add_span_attributes(span, {"chatops.result": "disabled"})
set_span_ok(span)
return {"status": "ignored", "message": "Chatops not enabled"}
# Parse command
command_match = re.search(r"@stampbot\s+(\w+)", comment_body)
if not command_match:
chatops_commands_total.labels(command="none", status="ignored").inc()
add_span_attributes(span, {"chatops.result": "no_command"})
set_span_ok(span)
return {"status": "ignored", "message": "No command found"}
command = command_match.group(1).lower()
add_span_attributes(span, {"chatops.command": command})
> if command in repo_config.approve_commands * repo_config.unapprove_commands:
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E TypeError: can't multiply sequence by non-int of type 'BoxList'
stampbot/webhook_handler.py:454: TypeError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:28:17.157365Z[0m [[32m[1minfo [0m] [1mReceived issue_comment event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'issue_comment', 'action': 'created'}[0m
[2m2026-05-16T19:28:17.157898Z[0m [[32m[1minfo [0m] [1mNo stampbot.toml found in octocat/hello-world, using defaults[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'org_repo': None}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'level': 'info', 'timestamp': '2026-05-16T19:28:17.157365Z'}
INFO stampbot.webhook_handler:webhook_handler.py:593 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'level': 'info', 'timestamp': '2026-05-16T19:28:17.157898Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_issue_comment_approve - TypeError: can't multiply sequence by non-int of type 'BoxList'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 177 passed, 3 deselected in 1.16s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Add_Div, occurrence: 0
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -71,7 +71,7 @@
return False
expected_signature = (
- "sha256=" + hmac.new(self.webhook_secret, payload, hashlib.sha256).hexdigest()
+ "sha256=" / hmac.new(self.webhook_secret, payload, hashlib.sha256).hexdigest()
)
return hmac.compare_digest(expected_signature, signature)........................................................................ [ 34%]
........................F
=================================== FAILURES ===================================
_______________________ test_request_with_content_length _______________________
test_client = <starlette.testclient.TestClient object at 0x7ffbdd983770>
def test_request_with_content_length(test_client: TestClient):
"""Test that requests with Content-Length header are tracked."""
# POST with a body will have Content-Length set automatically
> response = test_client.post(
"/webhook",
content=b'{"test": "data"}',
headers={
"Content-Type": "application/json",
"X-GitHub-Event": "ping",
"X-Hub-Signature-256": "sha256=invalid",
},
)
tests/test_main.py:69:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:546: in post
return super().post(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1144: in post
return self.request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:445: in request
return super().request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:825: in request
return self.send(request, auth=auth, follow_redirects=follow_redirects)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:914: in send
response = self._send_handling_auth(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:942: in _send_handling_auth
response = self._send_handling_redirects(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:979: in _send_handling_redirects
response = self._send_single_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1014: in _send_single_request
response = transport.handle_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:348: in handle_request
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:345: in handle_request
portal.call(self.app, scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:334: in call
return cast(T_Retval, self.start_task_soon(func, *args).result())
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:443: in result
return self.__get_result()
^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:395: in __get_result
raise self._exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:259: in _call_func
retval = await retval_or_awaitable
^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1135: in __call__
await super().__call__(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:107: in __call__
await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:186: in __call__
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:164: in __call__
await self.app(scope, receive, _send)
stampbot/main.py:205: in __call__
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:191: in __call__
with recv_stream, send_stream, collapse_excgroups():
^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/contextlib.py:162: in __exit__
self.gen.throw(value)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_utils.py:85: in collapse_excgroups
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:193: in __call__
response = await self.dispatch_func(request, call_next)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:120: in metrics_middleware
response = await call_next(request)
^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:168: in call_next
raise app_exc from app_exc.__cause__ or app_exc.__context__
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:144: in coro
await self.app(scope, receive_or_disconnect, send_no_error)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/exceptions.py:63: in __call__
await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/middleware/asyncexitstack.py:18: in __call__
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:716: in __call__
await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:736: in app
await route.handle(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:290: in handle
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:115: in app
await wrap_app_handling_exceptions(app, request)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:101: in app
response = await f(request)
^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:355: in app
raw_response = await run_endpoint_function(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:243: in run_endpoint_function
return await dependant.call(**values)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:296: in webhook
if not webhook_handler.verify_signature(body, x_hub_signature_256):
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.webhook_handler.WebhookHandler object at 0x7ffbdd986e40>
payload = b'{"test": "data"}', signature = 'sha256=invalid'
def verify_signature(self, payload: bytes, signature: str) -> bool:
"""Verify GitHub webhook signature.
Args:
payload: Request payload
signature: X-Hub-Signature-256 header value
Returns:
True if signature is valid
"""
if not signature:
return False
expected_signature = (
> "sha256=" / hmac.new(self.webhook_secret, payload, hashlib.sha256).hexdigest()
)
E TypeError: unsupported operand type(s) for /: 'str' and 'str'
stampbot/webhook_handler.py:74: TypeError
=========================== short test summary info ============================
FAILED tests/test_main.py::test_request_with_content_length - TypeError: unsupported operand type(s) for /: 'str' and 'str'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 96 passed, 3 deselected in 1.21s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Add_Div, occurrence: 1
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -451,7 +451,7 @@
command = command_match.group(1).lower()
add_span_attributes(span, {"chatops.command": command})
- if command in repo_config.approve_commands + repo_config.unapprove_commands:
+ if command in repo_config.approve_commands / repo_config.unapprove_commands:
has_permission = await run_in_threadpool(
github_client.user_has_permission,
installation_id,........................................................................ [ 34%]
........................................................................ [ 68%]
.................................F
=================================== FAILURES ===================================
__________________________ test_issue_comment_approve __________________________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7f0852267330>
mock_github_client = <MagicMock name='github_client' id='139673715208080'>
@pytest.mark.asyncio
async def test_issue_comment_approve(webhook_handler, mock_github_client):
"""Test @stampbot approve command."""
payload = load_fixture("issue_comment_approve")
> result = await webhook_handler.handle_event("issue_comment", payload)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_webhook_handler.py:257:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
stampbot/webhook_handler.py:119: in handle_event
result = await self._handle_pr_comment(payload)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.webhook_handler.WebhookHandler object at 0x7f0852267330>
payload = {'action': 'created', 'comment': {'body': '@stampbot approve\n\nLGTM, approving this PR.', 'created_at': '2025-01-10T1...U='}, 'issue': {'assignee': None, 'assignees': [], 'body': 'This PR adds a new feature.', 'closed_at': None, ...}, ...}
async def _handle_pr_comment(self, payload: dict[str, Any]) -> dict[str, Any]:
"""Handle PR comment events for chatops.
Args:
payload: Event payload
Returns:
Response dictionary
"""
comment = payload.get("comment", {})
comment_body = comment.get("body", "")
# Security: limit comment length to prevent DoS
if len(comment_body) > MAX_COMMENT_LENGTH:
return {"status": "ignored", "message": "Comment too long"}
comment_body = comment_body.lower().strip()
# Check if bot is mentioned
if "@stampbot" not in comment_body:
return {"status": "ignored", "message": "Bot not mentioned"}
# Extract PR info
if "pull_request" in payload.get("issue", {}):
pr_url = payload["issue"]["pull_request"]["url"]
# Extract PR number from URL
pr_number = int(pr_url.split("/")[-1])
elif "pull_request" in payload:
pr_number = payload["pull_request"]["number"]
else:
return {"status": "error", "message": "Not a PR comment"}
repo = payload.get("repository", {})
repo_full_name = repo.get("full_name")
repo_default_branch = repo.get("default_branch") or "main"
repo_owner = repo.get("owner", {})
owner_login = repo_owner.get("login")
owner_type = repo_owner.get("type")
installation_id = payload.get("installation", {}).get("id")
commenter = comment.get("user", {}).get("login", "unknown")
with create_span(
"webhook.handle_chatops",
{
"github.repo": repo_full_name or "unknown",
"github.pr_number": pr_number,
"chatops.commenter": commenter,
},
) as span:
if not all([pr_number, repo_full_name, installation_id]):
logger.warning("Missing required fields in comment event")
add_span_attributes(span, {"chatops.result": "missing_fields"})
set_span_ok(span)
return {"status": "error", "message": "Missing required fields"}
# Get repository configuration
repo_config = await self._get_repo_config(
installation_id,
repo_full_name,
repo_default_branch,
owner_login,
owner_type,
)
if repo_config.config_error:
add_span_attributes(
span,
{
"chatops.result": "config_error",
"chatops.config_error": repo_config.config_error,
},
)
set_span_ok(span)
return {
"status": "error",
"message": "Invalid repository configuration",
}
if not repo_config.chatops_enabled:
add_span_attributes(span, {"chatops.result": "disabled"})
set_span_ok(span)
return {"status": "ignored", "message": "Chatops not enabled"}
# Parse command
command_match = re.search(r"@stampbot\s+(\w+)", comment_body)
if not command_match:
chatops_commands_total.labels(command="none", status="ignored").inc()
add_span_attributes(span, {"chatops.result": "no_command"})
set_span_ok(span)
return {"status": "ignored", "message": "No command found"}
command = command_match.group(1).lower()
add_span_attributes(span, {"chatops.command": command})
> if command in repo_config.approve_commands / repo_config.unapprove_commands:
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E TypeError: unsupported operand type(s) for /: 'BoxList' and 'BoxList'
stampbot/webhook_handler.py:454: TypeError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:56:21.713947Z[0m [[32m[1minfo [0m] [1mReceived issue_comment event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'issue_comment', 'action': 'created'}[0m
[2m2026-05-16T19:56:21.714503Z[0m [[32m[1minfo [0m] [1mNo stampbot.toml found in octocat/hello-world, using defaults[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'org_repo': None}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'level': 'info', 'timestamp': '2026-05-16T19:56:21.713947Z'}
INFO stampbot.webhook_handler:webhook_handler.py:593 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'level': 'info', 'timestamp': '2026-05-16T19:56:21.714503Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_issue_comment_approve - TypeError: unsupported operand type(s) for /: 'BoxList' and 'BoxList'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 177 passed, 3 deselected in 1.16s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Add_FloorDiv, occurrence: 0
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -71,7 +71,7 @@
return False
expected_signature = (
- "sha256=" + hmac.new(self.webhook_secret, payload, hashlib.sha256).hexdigest()
+ "sha256=" // hmac.new(self.webhook_secret, payload, hashlib.sha256).hexdigest()
)
return hmac.compare_digest(expected_signature, signature)........................................................................ [ 34%]
........................F
=================================== FAILURES ===================================
_______________________ test_request_with_content_length _______________________
test_client = <starlette.testclient.TestClient object at 0x7f699d097770>
def test_request_with_content_length(test_client: TestClient):
"""Test that requests with Content-Length header are tracked."""
# POST with a body will have Content-Length set automatically
> response = test_client.post(
"/webhook",
content=b'{"test": "data"}',
headers={
"Content-Type": "application/json",
"X-GitHub-Event": "ping",
"X-Hub-Signature-256": "sha256=invalid",
},
)
tests/test_main.py:69:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:546: in post
return super().post(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1144: in post
return self.request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:445: in request
return super().request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:825: in request
return self.send(request, auth=auth, follow_redirects=follow_redirects)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:914: in send
response = self._send_handling_auth(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:942: in _send_handling_auth
response = self._send_handling_redirects(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:979: in _send_handling_redirects
response = self._send_single_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1014: in _send_single_request
response = transport.handle_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:348: in handle_request
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:345: in handle_request
portal.call(self.app, scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:334: in call
return cast(T_Retval, self.start_task_soon(func, *args).result())
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:443: in result
return self.__get_result()
^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:395: in __get_result
raise self._exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:259: in _call_func
retval = await retval_or_awaitable
^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1135: in __call__
await super().__call__(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:107: in __call__
await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:186: in __call__
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:164: in __call__
await self.app(scope, receive, _send)
stampbot/main.py:205: in __call__
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:191: in __call__
with recv_stream, send_stream, collapse_excgroups():
^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/contextlib.py:162: in __exit__
self.gen.throw(value)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_utils.py:85: in collapse_excgroups
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:193: in __call__
response = await self.dispatch_func(request, call_next)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:120: in metrics_middleware
response = await call_next(request)
^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:168: in call_next
raise app_exc from app_exc.__cause__ or app_exc.__context__
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:144: in coro
await self.app(scope, receive_or_disconnect, send_no_error)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/exceptions.py:63: in __call__
await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/middleware/asyncexitstack.py:18: in __call__
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:716: in __call__
await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:736: in app
await route.handle(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:290: in handle
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:115: in app
await wrap_app_handling_exceptions(app, request)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:101: in app
response = await f(request)
^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:355: in app
raw_response = await run_endpoint_function(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:243: in run_endpoint_function
return await dependant.call(**values)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:296: in webhook
if not webhook_handler.verify_signature(body, x_hub_signature_256):
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.webhook_handler.WebhookHandler object at 0x7f699d09ae40>
payload = b'{"test": "data"}', signature = 'sha256=invalid'
def verify_signature(self, payload: bytes, signature: str) -> bool:
"""Verify GitHub webhook signature.
Args:
payload: Request payload
signature: X-Hub-Signature-256 header value
Returns:
True if signature is valid
"""
if not signature:
return False
expected_signature = (
> "sha256=" // hmac.new(self.webhook_secret, payload, hashlib.sha256).hexdigest()
)
E TypeError: unsupported operand type(s) for //: 'str' and 'str'
stampbot/webhook_handler.py:74: TypeError
=========================== short test summary info ============================
FAILED tests/test_main.py::test_request_with_content_length - TypeError: unsupported operand type(s) for //: 'str' and 'str'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 96 passed, 3 deselected in 1.17s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Add_FloorDiv, occurrence: 1
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -451,7 +451,7 @@
command = command_match.group(1).lower()
add_span_attributes(span, {"chatops.command": command})
- if command in repo_config.approve_commands + repo_config.unapprove_commands:
+ if command in repo_config.approve_commands // repo_config.unapprove_commands:
has_permission = await run_in_threadpool(
github_client.user_has_permission,
installation_id,........................................................................ [ 34%]
........................................................................ [ 68%]
.................................F
=================================== FAILURES ===================================
__________________________ test_issue_comment_approve __________________________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7f87a9267330>
mock_github_client = <MagicMock name='github_client' id='140220635672464'>
@pytest.mark.asyncio
async def test_issue_comment_approve(webhook_handler, mock_github_client):
"""Test @stampbot approve command."""
payload = load_fixture("issue_comment_approve")
> result = await webhook_handler.handle_event("issue_comment", payload)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_webhook_handler.py:257:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
stampbot/webhook_handler.py:119: in handle_event
result = await self._handle_pr_comment(payload)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.webhook_handler.WebhookHandler object at 0x7f87a9267330>
payload = {'action': 'created', 'comment': {'body': '@stampbot approve\n\nLGTM, approving this PR.', 'created_at': '2025-01-10T1...U='}, 'issue': {'assignee': None, 'assignees': [], 'body': 'This PR adds a new feature.', 'closed_at': None, ...}, ...}
async def _handle_pr_comment(self, payload: dict[str, Any]) -> dict[str, Any]:
"""Handle PR comment events for chatops.
Args:
payload: Event payload
Returns:
Response dictionary
"""
comment = payload.get("comment", {})
comment_body = comment.get("body", "")
# Security: limit comment length to prevent DoS
if len(comment_body) > MAX_COMMENT_LENGTH:
return {"status": "ignored", "message": "Comment too long"}
comment_body = comment_body.lower().strip()
# Check if bot is mentioned
if "@stampbot" not in comment_body:
return {"status": "ignored", "message": "Bot not mentioned"}
# Extract PR info
if "pull_request" in payload.get("issue", {}):
pr_url = payload["issue"]["pull_request"]["url"]
# Extract PR number from URL
pr_number = int(pr_url.split("/")[-1])
elif "pull_request" in payload:
pr_number = payload["pull_request"]["number"]
else:
return {"status": "error", "message": "Not a PR comment"}
repo = payload.get("repository", {})
repo_full_name = repo.get("full_name")
repo_default_branch = repo.get("default_branch") or "main"
repo_owner = repo.get("owner", {})
owner_login = repo_owner.get("login")
owner_type = repo_owner.get("type")
installation_id = payload.get("installation", {}).get("id")
commenter = comment.get("user", {}).get("login", "unknown")
with create_span(
"webhook.handle_chatops",
{
"github.repo": repo_full_name or "unknown",
"github.pr_number": pr_number,
"chatops.commenter": commenter,
},
) as span:
if not all([pr_number, repo_full_name, installation_id]):
logger.warning("Missing required fields in comment event")
add_span_attributes(span, {"chatops.result": "missing_fields"})
set_span_ok(span)
return {"status": "error", "message": "Missing required fields"}
# Get repository configuration
repo_config = await self._get_repo_config(
installation_id,
repo_full_name,
repo_default_branch,
owner_login,
owner_type,
)
if repo_config.config_error:
add_span_attributes(
span,
{
"chatops.result": "config_error",
"chatops.config_error": repo_config.config_error,
},
)
set_span_ok(span)
return {
"status": "error",
"message": "Invalid repository configuration",
}
if not repo_config.chatops_enabled:
add_span_attributes(span, {"chatops.result": "disabled"})
set_span_ok(span)
return {"status": "ignored", "message": "Chatops not enabled"}
# Parse command
command_match = re.search(r"@stampbot\s+(\w+)", comment_body)
if not command_match:
chatops_commands_total.labels(command="none", status="ignored").inc()
add_span_attributes(span, {"chatops.result": "no_command"})
set_span_ok(span)
return {"status": "ignored", "message": "No command found"}
command = command_match.group(1).lower()
add_span_attributes(span, {"chatops.command": command})
> if command in repo_config.approve_commands // repo_config.unapprove_commands:
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E TypeError: unsupported operand type(s) for //: 'BoxList' and 'BoxList'
stampbot/webhook_handler.py:454: TypeError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:40:40.335046Z[0m [[32m[1minfo [0m] [1mReceived issue_comment event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'issue_comment', 'action': 'created'}[0m
[2m2026-05-16T19:40:40.335628Z[0m [[32m[1minfo [0m] [1mNo stampbot.toml found in octocat/hello-world, using defaults[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'org_repo': None}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'level': 'info', 'timestamp': '2026-05-16T19:40:40.335046Z'}
INFO stampbot.webhook_handler:webhook_handler.py:593 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'level': 'info', 'timestamp': '2026-05-16T19:40:40.335628Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_issue_comment_approve - TypeError: unsupported operand type(s) for //: 'BoxList' and 'BoxList'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 177 passed, 3 deselected in 1.17s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Add_Mod, occurrence: 0
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -71,7 +71,7 @@
return False
expected_signature = (
- "sha256=" + hmac.new(self.webhook_secret, payload, hashlib.sha256).hexdigest()
+ "sha256=" % hmac.new(self.webhook_secret, payload, hashlib.sha256).hexdigest()
)
return hmac.compare_digest(expected_signature, signature)........................................................................ [ 34%]
........................F
=================================== FAILURES ===================================
_______________________ test_request_with_content_length _______________________
test_client = <starlette.testclient.TestClient object at 0x7fb07719b770>
def test_request_with_content_length(test_client: TestClient):
"""Test that requests with Content-Length header are tracked."""
# POST with a body will have Content-Length set automatically
> response = test_client.post(
"/webhook",
content=b'{"test": "data"}',
headers={
"Content-Type": "application/json",
"X-GitHub-Event": "ping",
"X-Hub-Signature-256": "sha256=invalid",
},
)
tests/test_main.py:69:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:546: in post
return super().post(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1144: in post
return self.request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:445: in request
return super().request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:825: in request
return self.send(request, auth=auth, follow_redirects=follow_redirects)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:914: in send
response = self._send_handling_auth(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:942: in _send_handling_auth
response = self._send_handling_redirects(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:979: in _send_handling_redirects
response = self._send_single_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1014: in _send_single_request
response = transport.handle_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:348: in handle_request
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:345: in handle_request
portal.call(self.app, scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:334: in call
return cast(T_Retval, self.start_task_soon(func, *args).result())
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:443: in result
return self.__get_result()
^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:395: in __get_result
raise self._exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:259: in _call_func
retval = await retval_or_awaitable
^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1135: in __call__
await super().__call__(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:107: in __call__
await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:186: in __call__
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:164: in __call__
await self.app(scope, receive, _send)
stampbot/main.py:205: in __call__
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:191: in __call__
with recv_stream, send_stream, collapse_excgroups():
^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/contextlib.py:162: in __exit__
self.gen.throw(value)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_utils.py:85: in collapse_excgroups
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:193: in __call__
response = await self.dispatch_func(request, call_next)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:120: in metrics_middleware
response = await call_next(request)
^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:168: in call_next
raise app_exc from app_exc.__cause__ or app_exc.__context__
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:144: in coro
await self.app(scope, receive_or_disconnect, send_no_error)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/exceptions.py:63: in __call__
await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/middleware/asyncexitstack.py:18: in __call__
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:716: in __call__
await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:736: in app
await route.handle(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:290: in handle
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:115: in app
await wrap_app_handling_exceptions(app, request)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:101: in app
response = await f(request)
^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:355: in app
raw_response = await run_endpoint_function(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:243: in run_endpoint_function
return await dependant.call(**values)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:296: in webhook
if not webhook_handler.verify_signature(body, x_hub_signature_256):
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.webhook_handler.WebhookHandler object at 0x7fb0771a2e40>
payload = b'{"test": "data"}', signature = 'sha256=invalid'
def verify_signature(self, payload: bytes, signature: str) -> bool:
"""Verify GitHub webhook signature.
Args:
payload: Request payload
signature: X-Hub-Signature-256 header value
Returns:
True if signature is valid
"""
if not signature:
return False
expected_signature = (
> "sha256=" % hmac.new(self.webhook_secret, payload, hashlib.sha256).hexdigest()
)
E TypeError: not all arguments converted during string formatting
stampbot/webhook_handler.py:74: TypeError
=========================== short test summary info ============================
FAILED tests/test_main.py::test_request_with_content_length - TypeError: not all arguments converted during string formatting
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 96 passed, 3 deselected in 1.20s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Add_Mod, occurrence: 1
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -451,7 +451,7 @@
command = command_match.group(1).lower()
add_span_attributes(span, {"chatops.command": command})
- if command in repo_config.approve_commands + repo_config.unapprove_commands:
+ if command in repo_config.approve_commands % repo_config.unapprove_commands:
has_permission = await run_in_threadpool(
github_client.user_has_permission,
installation_id,........................................................................ [ 34%]
........................................................................ [ 68%]
.................................F
=================================== FAILURES ===================================
__________________________ test_issue_comment_approve __________________________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7f0d4a15b330>
mock_github_client = <MagicMock name='github_client' id='139695054729104'>
@pytest.mark.asyncio
async def test_issue_comment_approve(webhook_handler, mock_github_client):
"""Test @stampbot approve command."""
payload = load_fixture("issue_comment_approve")
> result = await webhook_handler.handle_event("issue_comment", payload)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_webhook_handler.py:257:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
stampbot/webhook_handler.py:119: in handle_event
result = await self._handle_pr_comment(payload)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.webhook_handler.WebhookHandler object at 0x7f0d4a15b330>
payload = {'action': 'created', 'comment': {'body': '@stampbot approve\n\nLGTM, approving this PR.', 'created_at': '2025-01-10T1...U='}, 'issue': {'assignee': None, 'assignees': [], 'body': 'This PR adds a new feature.', 'closed_at': None, ...}, ...}
async def _handle_pr_comment(self, payload: dict[str, Any]) -> dict[str, Any]:
"""Handle PR comment events for chatops.
Args:
payload: Event payload
Returns:
Response dictionary
"""
comment = payload.get("comment", {})
comment_body = comment.get("body", "")
# Security: limit comment length to prevent DoS
if len(comment_body) > MAX_COMMENT_LENGTH:
return {"status": "ignored", "message": "Comment too long"}
comment_body = comment_body.lower().strip()
# Check if bot is mentioned
if "@stampbot" not in comment_body:
return {"status": "ignored", "message": "Bot not mentioned"}
# Extract PR info
if "pull_request" in payload.get("issue", {}):
pr_url = payload["issue"]["pull_request"]["url"]
# Extract PR number from URL
pr_number = int(pr_url.split("/")[-1])
elif "pull_request" in payload:
pr_number = payload["pull_request"]["number"]
else:
return {"status": "error", "message": "Not a PR comment"}
repo = payload.get("repository", {})
repo_full_name = repo.get("full_name")
repo_default_branch = repo.get("default_branch") or "main"
repo_owner = repo.get("owner", {})
owner_login = repo_owner.get("login")
owner_type = repo_owner.get("type")
installation_id = payload.get("installation", {}).get("id")
commenter = comment.get("user", {}).get("login", "unknown")
with create_span(
"webhook.handle_chatops",
{
"github.repo": repo_full_name or "unknown",
"github.pr_number": pr_number,
"chatops.commenter": commenter,
},
) as span:
if not all([pr_number, repo_full_name, installation_id]):
logger.warning("Missing required fields in comment event")
add_span_attributes(span, {"chatops.result": "missing_fields"})
set_span_ok(span)
return {"status": "error", "message": "Missing required fields"}
# Get repository configuration
repo_config = await self._get_repo_config(
installation_id,
repo_full_name,
repo_default_branch,
owner_login,
owner_type,
)
if repo_config.config_error:
add_span_attributes(
span,
{
"chatops.result": "config_error",
"chatops.config_error": repo_config.config_error,
},
)
set_span_ok(span)
return {
"status": "error",
"message": "Invalid repository configuration",
}
if not repo_config.chatops_enabled:
add_span_attributes(span, {"chatops.result": "disabled"})
set_span_ok(span)
return {"status": "ignored", "message": "Chatops not enabled"}
# Parse command
command_match = re.search(r"@stampbot\s+(\w+)", comment_body)
if not command_match:
chatops_commands_total.labels(command="none", status="ignored").inc()
add_span_attributes(span, {"chatops.result": "no_command"})
set_span_ok(span)
return {"status": "ignored", "message": "No command found"}
command = command_match.group(1).lower()
add_span_attributes(span, {"chatops.command": command})
> if command in repo_config.approve_commands % repo_config.unapprove_commands:
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E TypeError: unsupported operand type(s) for %: 'BoxList' and 'BoxList'
stampbot/webhook_handler.py:454: TypeError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:42:53.263660Z[0m [[32m[1minfo [0m] [1mReceived issue_comment event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'issue_comment', 'action': 'created'}[0m
[2m2026-05-16T19:42:53.264184Z[0m [[32m[1minfo [0m] [1mNo stampbot.toml found in octocat/hello-world, using defaults[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'org_repo': None}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'level': 'info', 'timestamp': '2026-05-16T19:42:53.263660Z'}
INFO stampbot.webhook_handler:webhook_handler.py:593 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'level': 'info', 'timestamp': '2026-05-16T19:42:53.264184Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_issue_comment_approve - TypeError: unsupported operand type(s) for %: 'BoxList' and 'BoxList'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 177 passed, 3 deselected in 1.17s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Add_Pow, occurrence: 0
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -71,7 +71,7 @@
return False
expected_signature = (
- "sha256=" + hmac.new(self.webhook_secret, payload, hashlib.sha256).hexdigest()
+ "sha256=" ** hmac.new(self.webhook_secret, payload, hashlib.sha256).hexdigest()
)
return hmac.compare_digest(expected_signature, signature)........................................................................ [ 34%]
........................F
=================================== FAILURES ===================================
_______________________ test_request_with_content_length _______________________
test_client = <starlette.testclient.TestClient object at 0x7f069429b770>
def test_request_with_content_length(test_client: TestClient):
"""Test that requests with Content-Length header are tracked."""
# POST with a body will have Content-Length set automatically
> response = test_client.post(
"/webhook",
content=b'{"test": "data"}',
headers={
"Content-Type": "application/json",
"X-GitHub-Event": "ping",
"X-Hub-Signature-256": "sha256=invalid",
},
)
tests/test_main.py:69:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:546: in post
return super().post(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1144: in post
return self.request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:445: in request
return super().request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:825: in request
return self.send(request, auth=auth, follow_redirects=follow_redirects)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:914: in send
response = self._send_handling_auth(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:942: in _send_handling_auth
response = self._send_handling_redirects(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:979: in _send_handling_redirects
response = self._send_single_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1014: in _send_single_request
response = transport.handle_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:348: in handle_request
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:345: in handle_request
portal.call(self.app, scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:334: in call
return cast(T_Retval, self.start_task_soon(func, *args).result())
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:443: in result
return self.__get_result()
^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:395: in __get_result
raise self._exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:259: in _call_func
retval = await retval_or_awaitable
^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1135: in __call__
await super().__call__(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:107: in __call__
await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:186: in __call__
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:164: in __call__
await self.app(scope, receive, _send)
stampbot/main.py:205: in __call__
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:191: in __call__
with recv_stream, send_stream, collapse_excgroups():
^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/contextlib.py:162: in __exit__
self.gen.throw(value)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_utils.py:85: in collapse_excgroups
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:193: in __call__
response = await self.dispatch_func(request, call_next)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:120: in metrics_middleware
response = await call_next(request)
^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:168: in call_next
raise app_exc from app_exc.__cause__ or app_exc.__context__
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:144: in coro
await self.app(scope, receive_or_disconnect, send_no_error)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/exceptions.py:63: in __call__
await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/middleware/asyncexitstack.py:18: in __call__
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:716: in __call__
await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:736: in app
await route.handle(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:290: in handle
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:115: in app
await wrap_app_handling_exceptions(app, request)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:101: in app
response = await f(request)
^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:355: in app
raw_response = await run_endpoint_function(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:243: in run_endpoint_function
return await dependant.call(**values)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:296: in webhook
if not webhook_handler.verify_signature(body, x_hub_signature_256):
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.webhook_handler.WebhookHandler object at 0x7f069429ee40>
payload = b'{"test": "data"}', signature = 'sha256=invalid'
def verify_signature(self, payload: bytes, signature: str) -> bool:
"""Verify GitHub webhook signature.
Args:
payload: Request payload
signature: X-Hub-Signature-256 header value
Returns:
True if signature is valid
"""
if not signature:
return False
expected_signature = (
> "sha256=" ** hmac.new(self.webhook_secret, payload, hashlib.sha256).hexdigest()
)
E TypeError: unsupported operand type(s) for ** or pow(): 'str' and 'str'
stampbot/webhook_handler.py:74: TypeError
=========================== short test summary info ============================
FAILED tests/test_main.py::test_request_with_content_length - TypeError: unsupported operand type(s) for ** or pow(): 'str' and 'str'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 96 passed, 3 deselected in 1.18s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Add_Pow, occurrence: 1
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -451,7 +451,7 @@
command = command_match.group(1).lower()
add_span_attributes(span, {"chatops.command": command})
- if command in repo_config.approve_commands + repo_config.unapprove_commands:
+ if command in repo_config.approve_commands ** repo_config.unapprove_commands:
has_permission = await run_in_threadpool(
github_client.user_has_permission,
installation_id,........................................................................ [ 34%]
........................................................................ [ 68%]
.................................F
=================================== FAILURES ===================================
__________________________ test_issue_comment_approve __________________________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7fe590c6dff0>
mock_github_client = <MagicMock name='github_client' id='140623953686416'>
@pytest.mark.asyncio
async def test_issue_comment_approve(webhook_handler, mock_github_client):
"""Test @stampbot approve command."""
payload = load_fixture("issue_comment_approve")
> result = await webhook_handler.handle_event("issue_comment", payload)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_webhook_handler.py:257:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
stampbot/webhook_handler.py:119: in handle_event
result = await self._handle_pr_comment(payload)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.webhook_handler.WebhookHandler object at 0x7fe590c6dff0>
payload = {'action': 'created', 'comment': {'body': '@stampbot approve\n\nLGTM, approving this PR.', 'created_at': '2025-01-10T1...U='}, 'issue': {'assignee': None, 'assignees': [], 'body': 'This PR adds a new feature.', 'closed_at': None, ...}, ...}
async def _handle_pr_comment(self, payload: dict[str, Any]) -> dict[str, Any]:
"""Handle PR comment events for chatops.
Args:
payload: Event payload
Returns:
Response dictionary
"""
comment = payload.get("comment", {})
comment_body = comment.get("body", "")
# Security: limit comment length to prevent DoS
if len(comment_body) > MAX_COMMENT_LENGTH:
return {"status": "ignored", "message": "Comment too long"}
comment_body = comment_body.lower().strip()
# Check if bot is mentioned
if "@stampbot" not in comment_body:
return {"status": "ignored", "message": "Bot not mentioned"}
# Extract PR info
if "pull_request" in payload.get("issue", {}):
pr_url = payload["issue"]["pull_request"]["url"]
# Extract PR number from URL
pr_number = int(pr_url.split("/")[-1])
elif "pull_request" in payload:
pr_number = payload["pull_request"]["number"]
else:
return {"status": "error", "message": "Not a PR comment"}
repo = payload.get("repository", {})
repo_full_name = repo.get("full_name")
repo_default_branch = repo.get("default_branch") or "main"
repo_owner = repo.get("owner", {})
owner_login = repo_owner.get("login")
owner_type = repo_owner.get("type")
installation_id = payload.get("installation", {}).get("id")
commenter = comment.get("user", {}).get("login", "unknown")
with create_span(
"webhook.handle_chatops",
{
"github.repo": repo_full_name or "unknown",
"github.pr_number": pr_number,
"chatops.commenter": commenter,
},
) as span:
if not all([pr_number, repo_full_name, installation_id]):
logger.warning("Missing required fields in comment event")
add_span_attributes(span, {"chatops.result": "missing_fields"})
set_span_ok(span)
return {"status": "error", "message": "Missing required fields"}
# Get repository configuration
repo_config = await self._get_repo_config(
installation_id,
repo_full_name,
repo_default_branch,
owner_login,
owner_type,
)
if repo_config.config_error:
add_span_attributes(
span,
{
"chatops.result": "config_error",
"chatops.config_error": repo_config.config_error,
},
)
set_span_ok(span)
return {
"status": "error",
"message": "Invalid repository configuration",
}
if not repo_config.chatops_enabled:
add_span_attributes(span, {"chatops.result": "disabled"})
set_span_ok(span)
return {"status": "ignored", "message": "Chatops not enabled"}
# Parse command
command_match = re.search(r"@stampbot\s+(\w+)", comment_body)
if not command_match:
chatops_commands_total.labels(command="none", status="ignored").inc()
add_span_attributes(span, {"chatops.result": "no_command"})
set_span_ok(span)
return {"status": "ignored", "message": "No command found"}
command = command_match.group(1).lower()
add_span_attributes(span, {"chatops.command": command})
> if command in repo_config.approve_commands ** repo_config.unapprove_commands:
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E TypeError: unsupported operand type(s) for ** or pow(): 'BoxList' and 'BoxList'
stampbot/webhook_handler.py:454: TypeError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T20:08:37.626863Z[0m [[32m[1minfo [0m] [1mReceived issue_comment event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'issue_comment', 'action': 'created'}[0m
[2m2026-05-16T20:08:37.627418Z[0m [[32m[1minfo [0m] [1mNo stampbot.toml found in octocat/hello-world, using defaults[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'org_repo': None}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'level': 'info', 'timestamp': '2026-05-16T20:08:37.626863Z'}
INFO stampbot.webhook_handler:webhook_handler.py:593 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'level': 'info', 'timestamp': '2026-05-16T20:08:37.627418Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_issue_comment_approve - TypeError: unsupported operand type(s) for ** or pow(): 'BoxList' and 'BoxList'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 177 passed, 3 deselected in 1.20s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Add_RShift, occurrence: 0
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -71,7 +71,7 @@
return False
expected_signature = (
- "sha256=" + hmac.new(self.webhook_secret, payload, hashlib.sha256).hexdigest()
+ "sha256=" >> hmac.new(self.webhook_secret, payload, hashlib.sha256).hexdigest()
)
return hmac.compare_digest(expected_signature, signature)........................................................................ [ 34%]
........................F
=================================== FAILURES ===================================
_______________________ test_request_with_content_length _______________________
test_client = <starlette.testclient.TestClient object at 0x7f2477fab770>
def test_request_with_content_length(test_client: TestClient):
"""Test that requests with Content-Length header are tracked."""
# POST with a body will have Content-Length set automatically
> response = test_client.post(
"/webhook",
content=b'{"test": "data"}',
headers={
"Content-Type": "application/json",
"X-GitHub-Event": "ping",
"X-Hub-Signature-256": "sha256=invalid",
},
)
tests/test_main.py:69:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:546: in post
return super().post(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1144: in post
return self.request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:445: in request
return super().request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:825: in request
return self.send(request, auth=auth, follow_redirects=follow_redirects)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:914: in send
response = self._send_handling_auth(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:942: in _send_handling_auth
response = self._send_handling_redirects(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:979: in _send_handling_redirects
response = self._send_single_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1014: in _send_single_request
response = transport.handle_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:348: in handle_request
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:345: in handle_request
portal.call(self.app, scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:334: in call
return cast(T_Retval, self.start_task_soon(func, *args).result())
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:443: in result
return self.__get_result()
^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:395: in __get_result
raise self._exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:259: in _call_func
retval = await retval_or_awaitable
^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1135: in __call__
await super().__call__(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:107: in __call__
await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:186: in __call__
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:164: in __call__
await self.app(scope, receive, _send)
stampbot/main.py:205: in __call__
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:191: in __call__
with recv_stream, send_stream, collapse_excgroups():
^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/contextlib.py:162: in __exit__
self.gen.throw(value)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_utils.py:85: in collapse_excgroups
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:193: in __call__
response = await self.dispatch_func(request, call_next)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:120: in metrics_middleware
response = await call_next(request)
^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:168: in call_next
raise app_exc from app_exc.__cause__ or app_exc.__context__
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:144: in coro
await self.app(scope, receive_or_disconnect, send_no_error)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/exceptions.py:63: in __call__
await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/middleware/asyncexitstack.py:18: in __call__
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:716: in __call__
await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:736: in app
await route.handle(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:290: in handle
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:115: in app
await wrap_app_handling_exceptions(app, request)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:101: in app
response = await f(request)
^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:355: in app
raw_response = await run_endpoint_function(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:243: in run_endpoint_function
return await dependant.call(**values)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:296: in webhook
if not webhook_handler.verify_signature(body, x_hub_signature_256):
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.webhook_handler.WebhookHandler object at 0x7f2477fb2e40>
payload = b'{"test": "data"}', signature = 'sha256=invalid'
def verify_signature(self, payload: bytes, signature: str) -> bool:
"""Verify GitHub webhook signature.
Args:
payload: Request payload
signature: X-Hub-Signature-256 header value
Returns:
True if signature is valid
"""
if not signature:
return False
expected_signature = (
> "sha256=" >> hmac.new(self.webhook_secret, payload, hashlib.sha256).hexdigest()
)
E TypeError: unsupported operand type(s) for >>: 'str' and 'str'
stampbot/webhook_handler.py:74: TypeError
=========================== short test summary info ============================
FAILED tests/test_main.py::test_request_with_content_length - TypeError: unsupported operand type(s) for >>: 'str' and 'str'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 96 passed, 3 deselected in 1.18s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Add_RShift, occurrence: 1
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -451,7 +451,7 @@
command = command_match.group(1).lower()
add_span_attributes(span, {"chatops.command": command})
- if command in repo_config.approve_commands + repo_config.unapprove_commands:
+ if command in repo_config.approve_commands >> repo_config.unapprove_commands:
has_permission = await run_in_threadpool(
github_client.user_has_permission,
installation_id,........................................................................ [ 34%]
........................................................................ [ 68%]
.................................F
=================================== FAILURES ===================================
__________________________ test_issue_comment_approve __________________________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7f646d1371d0>
mock_github_client = <MagicMock name='github_client' id='140069303938960'>
@pytest.mark.asyncio
async def test_issue_comment_approve(webhook_handler, mock_github_client):
"""Test @stampbot approve command."""
payload = load_fixture("issue_comment_approve")
> result = await webhook_handler.handle_event("issue_comment", payload)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_webhook_handler.py:257:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
stampbot/webhook_handler.py:119: in handle_event
result = await self._handle_pr_comment(payload)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.webhook_handler.WebhookHandler object at 0x7f646d1371d0>
payload = {'action': 'created', 'comment': {'body': '@stampbot approve\n\nLGTM, approving this PR.', 'created_at': '2025-01-10T1...U='}, 'issue': {'assignee': None, 'assignees': [], 'body': 'This PR adds a new feature.', 'closed_at': None, ...}, ...}
async def _handle_pr_comment(self, payload: dict[str, Any]) -> dict[str, Any]:
"""Handle PR comment events for chatops.
Args:
payload: Event payload
Returns:
Response dictionary
"""
comment = payload.get("comment", {})
comment_body = comment.get("body", "")
# Security: limit comment length to prevent DoS
if len(comment_body) > MAX_COMMENT_LENGTH:
return {"status": "ignored", "message": "Comment too long"}
comment_body = comment_body.lower().strip()
# Check if bot is mentioned
if "@stampbot" not in comment_body:
return {"status": "ignored", "message": "Bot not mentioned"}
# Extract PR info
if "pull_request" in payload.get("issue", {}):
pr_url = payload["issue"]["pull_request"]["url"]
# Extract PR number from URL
pr_number = int(pr_url.split("/")[-1])
elif "pull_request" in payload:
pr_number = payload["pull_request"]["number"]
else:
return {"status": "error", "message": "Not a PR comment"}
repo = payload.get("repository", {})
repo_full_name = repo.get("full_name")
repo_default_branch = repo.get("default_branch") or "main"
repo_owner = repo.get("owner", {})
owner_login = repo_owner.get("login")
owner_type = repo_owner.get("type")
installation_id = payload.get("installation", {}).get("id")
commenter = comment.get("user", {}).get("login", "unknown")
with create_span(
"webhook.handle_chatops",
{
"github.repo": repo_full_name or "unknown",
"github.pr_number": pr_number,
"chatops.commenter": commenter,
},
) as span:
if not all([pr_number, repo_full_name, installation_id]):
logger.warning("Missing required fields in comment event")
add_span_attributes(span, {"chatops.result": "missing_fields"})
set_span_ok(span)
return {"status": "error", "message": "Missing required fields"}
# Get repository configuration
repo_config = await self._get_repo_config(
installation_id,
repo_full_name,
repo_default_branch,
owner_login,
owner_type,
)
if repo_config.config_error:
add_span_attributes(
span,
{
"chatops.result": "config_error",
"chatops.config_error": repo_config.config_error,
},
)
set_span_ok(span)
return {
"status": "error",
"message": "Invalid repository configuration",
}
if not repo_config.chatops_enabled:
add_span_attributes(span, {"chatops.result": "disabled"})
set_span_ok(span)
return {"status": "ignored", "message": "Chatops not enabled"}
# Parse command
command_match = re.search(r"@stampbot\s+(\w+)", comment_body)
if not command_match:
chatops_commands_total.labels(command="none", status="ignored").inc()
add_span_attributes(span, {"chatops.result": "no_command"})
set_span_ok(span)
return {"status": "ignored", "message": "No command found"}
command = command_match.group(1).lower()
add_span_attributes(span, {"chatops.command": command})
> if command in repo_config.approve_commands >> repo_config.unapprove_commands:
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E TypeError: unsupported operand type(s) for >>: 'BoxList' and 'BoxList'
stampbot/webhook_handler.py:454: TypeError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:38:49.878350Z[0m [[32m[1minfo [0m] [1mReceived issue_comment event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'issue_comment', 'action': 'created'}[0m
[2m2026-05-16T19:38:49.878892Z[0m [[32m[1minfo [0m] [1mNo stampbot.toml found in octocat/hello-world, using defaults[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'org_repo': None}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'level': 'info', 'timestamp': '2026-05-16T19:38:49.878350Z'}
INFO stampbot.webhook_handler:webhook_handler.py:593 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'level': 'info', 'timestamp': '2026-05-16T19:38:49.878892Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_issue_comment_approve - TypeError: unsupported operand type(s) for >>: 'BoxList' and 'BoxList'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 177 passed, 3 deselected in 1.17s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Add_LShift, occurrence: 0
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -71,7 +71,7 @@
return False
expected_signature = (
- "sha256=" + hmac.new(self.webhook_secret, payload, hashlib.sha256).hexdigest()
+ "sha256=" << hmac.new(self.webhook_secret, payload, hashlib.sha256).hexdigest()
)
return hmac.compare_digest(expected_signature, signature)........................................................................ [ 34%]
........................F
=================================== FAILURES ===================================
_______________________ test_request_with_content_length _______________________
test_client = <starlette.testclient.TestClient object at 0x7f7cb6dab770>
def test_request_with_content_length(test_client: TestClient):
"""Test that requests with Content-Length header are tracked."""
# POST with a body will have Content-Length set automatically
> response = test_client.post(
"/webhook",
content=b'{"test": "data"}',
headers={
"Content-Type": "application/json",
"X-GitHub-Event": "ping",
"X-Hub-Signature-256": "sha256=invalid",
},
)
tests/test_main.py:69:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:546: in post
return super().post(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1144: in post
return self.request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:445: in request
return super().request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:825: in request
return self.send(request, auth=auth, follow_redirects=follow_redirects)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:914: in send
response = self._send_handling_auth(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:942: in _send_handling_auth
response = self._send_handling_redirects(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:979: in _send_handling_redirects
response = self._send_single_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1014: in _send_single_request
response = transport.handle_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:348: in handle_request
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:345: in handle_request
portal.call(self.app, scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:334: in call
return cast(T_Retval, self.start_task_soon(func, *args).result())
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:443: in result
return self.__get_result()
^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:395: in __get_result
raise self._exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:259: in _call_func
retval = await retval_or_awaitable
^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1135: in __call__
await super().__call__(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:107: in __call__
await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:186: in __call__
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:164: in __call__
await self.app(scope, receive, _send)
stampbot/main.py:205: in __call__
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:191: in __call__
with recv_stream, send_stream, collapse_excgroups():
^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/contextlib.py:162: in __exit__
self.gen.throw(value)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_utils.py:85: in collapse_excgroups
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:193: in __call__
response = await self.dispatch_func(request, call_next)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:120: in metrics_middleware
response = await call_next(request)
^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:168: in call_next
raise app_exc from app_exc.__cause__ or app_exc.__context__
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:144: in coro
await self.app(scope, receive_or_disconnect, send_no_error)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/exceptions.py:63: in __call__
await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/middleware/asyncexitstack.py:18: in __call__
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:716: in __call__
await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:736: in app
await route.handle(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:290: in handle
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:115: in app
await wrap_app_handling_exceptions(app, request)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:101: in app
response = await f(request)
^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:355: in app
raw_response = await run_endpoint_function(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:243: in run_endpoint_function
return await dependant.call(**values)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:296: in webhook
if not webhook_handler.verify_signature(body, x_hub_signature_256):
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.webhook_handler.WebhookHandler object at 0x7f7cb6daee40>
payload = b'{"test": "data"}', signature = 'sha256=invalid'
def verify_signature(self, payload: bytes, signature: str) -> bool:
"""Verify GitHub webhook signature.
Args:
payload: Request payload
signature: X-Hub-Signature-256 header value
Returns:
True if signature is valid
"""
if not signature:
return False
expected_signature = (
> "sha256=" << hmac.new(self.webhook_secret, payload, hashlib.sha256).hexdigest()
)
E TypeError: unsupported operand type(s) for <<: 'str' and 'str'
stampbot/webhook_handler.py:74: TypeError
=========================== short test summary info ============================
FAILED tests/test_main.py::test_request_with_content_length - TypeError: unsupported operand type(s) for <<: 'str' and 'str'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 96 passed, 3 deselected in 1.16s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Add_LShift, occurrence: 1
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -451,7 +451,7 @@
command = command_match.group(1).lower()
add_span_attributes(span, {"chatops.command": command})
- if command in repo_config.approve_commands + repo_config.unapprove_commands:
+ if command in repo_config.approve_commands << repo_config.unapprove_commands:
has_permission = await run_in_threadpool(
github_client.user_has_permission,
installation_id,........................................................................ [ 34%]
........................................................................ [ 68%]
.................................F
=================================== FAILURES ===================================
__________________________ test_issue_comment_approve __________________________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7f80c1931ff0>
mock_github_client = <MagicMock name='github_client' id='140190980681616'>
@pytest.mark.asyncio
async def test_issue_comment_approve(webhook_handler, mock_github_client):
"""Test @stampbot approve command."""
payload = load_fixture("issue_comment_approve")
> result = await webhook_handler.handle_event("issue_comment", payload)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_webhook_handler.py:257:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
stampbot/webhook_handler.py:119: in handle_event
result = await self._handle_pr_comment(payload)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.webhook_handler.WebhookHandler object at 0x7f80c1931ff0>
payload = {'action': 'created', 'comment': {'body': '@stampbot approve\n\nLGTM, approving this PR.', 'created_at': '2025-01-10T1...U='}, 'issue': {'assignee': None, 'assignees': [], 'body': 'This PR adds a new feature.', 'closed_at': None, ...}, ...}
async def _handle_pr_comment(self, payload: dict[str, Any]) -> dict[str, Any]:
"""Handle PR comment events for chatops.
Args:
payload: Event payload
Returns:
Response dictionary
"""
comment = payload.get("comment", {})
comment_body = comment.get("body", "")
# Security: limit comment length to prevent DoS
if len(comment_body) > MAX_COMMENT_LENGTH:
return {"status": "ignored", "message": "Comment too long"}
comment_body = comment_body.lower().strip()
# Check if bot is mentioned
if "@stampbot" not in comment_body:
return {"status": "ignored", "message": "Bot not mentioned"}
# Extract PR info
if "pull_request" in payload.get("issue", {}):
pr_url = payload["issue"]["pull_request"]["url"]
# Extract PR number from URL
pr_number = int(pr_url.split("/")[-1])
elif "pull_request" in payload:
pr_number = payload["pull_request"]["number"]
else:
return {"status": "error", "message": "Not a PR comment"}
repo = payload.get("repository", {})
repo_full_name = repo.get("full_name")
repo_default_branch = repo.get("default_branch") or "main"
repo_owner = repo.get("owner", {})
owner_login = repo_owner.get("login")
owner_type = repo_owner.get("type")
installation_id = payload.get("installation", {}).get("id")
commenter = comment.get("user", {}).get("login", "unknown")
with create_span(
"webhook.handle_chatops",
{
"github.repo": repo_full_name or "unknown",
"github.pr_number": pr_number,
"chatops.commenter": commenter,
},
) as span:
if not all([pr_number, repo_full_name, installation_id]):
logger.warning("Missing required fields in comment event")
add_span_attributes(span, {"chatops.result": "missing_fields"})
set_span_ok(span)
return {"status": "error", "message": "Missing required fields"}
# Get repository configuration
repo_config = await self._get_repo_config(
installation_id,
repo_full_name,
repo_default_branch,
owner_login,
owner_type,
)
if repo_config.config_error:
add_span_attributes(
span,
{
"chatops.result": "config_error",
"chatops.config_error": repo_config.config_error,
},
)
set_span_ok(span)
return {
"status": "error",
"message": "Invalid repository configuration",
}
if not repo_config.chatops_enabled:
add_span_attributes(span, {"chatops.result": "disabled"})
set_span_ok(span)
return {"status": "ignored", "message": "Chatops not enabled"}
# Parse command
command_match = re.search(r"@stampbot\s+(\w+)", comment_body)
if not command_match:
chatops_commands_total.labels(command="none", status="ignored").inc()
add_span_attributes(span, {"chatops.result": "no_command"})
set_span_ok(span)
return {"status": "ignored", "message": "No command found"}
command = command_match.group(1).lower()
add_span_attributes(span, {"chatops.command": command})
> if command in repo_config.approve_commands << repo_config.unapprove_commands:
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E TypeError: unsupported operand type(s) for <<: 'BoxList' and 'BoxList'
stampbot/webhook_handler.py:454: TypeError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T20:02:16.276814Z[0m [[32m[1minfo [0m] [1mReceived issue_comment event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'issue_comment', 'action': 'created'}[0m
[2m2026-05-16T20:02:16.277370Z[0m [[32m[1minfo [0m] [1mNo stampbot.toml found in octocat/hello-world, using defaults[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'org_repo': None}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'level': 'info', 'timestamp': '2026-05-16T20:02:16.276814Z'}
INFO stampbot.webhook_handler:webhook_handler.py:593 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'level': 'info', 'timestamp': '2026-05-16T20:02:16.277370Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_issue_comment_approve - TypeError: unsupported operand type(s) for <<: 'BoxList' and 'BoxList'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 177 passed, 3 deselected in 1.17s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Add_BitOr, occurrence: 0
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -71,7 +71,7 @@
return False
expected_signature = (
- "sha256=" + hmac.new(self.webhook_secret, payload, hashlib.sha256).hexdigest()
+ "sha256=" | hmac.new(self.webhook_secret, payload, hashlib.sha256).hexdigest()
)
return hmac.compare_digest(expected_signature, signature)........................................................................ [ 34%]
........................F
=================================== FAILURES ===================================
_______________________ test_request_with_content_length _______________________
test_client = <starlette.testclient.TestClient object at 0x7f62058a3770>
def test_request_with_content_length(test_client: TestClient):
"""Test that requests with Content-Length header are tracked."""
# POST with a body will have Content-Length set automatically
> response = test_client.post(
"/webhook",
content=b'{"test": "data"}',
headers={
"Content-Type": "application/json",
"X-GitHub-Event": "ping",
"X-Hub-Signature-256": "sha256=invalid",
},
)
tests/test_main.py:69:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:546: in post
return super().post(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1144: in post
return self.request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:445: in request
return super().request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:825: in request
return self.send(request, auth=auth, follow_redirects=follow_redirects)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:914: in send
response = self._send_handling_auth(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:942: in _send_handling_auth
response = self._send_handling_redirects(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:979: in _send_handling_redirects
response = self._send_single_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1014: in _send_single_request
response = transport.handle_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:348: in handle_request
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:345: in handle_request
portal.call(self.app, scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:334: in call
return cast(T_Retval, self.start_task_soon(func, *args).result())
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:443: in result
return self.__get_result()
^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:395: in __get_result
raise self._exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:259: in _call_func
retval = await retval_or_awaitable
^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1135: in __call__
await super().__call__(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:107: in __call__
await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:186: in __call__
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:164: in __call__
await self.app(scope, receive, _send)
stampbot/main.py:205: in __call__
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:191: in __call__
with recv_stream, send_stream, collapse_excgroups():
^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/contextlib.py:162: in __exit__
self.gen.throw(value)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_utils.py:85: in collapse_excgroups
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:193: in __call__
response = await self.dispatch_func(request, call_next)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:120: in metrics_middleware
response = await call_next(request)
^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:168: in call_next
raise app_exc from app_exc.__cause__ or app_exc.__context__
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:144: in coro
await self.app(scope, receive_or_disconnect, send_no_error)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/exceptions.py:63: in __call__
await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/middleware/asyncexitstack.py:18: in __call__
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:716: in __call__
await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:736: in app
await route.handle(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:290: in handle
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:115: in app
await wrap_app_handling_exceptions(app, request)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:101: in app
response = await f(request)
^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:355: in app
raw_response = await run_endpoint_function(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:243: in run_endpoint_function
return await dependant.call(**values)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:296: in webhook
if not webhook_handler.verify_signature(body, x_hub_signature_256):
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.webhook_handler.WebhookHandler object at 0x7f62058a6e40>
payload = b'{"test": "data"}', signature = 'sha256=invalid'
def verify_signature(self, payload: bytes, signature: str) -> bool:
"""Verify GitHub webhook signature.
Args:
payload: Request payload
signature: X-Hub-Signature-256 header value
Returns:
True if signature is valid
"""
if not signature:
return False
expected_signature = (
> "sha256=" | hmac.new(self.webhook_secret, payload, hashlib.sha256).hexdigest()
)
E TypeError: unsupported operand type(s) for |: 'str' and 'str'
stampbot/webhook_handler.py:74: TypeError
=========================== short test summary info ============================
FAILED tests/test_main.py::test_request_with_content_length - TypeError: unsupported operand type(s) for |: 'str' and 'str'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 96 passed, 3 deselected in 1.16s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Add_BitOr, occurrence: 1
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -451,7 +451,7 @@
command = command_match.group(1).lower()
add_span_attributes(span, {"chatops.command": command})
- if command in repo_config.approve_commands + repo_config.unapprove_commands:
+ if command in repo_config.approve_commands | repo_config.unapprove_commands:
has_permission = await run_in_threadpool(
github_client.user_has_permission,
installation_id,........................................................................ [ 34%]
........................................................................ [ 68%]
.................................F
=================================== FAILURES ===================================
__________________________ test_issue_comment_approve __________________________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7ff4ae943330>
mock_github_client = <MagicMock name='github_client' id='140688878186384'>
@pytest.mark.asyncio
async def test_issue_comment_approve(webhook_handler, mock_github_client):
"""Test @stampbot approve command."""
payload = load_fixture("issue_comment_approve")
> result = await webhook_handler.handle_event("issue_comment", payload)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_webhook_handler.py:257:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
stampbot/webhook_handler.py:119: in handle_event
result = await self._handle_pr_comment(payload)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.webhook_handler.WebhookHandler object at 0x7ff4ae943330>
payload = {'action': 'created', 'comment': {'body': '@stampbot approve\n\nLGTM, approving this PR.', 'created_at': '2025-01-10T1...U='}, 'issue': {'assignee': None, 'assignees': [], 'body': 'This PR adds a new feature.', 'closed_at': None, ...}, ...}
async def _handle_pr_comment(self, payload: dict[str, Any]) -> dict[str, Any]:
"""Handle PR comment events for chatops.
Args:
payload: Event payload
Returns:
Response dictionary
"""
comment = payload.get("comment", {})
comment_body = comment.get("body", "")
# Security: limit comment length to prevent DoS
if len(comment_body) > MAX_COMMENT_LENGTH:
return {"status": "ignored", "message": "Comment too long"}
comment_body = comment_body.lower().strip()
# Check if bot is mentioned
if "@stampbot" not in comment_body:
return {"status": "ignored", "message": "Bot not mentioned"}
# Extract PR info
if "pull_request" in payload.get("issue", {}):
pr_url = payload["issue"]["pull_request"]["url"]
# Extract PR number from URL
pr_number = int(pr_url.split("/")[-1])
elif "pull_request" in payload:
pr_number = payload["pull_request"]["number"]
else:
return {"status": "error", "message": "Not a PR comment"}
repo = payload.get("repository", {})
repo_full_name = repo.get("full_name")
repo_default_branch = repo.get("default_branch") or "main"
repo_owner = repo.get("owner", {})
owner_login = repo_owner.get("login")
owner_type = repo_owner.get("type")
installation_id = payload.get("installation", {}).get("id")
commenter = comment.get("user", {}).get("login", "unknown")
with create_span(
"webhook.handle_chatops",
{
"github.repo": repo_full_name or "unknown",
"github.pr_number": pr_number,
"chatops.commenter": commenter,
},
) as span:
if not all([pr_number, repo_full_name, installation_id]):
logger.warning("Missing required fields in comment event")
add_span_attributes(span, {"chatops.result": "missing_fields"})
set_span_ok(span)
return {"status": "error", "message": "Missing required fields"}
# Get repository configuration
repo_config = await self._get_repo_config(
installation_id,
repo_full_name,
repo_default_branch,
owner_login,
owner_type,
)
if repo_config.config_error:
add_span_attributes(
span,
{
"chatops.result": "config_error",
"chatops.config_error": repo_config.config_error,
},
)
set_span_ok(span)
return {
"status": "error",
"message": "Invalid repository configuration",
}
if not repo_config.chatops_enabled:
add_span_attributes(span, {"chatops.result": "disabled"})
set_span_ok(span)
return {"status": "ignored", "message": "Chatops not enabled"}
# Parse command
command_match = re.search(r"@stampbot\s+(\w+)", comment_body)
if not command_match:
chatops_commands_total.labels(command="none", status="ignored").inc()
add_span_attributes(span, {"chatops.result": "no_command"})
set_span_ok(span)
return {"status": "ignored", "message": "No command found"}
command = command_match.group(1).lower()
add_span_attributes(span, {"chatops.command": command})
> if command in repo_config.approve_commands | repo_config.unapprove_commands:
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E TypeError: unsupported operand type(s) for |: 'BoxList' and 'BoxList'
stampbot/webhook_handler.py:454: TypeError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:31:52.160339Z[0m [[32m[1minfo [0m] [1mReceived issue_comment event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'issue_comment', 'action': 'created'}[0m
[2m2026-05-16T19:31:52.160875Z[0m [[32m[1minfo [0m] [1mNo stampbot.toml found in octocat/hello-world, using defaults[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'org_repo': None}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'level': 'info', 'timestamp': '2026-05-16T19:31:52.160339Z'}
INFO stampbot.webhook_handler:webhook_handler.py:593 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'level': 'info', 'timestamp': '2026-05-16T19:31:52.160875Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_issue_comment_approve - TypeError: unsupported operand type(s) for |: 'BoxList' and 'BoxList'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 177 passed, 3 deselected in 1.16s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Add_BitAnd, occurrence: 0
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -71,7 +71,7 @@
return False
expected_signature = (
- "sha256=" + hmac.new(self.webhook_secret, payload, hashlib.sha256).hexdigest()
+ "sha256=" & hmac.new(self.webhook_secret, payload, hashlib.sha256).hexdigest()
)
return hmac.compare_digest(expected_signature, signature)........................................................................ [ 34%]
........................F
=================================== FAILURES ===================================
_______________________ test_request_with_content_length _______________________
test_client = <starlette.testclient.TestClient object at 0x7f42a30a7770>
def test_request_with_content_length(test_client: TestClient):
"""Test that requests with Content-Length header are tracked."""
# POST with a body will have Content-Length set automatically
> response = test_client.post(
"/webhook",
content=b'{"test": "data"}',
headers={
"Content-Type": "application/json",
"X-GitHub-Event": "ping",
"X-Hub-Signature-256": "sha256=invalid",
},
)
tests/test_main.py:69:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:546: in post
return super().post(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1144: in post
return self.request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:445: in request
return super().request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:825: in request
return self.send(request, auth=auth, follow_redirects=follow_redirects)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:914: in send
response = self._send_handling_auth(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:942: in _send_handling_auth
response = self._send_handling_redirects(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:979: in _send_handling_redirects
response = self._send_single_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1014: in _send_single_request
response = transport.handle_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:348: in handle_request
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:345: in handle_request
portal.call(self.app, scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:334: in call
return cast(T_Retval, self.start_task_soon(func, *args).result())
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:443: in result
return self.__get_result()
^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:395: in __get_result
raise self._exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:259: in _call_func
retval = await retval_or_awaitable
^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1135: in __call__
await super().__call__(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:107: in __call__
await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:186: in __call__
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:164: in __call__
await self.app(scope, receive, _send)
stampbot/main.py:205: in __call__
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:191: in __call__
with recv_stream, send_stream, collapse_excgroups():
^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/contextlib.py:162: in __exit__
self.gen.throw(value)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_utils.py:85: in collapse_excgroups
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:193: in __call__
response = await self.dispatch_func(request, call_next)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:120: in metrics_middleware
response = await call_next(request)
^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:168: in call_next
raise app_exc from app_exc.__cause__ or app_exc.__context__
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:144: in coro
await self.app(scope, receive_or_disconnect, send_no_error)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/exceptions.py:63: in __call__
await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/middleware/asyncexitstack.py:18: in __call__
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:716: in __call__
await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:736: in app
await route.handle(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:290: in handle
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:115: in app
await wrap_app_handling_exceptions(app, request)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:101: in app
response = await f(request)
^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:355: in app
raw_response = await run_endpoint_function(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:243: in run_endpoint_function
return await dependant.call(**values)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:296: in webhook
if not webhook_handler.verify_signature(body, x_hub_signature_256):
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.webhook_handler.WebhookHandler object at 0x7f42a30aae40>
payload = b'{"test": "data"}', signature = 'sha256=invalid'
def verify_signature(self, payload: bytes, signature: str) -> bool:
"""Verify GitHub webhook signature.
Args:
payload: Request payload
signature: X-Hub-Signature-256 header value
Returns:
True if signature is valid
"""
if not signature:
return False
expected_signature = (
> "sha256=" & hmac.new(self.webhook_secret, payload, hashlib.sha256).hexdigest()
)
E TypeError: unsupported operand type(s) for &: 'str' and 'str'
stampbot/webhook_handler.py:74: TypeError
=========================== short test summary info ============================
FAILED tests/test_main.py::test_request_with_content_length - TypeError: unsupported operand type(s) for &: 'str' and 'str'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 96 passed, 3 deselected in 1.18s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Add_BitAnd, occurrence: 1
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -451,7 +451,7 @@
command = command_match.group(1).lower()
add_span_attributes(span, {"chatops.command": command})
- if command in repo_config.approve_commands + repo_config.unapprove_commands:
+ if command in repo_config.approve_commands & repo_config.unapprove_commands:
has_permission = await run_in_threadpool(
github_client.user_has_permission,
installation_id,........................................................................ [ 34%]
........................................................................ [ 68%]
.................................F
=================================== FAILURES ===================================
__________________________ test_issue_comment_approve __________________________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7f9a52145ff0>
mock_github_client = <MagicMock name='github_client' id='140300779253648'>
@pytest.mark.asyncio
async def test_issue_comment_approve(webhook_handler, mock_github_client):
"""Test @stampbot approve command."""
payload = load_fixture("issue_comment_approve")
> result = await webhook_handler.handle_event("issue_comment", payload)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_webhook_handler.py:257:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
stampbot/webhook_handler.py:119: in handle_event
result = await self._handle_pr_comment(payload)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.webhook_handler.WebhookHandler object at 0x7f9a52145ff0>
payload = {'action': 'created', 'comment': {'body': '@stampbot approve\n\nLGTM, approving this PR.', 'created_at': '2025-01-10T1...U='}, 'issue': {'assignee': None, 'assignees': [], 'body': 'This PR adds a new feature.', 'closed_at': None, ...}, ...}
async def _handle_pr_comment(self, payload: dict[str, Any]) -> dict[str, Any]:
"""Handle PR comment events for chatops.
Args:
payload: Event payload
Returns:
Response dictionary
"""
comment = payload.get("comment", {})
comment_body = comment.get("body", "")
# Security: limit comment length to prevent DoS
if len(comment_body) > MAX_COMMENT_LENGTH:
return {"status": "ignored", "message": "Comment too long"}
comment_body = comment_body.lower().strip()
# Check if bot is mentioned
if "@stampbot" not in comment_body:
return {"status": "ignored", "message": "Bot not mentioned"}
# Extract PR info
if "pull_request" in payload.get("issue", {}):
pr_url = payload["issue"]["pull_request"]["url"]
# Extract PR number from URL
pr_number = int(pr_url.split("/")[-1])
elif "pull_request" in payload:
pr_number = payload["pull_request"]["number"]
else:
return {"status": "error", "message": "Not a PR comment"}
repo = payload.get("repository", {})
repo_full_name = repo.get("full_name")
repo_default_branch = repo.get("default_branch") or "main"
repo_owner = repo.get("owner", {})
owner_login = repo_owner.get("login")
owner_type = repo_owner.get("type")
installation_id = payload.get("installation", {}).get("id")
commenter = comment.get("user", {}).get("login", "unknown")
with create_span(
"webhook.handle_chatops",
{
"github.repo": repo_full_name or "unknown",
"github.pr_number": pr_number,
"chatops.commenter": commenter,
},
) as span:
if not all([pr_number, repo_full_name, installation_id]):
logger.warning("Missing required fields in comment event")
add_span_attributes(span, {"chatops.result": "missing_fields"})
set_span_ok(span)
return {"status": "error", "message": "Missing required fields"}
# Get repository configuration
repo_config = await self._get_repo_config(
installation_id,
repo_full_name,
repo_default_branch,
owner_login,
owner_type,
)
if repo_config.config_error:
add_span_attributes(
span,
{
"chatops.result": "config_error",
"chatops.config_error": repo_config.config_error,
},
)
set_span_ok(span)
return {
"status": "error",
"message": "Invalid repository configuration",
}
if not repo_config.chatops_enabled:
add_span_attributes(span, {"chatops.result": "disabled"})
set_span_ok(span)
return {"status": "ignored", "message": "Chatops not enabled"}
# Parse command
command_match = re.search(r"@stampbot\s+(\w+)", comment_body)
if not command_match:
chatops_commands_total.labels(command="none", status="ignored").inc()
add_span_attributes(span, {"chatops.result": "no_command"})
set_span_ok(span)
return {"status": "ignored", "message": "No command found"}
command = command_match.group(1).lower()
add_span_attributes(span, {"chatops.command": command})
> if command in repo_config.approve_commands & repo_config.unapprove_commands:
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E TypeError: unsupported operand type(s) for &: 'BoxList' and 'BoxList'
stampbot/webhook_handler.py:454: TypeError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:57:40.920950Z[0m [[32m[1minfo [0m] [1mReceived issue_comment event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'issue_comment', 'action': 'created'}[0m
[2m2026-05-16T19:57:40.921508Z[0m [[32m[1minfo [0m] [1mNo stampbot.toml found in octocat/hello-world, using defaults[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'org_repo': None}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'level': 'info', 'timestamp': '2026-05-16T19:57:40.920950Z'}
INFO stampbot.webhook_handler:webhook_handler.py:593 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'level': 'info', 'timestamp': '2026-05-16T19:57:40.921508Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_issue_comment_approve - TypeError: unsupported operand type(s) for &: 'BoxList' and 'BoxList'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 177 passed, 3 deselected in 1.15s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Add_BitXor, occurrence: 0
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -71,7 +71,7 @@
return False
expected_signature = (
- "sha256=" + hmac.new(self.webhook_secret, payload, hashlib.sha256).hexdigest()
+ "sha256=" ^ hmac.new(self.webhook_secret, payload, hashlib.sha256).hexdigest()
)
return hmac.compare_digest(expected_signature, signature)........................................................................ [ 34%]
........................F
=================================== FAILURES ===================================
_______________________ test_request_with_content_length _______________________
test_client = <starlette.testclient.TestClient object at 0x7f60711a7770>
def test_request_with_content_length(test_client: TestClient):
"""Test that requests with Content-Length header are tracked."""
# POST with a body will have Content-Length set automatically
> response = test_client.post(
"/webhook",
content=b'{"test": "data"}',
headers={
"Content-Type": "application/json",
"X-GitHub-Event": "ping",
"X-Hub-Signature-256": "sha256=invalid",
},
)
tests/test_main.py:69:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:546: in post
return super().post(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1144: in post
return self.request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:445: in request
return super().request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:825: in request
return self.send(request, auth=auth, follow_redirects=follow_redirects)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:914: in send
response = self._send_handling_auth(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:942: in _send_handling_auth
response = self._send_handling_redirects(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:979: in _send_handling_redirects
response = self._send_single_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1014: in _send_single_request
response = transport.handle_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:348: in handle_request
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:345: in handle_request
portal.call(self.app, scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:334: in call
return cast(T_Retval, self.start_task_soon(func, *args).result())
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:443: in result
return self.__get_result()
^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:395: in __get_result
raise self._exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:259: in _call_func
retval = await retval_or_awaitable
^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1135: in __call__
await super().__call__(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:107: in __call__
await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:186: in __call__
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:164: in __call__
await self.app(scope, receive, _send)
stampbot/main.py:205: in __call__
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:191: in __call__
with recv_stream, send_stream, collapse_excgroups():
^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/contextlib.py:162: in __exit__
self.gen.throw(value)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_utils.py:85: in collapse_excgroups
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:193: in __call__
response = await self.dispatch_func(request, call_next)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:120: in metrics_middleware
response = await call_next(request)
^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:168: in call_next
raise app_exc from app_exc.__cause__ or app_exc.__context__
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:144: in coro
await self.app(scope, receive_or_disconnect, send_no_error)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/exceptions.py:63: in __call__
await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/middleware/asyncexitstack.py:18: in __call__
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:716: in __call__
await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:736: in app
await route.handle(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:290: in handle
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:115: in app
await wrap_app_handling_exceptions(app, request)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:101: in app
response = await f(request)
^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:355: in app
raw_response = await run_endpoint_function(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:243: in run_endpoint_function
return await dependant.call(**values)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:296: in webhook
if not webhook_handler.verify_signature(body, x_hub_signature_256):
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.webhook_handler.WebhookHandler object at 0x7f60711aae40>
payload = b'{"test": "data"}', signature = 'sha256=invalid'
def verify_signature(self, payload: bytes, signature: str) -> bool:
"""Verify GitHub webhook signature.
Args:
payload: Request payload
signature: X-Hub-Signature-256 header value
Returns:
True if signature is valid
"""
if not signature:
return False
expected_signature = (
> "sha256=" ^ hmac.new(self.webhook_secret, payload, hashlib.sha256).hexdigest()
)
E TypeError: unsupported operand type(s) for ^: 'str' and 'str'
stampbot/webhook_handler.py:74: TypeError
=========================== short test summary info ============================
FAILED tests/test_main.py::test_request_with_content_length - TypeError: unsupported operand type(s) for ^: 'str' and 'str'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 96 passed, 3 deselected in 1.17s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Add_BitXor, occurrence: 1
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -451,7 +451,7 @@
command = command_match.group(1).lower()
add_span_attributes(span, {"chatops.command": command})
- if command in repo_config.approve_commands + repo_config.unapprove_commands:
+ if command in repo_config.approve_commands ^ repo_config.unapprove_commands:
has_permission = await run_in_threadpool(
github_client.user_has_permission,
installation_id,........................................................................ [ 34%]
........................................................................ [ 68%]
.................................F
=================================== FAILURES ===================================
__________________________ test_issue_comment_approve __________________________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7f979c265ff0>
mock_github_client = <MagicMock name='github_client' id='140289137078160'>
@pytest.mark.asyncio
async def test_issue_comment_approve(webhook_handler, mock_github_client):
"""Test @stampbot approve command."""
payload = load_fixture("issue_comment_approve")
> result = await webhook_handler.handle_event("issue_comment", payload)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_webhook_handler.py:257:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
stampbot/webhook_handler.py:119: in handle_event
result = await self._handle_pr_comment(payload)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.webhook_handler.WebhookHandler object at 0x7f979c265ff0>
payload = {'action': 'created', 'comment': {'body': '@stampbot approve\n\nLGTM, approving this PR.', 'created_at': '2025-01-10T1...U='}, 'issue': {'assignee': None, 'assignees': [], 'body': 'This PR adds a new feature.', 'closed_at': None, ...}, ...}
async def _handle_pr_comment(self, payload: dict[str, Any]) -> dict[str, Any]:
"""Handle PR comment events for chatops.
Args:
payload: Event payload
Returns:
Response dictionary
"""
comment = payload.get("comment", {})
comment_body = comment.get("body", "")
# Security: limit comment length to prevent DoS
if len(comment_body) > MAX_COMMENT_LENGTH:
return {"status": "ignored", "message": "Comment too long"}
comment_body = comment_body.lower().strip()
# Check if bot is mentioned
if "@stampbot" not in comment_body:
return {"status": "ignored", "message": "Bot not mentioned"}
# Extract PR info
if "pull_request" in payload.get("issue", {}):
pr_url = payload["issue"]["pull_request"]["url"]
# Extract PR number from URL
pr_number = int(pr_url.split("/")[-1])
elif "pull_request" in payload:
pr_number = payload["pull_request"]["number"]
else:
return {"status": "error", "message": "Not a PR comment"}
repo = payload.get("repository", {})
repo_full_name = repo.get("full_name")
repo_default_branch = repo.get("default_branch") or "main"
repo_owner = repo.get("owner", {})
owner_login = repo_owner.get("login")
owner_type = repo_owner.get("type")
installation_id = payload.get("installation", {}).get("id")
commenter = comment.get("user", {}).get("login", "unknown")
with create_span(
"webhook.handle_chatops",
{
"github.repo": repo_full_name or "unknown",
"github.pr_number": pr_number,
"chatops.commenter": commenter,
},
) as span:
if not all([pr_number, repo_full_name, installation_id]):
logger.warning("Missing required fields in comment event")
add_span_attributes(span, {"chatops.result": "missing_fields"})
set_span_ok(span)
return {"status": "error", "message": "Missing required fields"}
# Get repository configuration
repo_config = await self._get_repo_config(
installation_id,
repo_full_name,
repo_default_branch,
owner_login,
owner_type,
)
if repo_config.config_error:
add_span_attributes(
span,
{
"chatops.result": "config_error",
"chatops.config_error": repo_config.config_error,
},
)
set_span_ok(span)
return {
"status": "error",
"message": "Invalid repository configuration",
}
if not repo_config.chatops_enabled:
add_span_attributes(span, {"chatops.result": "disabled"})
set_span_ok(span)
return {"status": "ignored", "message": "Chatops not enabled"}
# Parse command
command_match = re.search(r"@stampbot\s+(\w+)", comment_body)
if not command_match:
chatops_commands_total.labels(command="none", status="ignored").inc()
add_span_attributes(span, {"chatops.result": "no_command"})
set_span_ok(span)
return {"status": "ignored", "message": "No command found"}
command = command_match.group(1).lower()
add_span_attributes(span, {"chatops.command": command})
> if command in repo_config.approve_commands ^ repo_config.unapprove_commands:
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E TypeError: unsupported operand type(s) for ^: 'BoxList' and 'BoxList'
stampbot/webhook_handler.py:454: TypeError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:32:32.146857Z[0m [[32m[1minfo [0m] [1mReceived issue_comment event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'issue_comment', 'action': 'created'}[0m
[2m2026-05-16T19:32:32.147424Z[0m [[32m[1minfo [0m] [1mNo stampbot.toml found in octocat/hello-world, using defaults[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'org_repo': None}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'level': 'info', 'timestamp': '2026-05-16T19:32:32.146857Z'}
INFO stampbot.webhook_handler:webhook_handler.py:593 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'level': 'info', 'timestamp': '2026-05-16T19:32:32.147424Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_issue_comment_approve - TypeError: unsupported operand type(s) for ^: 'BoxList' and 'BoxList'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 177 passed, 3 deselected in 1.16s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_Add, occurrence: 0
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -720,7 +720,7 @@
comment,
)
- duration = time.time() - start_time
+ duration = time.time() + start_time
pr_approval_duration_seconds.observe(duration)
status = "success" if success else "failure"........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.62s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_Add, occurrence: 1
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -784,7 +784,7 @@
pr_number,
extra={"repo": repo_full_name, "pr_number": pr_number},
)
- duration = time.time() - start_time
+ duration = time.time() + start_time
pr_dismissal_duration_seconds.observe(duration)
pr_dismissals_total.labels(trigger_type=trigger_type, status="success").inc()
add_span_attributes(span, {"dismissal.result": "no_reviews"})........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.60s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_Add, occurrence: 2
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -804,7 +804,7 @@
)
success = success and result
- duration = time.time() - start_time
+ duration = time.time() + start_time
pr_dismissal_duration_seconds.observe(duration)
status = "success" if success else "failure"........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_Add, occurrence: 3
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -816,7 +816,7 @@
return success
except Exception as e:
- duration = time.time() - start_time
+ duration = time.time() + start_time
pr_dismissal_duration_seconds.observe(duration)
pr_dismissals_total.labels(trigger_type=trigger_type, status="failure").inc()
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.66s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_Mul, occurrence: 0
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -720,7 +720,7 @@
comment,
)
- duration = time.time() - start_time
+ duration = time.time() * start_time
pr_approval_duration_seconds.observe(duration)
status = "success" if success else "failure"........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.62s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_Mul, occurrence: 1
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -784,7 +784,7 @@
pr_number,
extra={"repo": repo_full_name, "pr_number": pr_number},
)
- duration = time.time() - start_time
+ duration = time.time() * start_time
pr_dismissal_duration_seconds.observe(duration)
pr_dismissals_total.labels(trigger_type=trigger_type, status="success").inc()
add_span_attributes(span, {"dismissal.result": "no_reviews"})........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.65s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_Mul, occurrence: 2
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -804,7 +804,7 @@
)
success = success and result
- duration = time.time() - start_time
+ duration = time.time() * start_time
pr_dismissal_duration_seconds.observe(duration)
status = "success" if success else "failure"........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_Mul, occurrence: 3
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -816,7 +816,7 @@
return success
except Exception as e:
- duration = time.time() - start_time
+ duration = time.time() * start_time
pr_dismissal_duration_seconds.observe(duration)
pr_dismissals_total.labels(trigger_type=trigger_type, status="failure").inc()
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_Div, occurrence: 0
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -720,7 +720,7 @@
comment,
)
- duration = time.time() - start_time
+ duration = time.time() / start_time
pr_approval_duration_seconds.observe(duration)
status = "success" if success else "failure"........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.60s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_Div, occurrence: 1
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -784,7 +784,7 @@
pr_number,
extra={"repo": repo_full_name, "pr_number": pr_number},
)
- duration = time.time() - start_time
+ duration = time.time() / start_time
pr_dismissal_duration_seconds.observe(duration)
pr_dismissals_total.labels(trigger_type=trigger_type, status="success").inc()
add_span_attributes(span, {"dismissal.result": "no_reviews"})........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.66s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_Div, occurrence: 2
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -804,7 +804,7 @@
)
success = success and result
- duration = time.time() - start_time
+ duration = time.time() / start_time
pr_dismissal_duration_seconds.observe(duration)
status = "success" if success else "failure"........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.60s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_Div, occurrence: 3
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -816,7 +816,7 @@
return success
except Exception as e:
- duration = time.time() - start_time
+ duration = time.time() / start_time
pr_dismissal_duration_seconds.observe(duration)
pr_dismissals_total.labels(trigger_type=trigger_type, status="failure").inc()
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_FloorDiv, occurrence: 0
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -720,7 +720,7 @@
comment,
)
- duration = time.time() - start_time
+ duration = time.time() // start_time
pr_approval_duration_seconds.observe(duration)
status = "success" if success else "failure"........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.62s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_FloorDiv, occurrence: 1
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -784,7 +784,7 @@
pr_number,
extra={"repo": repo_full_name, "pr_number": pr_number},
)
- duration = time.time() - start_time
+ duration = time.time() // start_time
pr_dismissal_duration_seconds.observe(duration)
pr_dismissals_total.labels(trigger_type=trigger_type, status="success").inc()
add_span_attributes(span, {"dismissal.result": "no_reviews"})........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.60s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_FloorDiv, occurrence: 2
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -804,7 +804,7 @@
)
success = success and result
- duration = time.time() - start_time
+ duration = time.time() // start_time
pr_dismissal_duration_seconds.observe(duration)
status = "success" if success else "failure"........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_FloorDiv, occurrence: 3
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -816,7 +816,7 @@
return success
except Exception as e:
- duration = time.time() - start_time
+ duration = time.time() // start_time
pr_dismissal_duration_seconds.observe(duration)
pr_dismissals_total.labels(trigger_type=trigger_type, status="failure").inc()
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.60s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_Mod, occurrence: 0
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -720,7 +720,7 @@
comment,
)
- duration = time.time() - start_time
+ duration = time.time() % start_time
pr_approval_duration_seconds.observe(duration)
status = "success" if success else "failure"........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.60s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_Mod, occurrence: 1
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -784,7 +784,7 @@
pr_number,
extra={"repo": repo_full_name, "pr_number": pr_number},
)
- duration = time.time() - start_time
+ duration = time.time() % start_time
pr_dismissal_duration_seconds.observe(duration)
pr_dismissals_total.labels(trigger_type=trigger_type, status="success").inc()
add_span_attributes(span, {"dismissal.result": "no_reviews"})........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.60s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_Mod, occurrence: 2
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -804,7 +804,7 @@
)
success = success and result
- duration = time.time() - start_time
+ duration = time.time() % start_time
pr_dismissal_duration_seconds.observe(duration)
status = "success" if success else "failure"........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_Sub_Mod, occurrence: 3
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -816,7 +816,7 @@
return success
except Exception as e:
- duration = time.time() - start_time
+ duration = time.time() % start_time
pr_dismissal_duration_seconds.observe(duration)
pr_dismissals_total.labels(trigger_type=trigger_type, status="failure").inc()
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.63s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_Pow, occurrence: 0
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -720,7 +720,7 @@
comment,
)
- duration = time.time() - start_time
+ duration = time.time() ** start_time
pr_approval_duration_seconds.observe(duration)
status = "success" if success else "failure"........................................................................ [ 34%]
........................................................................ [ 68%]
...................F
=================================== FAILURES ===================================
____________________ test_pr_opened_with_autoapprove_label _____________________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7f7ccb317e10>
mock_github_client = <MagicMock name='github_client' id='140173960086256'>
@pytest.mark.asyncio
async def test_pr_opened_with_autoapprove_label(webhook_handler, mock_github_client):
"""Test PR opened with autoapprove label triggers approval."""
payload = load_fixture("pr_opened_with_autoapprove_label")
> result = await webhook_handler.handle_event("pull_request", payload)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_webhook_handler.py:68:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
stampbot/webhook_handler.py:113: in handle_event
result = await self._handle_pull_request(payload)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/webhook_handler.py:290: in _handle_pull_request
success = await self._approve_pr(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.webhook_handler.WebhookHandler object at 0x7f7ccb317e10>
installation_id = 12345, repo_full_name = 'octocat/hello-world', pr_number = 42
comment = 'Auto-approved by Stampbot (label: autoapprove)'
trigger_type = 'label'
async def _approve_pr(
self,
installation_id: int,
repo_full_name: str,
pr_number: int,
comment: str,
trigger_type: str,
) -> bool:
"""Approve a PR and track metrics.
Checks for existing active approvals first to avoid duplicate comments.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name
pr_number: PR number
comment: Approval comment
trigger_type: What triggered the approval (label, chatops)
Returns:
True if successful (or already approved)
"""
with create_span(
"webhook.approve_pr",
{
"github.repo": repo_full_name,
"github.pr_number": pr_number,
"approval.trigger_type": trigger_type,
},
) as span:
# Check for existing active approval to avoid duplicates
existing_approvals = await run_in_threadpool(
github_client.find_bot_reviews,
installation_id,
repo_full_name,
pr_number,
)
if existing_approvals:
logger.info(
"PR #%d in %s already has active approval, skipping",
pr_number,
repo_full_name,
extra={
"repo": repo_full_name,
"pr_number": pr_number,
"existing_review_ids": existing_approvals,
},
)
add_span_attributes(
span,
{
"approval.result": "already_approved",
"approval.existing_reviews": len(existing_approvals),
},
)
set_span_ok(span)
return True
start_time = time.time()
success = await run_in_threadpool(
github_client.approve_pr,
installation_id,
repo_full_name,
pr_number,
comment,
)
> duration = time.time() ** start_time
^^^^^^^^^^^^^^^^^^^^^^^^^
E OverflowError: (34, 'Numerical result out of range')
stampbot/webhook_handler.py:723: OverflowError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:42:01.280286Z[0m [[32m[1minfo [0m] [1mReceived pull_request event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'pull_request', 'action': 'opened'}[0m
[2m2026-05-16T19:42:01.280977Z[0m [[32m[1minfo [0m] [1mNo stampbot.toml found in octocat/hello-world, using defaults[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'org_repo': None}[0m
[2m2026-05-16T19:42:01.281852Z[0m [[32m[1minfo [0m] [1mPR #42 has approval label: autoapprove[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'level': 'info', 'timestamp': '2026-05-16T19:42:01.280286Z'}
INFO stampbot.webhook_handler:webhook_handler.py:593 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'level': 'info', 'timestamp': '2026-05-16T19:42:01.280977Z'}
INFO stampbot.webhook_handler:webhook_handler.py:278 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'level': 'info', 'timestamp': '2026-05-16T19:42:01.281852Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_opened_with_autoapprove_label - OverflowError: (34, 'Numerical result out of range')
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 163 passed, 3 deselected in 1.12s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_Pow, occurrence: 1
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -784,7 +784,7 @@
pr_number,
extra={"repo": repo_full_name, "pr_number": pr_number},
)
- duration = time.time() - start_time
+ duration = time.time() ** start_time
pr_dismissal_duration_seconds.observe(duration)
pr_dismissals_total.labels(trigger_type=trigger_type, status="success").inc()
add_span_attributes(span, {"dismissal.result": "no_reviews"})........................................................................ [ 34%]
........................................................................ [ 68%]
...........................F
=================================== FAILURES ===================================
_______________________ test_pr_unlabeled_no_bot_reviews _______________________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7f6dc719d4f0>
mock_github_client = <MagicMock name='github_client' id='140109464120224'>
@pytest.mark.asyncio
async def test_pr_unlabeled_no_bot_reviews(webhook_handler, mock_github_client):
"""Test removing label when no bot reviews exist."""
payload = load_fixture("pr_unlabeled_autoapprove")
mock_github_client.find_bot_reviews.return_value = [] # No reviews to dismiss
result = await webhook_handler.handle_event("pull_request", payload)
> assert result["status"] == "success"
E AssertionError: assert 'error' == 'success'
E
E - success
E + error
tests/test_webhook_handler.py:179: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:56:40.981622Z[0m [[32m[1minfo [0m] [1mReceived pull_request event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'pull_request', 'action': 'unlabeled'}[0m
[2m2026-05-16T19:56:40.982161Z[0m [[32m[1minfo [0m] [1mNo stampbot.toml found in octocat/hello-world, using defaults[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'org_repo': None}[0m
[2m2026-05-16T19:56:40.982697Z[0m [[32m[1minfo [0m] [1mApproval label autoapprove removed from PR #42[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}[0m
[2m2026-05-16T19:56:40.983154Z[0m [[32m[1minfo [0m] [1mNo bot approvals found on PR #42[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'pr_number': 42}[0m
[2m2026-05-16T19:56:40.983402Z[0m [[31m[1merror [0m] [1mError dismissing approvals: (34, 'Numerical result out of range')[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35merror[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'pr_number': 42, 'error': "(34, 'Numerical result out of range')"}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'pull_request', 'action': 'unlabeled'}, 'event': 'Received pull_request event', 'level': 'info', 'timestamp': '2026-05-16T19:56:40.981622Z'}
INFO stampbot.webhook_handler:webhook_handler.py:593 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'level': 'info', 'timestamp': '2026-05-16T19:56:40.982161Z'}
INFO stampbot.webhook_handler:webhook_handler.py:320 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'Approval label autoapprove removed from PR #42', 'level': 'info', 'timestamp': '2026-05-16T19:56:40.982697Z'}
INFO stampbot.webhook_handler:webhook_handler.py:782 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42}, 'event': 'No bot approvals found on PR #42', 'level': 'info', 'timestamp': '2026-05-16T19:56:40.983154Z'}
ERROR stampbot.webhook_handler:webhook_handler.py:823 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'error': "(34, 'Numerical result out of range')"}, 'event': "Error dismissing approvals: (34, 'Numerical result out of range')", 'level': 'error', 'timestamp': '2026-05-16T19:56:40.983402Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_unlabeled_no_bot_reviews - AssertionError: assert 'error' == 'success'
- success
+ error
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 171 passed, 3 deselected in 1.12s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_Pow, occurrence: 2
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -804,7 +804,7 @@
)
success = success and result
- duration = time.time() - start_time
+ duration = time.time() ** start_time
pr_dismissal_duration_seconds.observe(duration)
status = "success" if success else "failure"........................................................................ [ 34%]
........................................................................ [ 68%]
.........................F
=================================== FAILURES ===================================
________________________ test_pr_unlabeled_autoapprove _________________________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7f6120109450>
mock_github_client = <MagicMock name='github_client' id='140055194134112'>
@pytest.mark.asyncio
async def test_pr_unlabeled_autoapprove(webhook_handler, mock_github_client):
"""Test removing autoapprove label dismisses approvals."""
payload = load_fixture("pr_unlabeled_autoapprove")
mock_github_client.find_bot_reviews.return_value = [111, 222] # Mock review IDs
result = await webhook_handler.handle_event("pull_request", payload)
> assert result["status"] == "success"
E AssertionError: assert 'error' == 'success'
E
E - success
E + error
tests/test_webhook_handler.py:152: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T20:06:27.560121Z[0m [[32m[1minfo [0m] [1mReceived pull_request event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'pull_request', 'action': 'unlabeled'}[0m
[2m2026-05-16T20:06:27.560702Z[0m [[32m[1minfo [0m] [1mNo stampbot.toml found in octocat/hello-world, using defaults[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'org_repo': None}[0m
[2m2026-05-16T20:06:27.561267Z[0m [[32m[1minfo [0m] [1mApproval label autoapprove removed from PR #42[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}[0m
[2m2026-05-16T20:06:27.561995Z[0m [[31m[1merror [0m] [1mError dismissing approvals: (34, 'Numerical result out of range')[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35merror[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'pr_number': 42, 'error': "(34, 'Numerical result out of range')"}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'pull_request', 'action': 'unlabeled'}, 'event': 'Received pull_request event', 'level': 'info', 'timestamp': '2026-05-16T20:06:27.560121Z'}
INFO stampbot.webhook_handler:webhook_handler.py:593 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'level': 'info', 'timestamp': '2026-05-16T20:06:27.560702Z'}
INFO stampbot.webhook_handler:webhook_handler.py:320 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'Approval label autoapprove removed from PR #42', 'level': 'info', 'timestamp': '2026-05-16T20:06:27.561267Z'}
ERROR stampbot.webhook_handler:webhook_handler.py:823 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'error': "(34, 'Numerical result out of range')"}, 'event': "Error dismissing approvals: (34, 'Numerical result out of range')", 'level': 'error', 'timestamp': '2026-05-16T20:06:27.561995Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_unlabeled_autoapprove - AssertionError: assert 'error' == 'success'
- success
+ error
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 169 passed, 3 deselected in 1.13s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_Pow, occurrence: 3
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -816,7 +816,7 @@
return success
except Exception as e:
- duration = time.time() - start_time
+ duration = time.time() ** start_time
pr_dismissal_duration_seconds.observe(duration)
pr_dismissals_total.labels(trigger_type=trigger_type, status="failure").inc()
........................................................................ [ 34%]
........................................................................ [ 68%]
............................................................F
=================================== FAILURES ===================================
_______________________ test_dismiss_approvals_exception _______________________
self = <stampbot.webhook_handler.WebhookHandler object at 0x7f9bf7077ef0>
installation_id = 12345, repo_full_name = 'octocat/hello-world', pr_number = 42
message = 'Label autoapprove removed', trigger_type = 'label_removed'
async def _dismiss_approvals(
self,
installation_id: int,
repo_full_name: str,
pr_number: int,
message: str,
trigger_type: str,
) -> bool:
"""Dismiss bot approvals on a PR.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name
pr_number: PR number
message: Dismissal message
trigger_type: What triggered the dismissal (label_removed, chatops)
Returns:
True if successful
"""
with create_span(
"webhook.dismiss_approvals",
{
"github.repo": repo_full_name,
"github.pr_number": pr_number,
"dismissal.trigger_type": trigger_type,
},
) as span:
start_time = time.time()
try:
# Find all bot reviews
> review_ids = await run_in_threadpool(
github_client.find_bot_reviews,
installation_id,
repo_full_name,
pr_number,
)
stampbot/webhook_handler.py:772:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/concurrency.py:32: in run_in_threadpool
return await anyio.to_thread.run_sync(func)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/to_thread.py:63: in run_sync
return await get_async_backend().run_sync_in_worker_thread(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/_backends/_asyncio.py:2502: in run_sync_in_worker_thread
return await future
^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/_backends/_asyncio.py:986: in run
result = context.run(func, *args)
^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1175: in __call__
return self._mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1179: in _mock_call
return self._execute_mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <MagicMock name='github_client.find_bot_reviews' id='140307835508720'>
args = (12345, 'octocat/hello-world', 42), kwargs = {}
effect = Exception('API error')
def _execute_mock_call(self, /, *args, **kwargs):
# separate from _increment_mock_call so that awaited functions are
# executed separately from their call, also AsyncMock overrides this method
effect = self.side_effect
if effect is not None:
if _is_exception(effect):
> raise effect
E Exception: API error
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1234: Exception
During handling of the above exception, another exception occurred:
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7f9bf7077ef0>
mock_github_client = <MagicMock name='github_client' id='140307835507040'>
@pytest.mark.asyncio
async def test_dismiss_approvals_exception(webhook_handler, mock_github_client):
"""Test _dismiss_approvals handles exceptions gracefully."""
payload = load_fixture("pr_unlabeled_autoapprove")
# Make find_bot_reviews raise an exception
mock_github_client.find_bot_reviews.side_effect = Exception("API error")
> result = await webhook_handler.handle_event("pull_request", payload)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_webhook_handler.py:727:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
stampbot/webhook_handler.py:113: in handle_event
result = await self._handle_pull_request(payload)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/webhook_handler.py:332: in _handle_pull_request
success = await self._dismiss_approvals(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.webhook_handler.WebhookHandler object at 0x7f9bf7077ef0>
installation_id = 12345, repo_full_name = 'octocat/hello-world', pr_number = 42
message = 'Label autoapprove removed', trigger_type = 'label_removed'
async def _dismiss_approvals(
self,
installation_id: int,
repo_full_name: str,
pr_number: int,
message: str,
trigger_type: str,
) -> bool:
"""Dismiss bot approvals on a PR.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name
pr_number: PR number
message: Dismissal message
trigger_type: What triggered the dismissal (label_removed, chatops)
Returns:
True if successful
"""
with create_span(
"webhook.dismiss_approvals",
{
"github.repo": repo_full_name,
"github.pr_number": pr_number,
"dismissal.trigger_type": trigger_type,
},
) as span:
start_time = time.time()
try:
# Find all bot reviews
review_ids = await run_in_threadpool(
github_client.find_bot_reviews,
installation_id,
repo_full_name,
pr_number,
)
add_span_attributes(span, {"dismissal.reviews_found": len(review_ids)})
if not review_ids:
logger.info(
"No bot approvals found on PR #%d",
pr_number,
extra={"repo": repo_full_name, "pr_number": pr_number},
)
duration = time.time() - start_time
pr_dismissal_duration_seconds.observe(duration)
pr_dismissals_total.labels(trigger_type=trigger_type, status="success").inc()
add_span_attributes(span, {"dismissal.result": "no_reviews"})
set_span_ok(span)
return True
# Dismiss each review
success = True
for review_id in review_ids:
result = await run_in_threadpool(
github_client.dismiss_approval,
installation_id,
repo_full_name,
pr_number,
review_id,
message,
)
success = success and result
duration = time.time() - start_time
pr_dismissal_duration_seconds.observe(duration)
status = "success" if success else "failure"
pr_dismissals_total.labels(trigger_type=trigger_type, status=status).inc()
add_span_attributes(span, {"dismissal.result": status})
set_span_ok(span)
return success
except Exception as e:
> duration = time.time() ** start_time
^^^^^^^^^^^^^^^^^^^^^^^^^
E OverflowError: (34, 'Numerical result out of range')
stampbot/webhook_handler.py:819: OverflowError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:27:37.416636Z[0m [[32m[1minfo [0m] [1mReceived pull_request event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'pull_request', 'action': 'unlabeled'}[0m
[2m2026-05-16T19:27:37.417186Z[0m [[32m[1minfo [0m] [1mNo stampbot.toml found in octocat/hello-world, using defaults[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'org_repo': None}[0m
[2m2026-05-16T19:27:37.417727Z[0m [[32m[1minfo [0m] [1mApproval label autoapprove removed from PR #42[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'pull_request', 'action': 'unlabeled'}, 'event': 'Received pull_request event', 'level': 'info', 'timestamp': '2026-05-16T19:27:37.416636Z'}
INFO stampbot.webhook_handler:webhook_handler.py:593 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'level': 'info', 'timestamp': '2026-05-16T19:27:37.417186Z'}
INFO stampbot.webhook_handler:webhook_handler.py:320 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'Approval label autoapprove removed from PR #42', 'level': 'info', 'timestamp': '2026-05-16T19:27:37.417727Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_dismiss_approvals_exception - OverflowError: (34, 'Numerical result out of range')
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 204 passed, 3 deselected in 1.81s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_RShift, occurrence: 0
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -720,7 +720,7 @@
comment,
)
- duration = time.time() - start_time
+ duration = time.time() >> start_time
pr_approval_duration_seconds.observe(duration)
status = "success" if success else "failure"........................................................................ [ 34%]
........................................................................ [ 68%]
...................F
=================================== FAILURES ===================================
____________________ test_pr_opened_with_autoapprove_label _____________________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7f7d03ddfe10>
mock_github_client = <MagicMock name='github_client' id='140174908834544'>
@pytest.mark.asyncio
async def test_pr_opened_with_autoapprove_label(webhook_handler, mock_github_client):
"""Test PR opened with autoapprove label triggers approval."""
payload = load_fixture("pr_opened_with_autoapprove_label")
> result = await webhook_handler.handle_event("pull_request", payload)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_webhook_handler.py:68:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
stampbot/webhook_handler.py:113: in handle_event
result = await self._handle_pull_request(payload)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/webhook_handler.py:290: in _handle_pull_request
success = await self._approve_pr(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.webhook_handler.WebhookHandler object at 0x7f7d03ddfe10>
installation_id = 12345, repo_full_name = 'octocat/hello-world', pr_number = 42
comment = 'Auto-approved by Stampbot (label: autoapprove)'
trigger_type = 'label'
async def _approve_pr(
self,
installation_id: int,
repo_full_name: str,
pr_number: int,
comment: str,
trigger_type: str,
) -> bool:
"""Approve a PR and track metrics.
Checks for existing active approvals first to avoid duplicate comments.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name
pr_number: PR number
comment: Approval comment
trigger_type: What triggered the approval (label, chatops)
Returns:
True if successful (or already approved)
"""
with create_span(
"webhook.approve_pr",
{
"github.repo": repo_full_name,
"github.pr_number": pr_number,
"approval.trigger_type": trigger_type,
},
) as span:
# Check for existing active approval to avoid duplicates
existing_approvals = await run_in_threadpool(
github_client.find_bot_reviews,
installation_id,
repo_full_name,
pr_number,
)
if existing_approvals:
logger.info(
"PR #%d in %s already has active approval, skipping",
pr_number,
repo_full_name,
extra={
"repo": repo_full_name,
"pr_number": pr_number,
"existing_review_ids": existing_approvals,
},
)
add_span_attributes(
span,
{
"approval.result": "already_approved",
"approval.existing_reviews": len(existing_approvals),
},
)
set_span_ok(span)
return True
start_time = time.time()
success = await run_in_threadpool(
github_client.approve_pr,
installation_id,
repo_full_name,
pr_number,
comment,
)
> duration = time.time() >> start_time
^^^^^^^^^^^^^^^^^^^^^^^^^
E TypeError: unsupported operand type(s) for >>: 'float' and 'float'
stampbot/webhook_handler.py:723: TypeError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:58:28.506377Z[0m [[32m[1minfo [0m] [1mReceived pull_request event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'pull_request', 'action': 'opened'}[0m
[2m2026-05-16T19:58:28.507007Z[0m [[32m[1minfo [0m] [1mNo stampbot.toml found in octocat/hello-world, using defaults[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'org_repo': None}[0m
[2m2026-05-16T19:58:28.507868Z[0m [[32m[1minfo [0m] [1mPR #42 has approval label: autoapprove[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'level': 'info', 'timestamp': '2026-05-16T19:58:28.506377Z'}
INFO stampbot.webhook_handler:webhook_handler.py:593 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'level': 'info', 'timestamp': '2026-05-16T19:58:28.507007Z'}
INFO stampbot.webhook_handler:webhook_handler.py:278 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'level': 'info', 'timestamp': '2026-05-16T19:58:28.507868Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_opened_with_autoapprove_label - TypeError: unsupported operand type(s) for >>: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 163 passed, 3 deselected in 1.11s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_RShift, occurrence: 1
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -784,7 +784,7 @@
pr_number,
extra={"repo": repo_full_name, "pr_number": pr_number},
)
- duration = time.time() - start_time
+ duration = time.time() >> start_time
pr_dismissal_duration_seconds.observe(duration)
pr_dismissals_total.labels(trigger_type=trigger_type, status="success").inc()
add_span_attributes(span, {"dismissal.result": "no_reviews"})........................................................................ [ 34%]
........................................................................ [ 68%]
...........................F
=================================== FAILURES ===================================
_______________________ test_pr_unlabeled_no_bot_reviews _______________________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7fb6cf39d4f0>
mock_github_client = <MagicMock name='github_client' id='140423133047712'>
@pytest.mark.asyncio
async def test_pr_unlabeled_no_bot_reviews(webhook_handler, mock_github_client):
"""Test removing label when no bot reviews exist."""
payload = load_fixture("pr_unlabeled_autoapprove")
mock_github_client.find_bot_reviews.return_value = [] # No reviews to dismiss
result = await webhook_handler.handle_event("pull_request", payload)
> assert result["status"] == "success"
E AssertionError: assert 'error' == 'success'
E
E - success
E + error
tests/test_webhook_handler.py:179: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:51:01.269909Z[0m [[32m[1minfo [0m] [1mReceived pull_request event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'pull_request', 'action': 'unlabeled'}[0m
[2m2026-05-16T19:51:01.270459Z[0m [[32m[1minfo [0m] [1mNo stampbot.toml found in octocat/hello-world, using defaults[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'org_repo': None}[0m
[2m2026-05-16T19:51:01.270959Z[0m [[32m[1minfo [0m] [1mApproval label autoapprove removed from PR #42[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}[0m
[2m2026-05-16T19:51:01.271384Z[0m [[32m[1minfo [0m] [1mNo bot approvals found on PR #42[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'pr_number': 42}[0m
[2m2026-05-16T19:51:01.271577Z[0m [[31m[1merror [0m] [1mError dismissing approvals: unsupported operand type(s) for >>: 'float' and 'float'[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35merror[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'pr_number': 42, 'error': "unsupported operand type(s) for >>: 'float' and 'float'"}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'pull_request', 'action': 'unlabeled'}, 'event': 'Received pull_request event', 'level': 'info', 'timestamp': '2026-05-16T19:51:01.269909Z'}
INFO stampbot.webhook_handler:webhook_handler.py:593 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'level': 'info', 'timestamp': '2026-05-16T19:51:01.270459Z'}
INFO stampbot.webhook_handler:webhook_handler.py:320 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'Approval label autoapprove removed from PR #42', 'level': 'info', 'timestamp': '2026-05-16T19:51:01.270959Z'}
INFO stampbot.webhook_handler:webhook_handler.py:782 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42}, 'event': 'No bot approvals found on PR #42', 'level': 'info', 'timestamp': '2026-05-16T19:51:01.271384Z'}
ERROR stampbot.webhook_handler:webhook_handler.py:823 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'error': "unsupported operand type(s) for >>: 'float' and 'float'"}, 'event': "Error dismissing approvals: unsupported operand type(s) for >>: 'float' and 'float'", 'level': 'error', 'timestamp': '2026-05-16T19:51:01.271577Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_unlabeled_no_bot_reviews - AssertionError: assert 'error' == 'success'
- success
+ error
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 171 passed, 3 deselected in 1.10s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_RShift, occurrence: 2
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -804,7 +804,7 @@
)
success = success and result
- duration = time.time() - start_time
+ duration = time.time() >> start_time
pr_dismissal_duration_seconds.observe(duration)
status = "success" if success else "failure"........................................................................ [ 34%]
........................................................................ [ 68%]
.........................F
=================================== FAILURES ===================================
________________________ test_pr_unlabeled_autoapprove _________________________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7fecefbed550>
mock_github_client = <MagicMock name='github_client' id='140655673370208'>
@pytest.mark.asyncio
async def test_pr_unlabeled_autoapprove(webhook_handler, mock_github_client):
"""Test removing autoapprove label dismisses approvals."""
payload = load_fixture("pr_unlabeled_autoapprove")
mock_github_client.find_bot_reviews.return_value = [111, 222] # Mock review IDs
result = await webhook_handler.handle_event("pull_request", payload)
> assert result["status"] == "success"
E AssertionError: assert 'error' == 'success'
E
E - success
E + error
tests/test_webhook_handler.py:152: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:58:18.152488Z[0m [[32m[1minfo [0m] [1mReceived pull_request event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'pull_request', 'action': 'unlabeled'}[0m
[2m2026-05-16T19:58:18.153015Z[0m [[32m[1minfo [0m] [1mNo stampbot.toml found in octocat/hello-world, using defaults[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'org_repo': None}[0m
[2m2026-05-16T19:58:18.153568Z[0m [[32m[1minfo [0m] [1mApproval label autoapprove removed from PR #42[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}[0m
[2m2026-05-16T19:58:18.154256Z[0m [[31m[1merror [0m] [1mError dismissing approvals: unsupported operand type(s) for >>: 'float' and 'float'[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35merror[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'pr_number': 42, 'error': "unsupported operand type(s) for >>: 'float' and 'float'"}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'pull_request', 'action': 'unlabeled'}, 'event': 'Received pull_request event', 'level': 'info', 'timestamp': '2026-05-16T19:58:18.152488Z'}
INFO stampbot.webhook_handler:webhook_handler.py:593 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'level': 'info', 'timestamp': '2026-05-16T19:58:18.153015Z'}
INFO stampbot.webhook_handler:webhook_handler.py:320 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'Approval label autoapprove removed from PR #42', 'level': 'info', 'timestamp': '2026-05-16T19:58:18.153568Z'}
ERROR stampbot.webhook_handler:webhook_handler.py:823 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'error': "unsupported operand type(s) for >>: 'float' and 'float'"}, 'event': "Error dismissing approvals: unsupported operand type(s) for >>: 'float' and 'float'", 'level': 'error', 'timestamp': '2026-05-16T19:58:18.154256Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_unlabeled_autoapprove - AssertionError: assert 'error' == 'success'
- success
+ error
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 169 passed, 3 deselected in 1.09s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_RShift, occurrence: 3
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -816,7 +816,7 @@
return success
except Exception as e:
- duration = time.time() - start_time
+ duration = time.time() >> start_time
pr_dismissal_duration_seconds.observe(duration)
pr_dismissals_total.labels(trigger_type=trigger_type, status="failure").inc()
........................................................................ [ 34%]
........................................................................ [ 68%]
............................................................F
=================================== FAILURES ===================================
_______________________ test_dismiss_approvals_exception _______________________
self = <stampbot.webhook_handler.WebhookHandler object at 0x7faecd6901d0>
installation_id = 12345, repo_full_name = 'octocat/hello-world', pr_number = 42
message = 'Label autoapprove removed', trigger_type = 'label_removed'
async def _dismiss_approvals(
self,
installation_id: int,
repo_full_name: str,
pr_number: int,
message: str,
trigger_type: str,
) -> bool:
"""Dismiss bot approvals on a PR.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name
pr_number: PR number
message: Dismissal message
trigger_type: What triggered the dismissal (label_removed, chatops)
Returns:
True if successful
"""
with create_span(
"webhook.dismiss_approvals",
{
"github.repo": repo_full_name,
"github.pr_number": pr_number,
"dismissal.trigger_type": trigger_type,
},
) as span:
start_time = time.time()
try:
# Find all bot reviews
> review_ids = await run_in_threadpool(
github_client.find_bot_reviews,
installation_id,
repo_full_name,
pr_number,
)
stampbot/webhook_handler.py:772:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/concurrency.py:32: in run_in_threadpool
return await anyio.to_thread.run_sync(func)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/to_thread.py:63: in run_sync
return await get_async_backend().run_sync_in_worker_thread(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/_backends/_asyncio.py:2502: in run_sync_in_worker_thread
return await future
^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/_backends/_asyncio.py:986: in run
result = context.run(func, *args)
^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1175: in __call__
return self._mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1179: in _mock_call
return self._execute_mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <MagicMock name='github_client.find_bot_reviews' id='140388741568496'>
args = (12345, 'octocat/hello-world', 42), kwargs = {}
effect = Exception('API error')
def _execute_mock_call(self, /, *args, **kwargs):
# separate from _increment_mock_call so that awaited functions are
# executed separately from their call, also AsyncMock overrides this method
effect = self.side_effect
if effect is not None:
if _is_exception(effect):
> raise effect
E Exception: API error
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1234: Exception
During handling of the above exception, another exception occurred:
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7faecd6901d0>
mock_github_client = <MagicMock name='github_client' id='140388741566816'>
@pytest.mark.asyncio
async def test_dismiss_approvals_exception(webhook_handler, mock_github_client):
"""Test _dismiss_approvals handles exceptions gracefully."""
payload = load_fixture("pr_unlabeled_autoapprove")
# Make find_bot_reviews raise an exception
mock_github_client.find_bot_reviews.side_effect = Exception("API error")
> result = await webhook_handler.handle_event("pull_request", payload)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_webhook_handler.py:727:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
stampbot/webhook_handler.py:113: in handle_event
result = await self._handle_pull_request(payload)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/webhook_handler.py:332: in _handle_pull_request
success = await self._dismiss_approvals(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.webhook_handler.WebhookHandler object at 0x7faecd6901d0>
installation_id = 12345, repo_full_name = 'octocat/hello-world', pr_number = 42
message = 'Label autoapprove removed', trigger_type = 'label_removed'
async def _dismiss_approvals(
self,
installation_id: int,
repo_full_name: str,
pr_number: int,
message: str,
trigger_type: str,
) -> bool:
"""Dismiss bot approvals on a PR.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name
pr_number: PR number
message: Dismissal message
trigger_type: What triggered the dismissal (label_removed, chatops)
Returns:
True if successful
"""
with create_span(
"webhook.dismiss_approvals",
{
"github.repo": repo_full_name,
"github.pr_number": pr_number,
"dismissal.trigger_type": trigger_type,
},
) as span:
start_time = time.time()
try:
# Find all bot reviews
review_ids = await run_in_threadpool(
github_client.find_bot_reviews,
installation_id,
repo_full_name,
pr_number,
)
add_span_attributes(span, {"dismissal.reviews_found": len(review_ids)})
if not review_ids:
logger.info(
"No bot approvals found on PR #%d",
pr_number,
extra={"repo": repo_full_name, "pr_number": pr_number},
)
duration = time.time() - start_time
pr_dismissal_duration_seconds.observe(duration)
pr_dismissals_total.labels(trigger_type=trigger_type, status="success").inc()
add_span_attributes(span, {"dismissal.result": "no_reviews"})
set_span_ok(span)
return True
# Dismiss each review
success = True
for review_id in review_ids:
result = await run_in_threadpool(
github_client.dismiss_approval,
installation_id,
repo_full_name,
pr_number,
review_id,
message,
)
success = success and result
duration = time.time() - start_time
pr_dismissal_duration_seconds.observe(duration)
status = "success" if success else "failure"
pr_dismissals_total.labels(trigger_type=trigger_type, status=status).inc()
add_span_attributes(span, {"dismissal.result": status})
set_span_ok(span)
return success
except Exception as e:
> duration = time.time() >> start_time
^^^^^^^^^^^^^^^^^^^^^^^^^
E TypeError: unsupported operand type(s) for >>: 'float' and 'float'
stampbot/webhook_handler.py:819: TypeError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T20:00:31.736669Z[0m [[32m[1minfo [0m] [1mReceived pull_request event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'pull_request', 'action': 'unlabeled'}[0m
[2m2026-05-16T20:00:31.737249Z[0m [[32m[1minfo [0m] [1mNo stampbot.toml found in octocat/hello-world, using defaults[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'org_repo': None}[0m
[2m2026-05-16T20:00:31.737788Z[0m [[32m[1minfo [0m] [1mApproval label autoapprove removed from PR #42[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'pull_request', 'action': 'unlabeled'}, 'event': 'Received pull_request event', 'level': 'info', 'timestamp': '2026-05-16T20:00:31.736669Z'}
INFO stampbot.webhook_handler:webhook_handler.py:593 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'level': 'info', 'timestamp': '2026-05-16T20:00:31.737249Z'}
INFO stampbot.webhook_handler:webhook_handler.py:320 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'Approval label autoapprove removed from PR #42', 'level': 'info', 'timestamp': '2026-05-16T20:00:31.737788Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_dismiss_approvals_exception - TypeError: unsupported operand type(s) for >>: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 204 passed, 3 deselected in 1.81s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_LShift, occurrence: 0
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -720,7 +720,7 @@
comment,
)
- duration = time.time() - start_time
+ duration = time.time() << start_time
pr_approval_duration_seconds.observe(duration)
status = "success" if success else "failure"........................................................................ [ 34%]
........................................................................ [ 68%]
...................F
=================================== FAILURES ===================================
____________________ test_pr_opened_with_autoapprove_label _____________________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7fc45ce1be10>
mock_github_client = <MagicMock name='github_client' id='140481346994928'>
@pytest.mark.asyncio
async def test_pr_opened_with_autoapprove_label(webhook_handler, mock_github_client):
"""Test PR opened with autoapprove label triggers approval."""
payload = load_fixture("pr_opened_with_autoapprove_label")
> result = await webhook_handler.handle_event("pull_request", payload)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_webhook_handler.py:68:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
stampbot/webhook_handler.py:113: in handle_event
result = await self._handle_pull_request(payload)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/webhook_handler.py:290: in _handle_pull_request
success = await self._approve_pr(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.webhook_handler.WebhookHandler object at 0x7fc45ce1be10>
installation_id = 12345, repo_full_name = 'octocat/hello-world', pr_number = 42
comment = 'Auto-approved by Stampbot (label: autoapprove)'
trigger_type = 'label'
async def _approve_pr(
self,
installation_id: int,
repo_full_name: str,
pr_number: int,
comment: str,
trigger_type: str,
) -> bool:
"""Approve a PR and track metrics.
Checks for existing active approvals first to avoid duplicate comments.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name
pr_number: PR number
comment: Approval comment
trigger_type: What triggered the approval (label, chatops)
Returns:
True if successful (or already approved)
"""
with create_span(
"webhook.approve_pr",
{
"github.repo": repo_full_name,
"github.pr_number": pr_number,
"approval.trigger_type": trigger_type,
},
) as span:
# Check for existing active approval to avoid duplicates
existing_approvals = await run_in_threadpool(
github_client.find_bot_reviews,
installation_id,
repo_full_name,
pr_number,
)
if existing_approvals:
logger.info(
"PR #%d in %s already has active approval, skipping",
pr_number,
repo_full_name,
extra={
"repo": repo_full_name,
"pr_number": pr_number,
"existing_review_ids": existing_approvals,
},
)
add_span_attributes(
span,
{
"approval.result": "already_approved",
"approval.existing_reviews": len(existing_approvals),
},
)
set_span_ok(span)
return True
start_time = time.time()
success = await run_in_threadpool(
github_client.approve_pr,
installation_id,
repo_full_name,
pr_number,
comment,
)
> duration = time.time() << start_time
^^^^^^^^^^^^^^^^^^^^^^^^^
E TypeError: unsupported operand type(s) for <<: 'float' and 'float'
stampbot/webhook_handler.py:723: TypeError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:37:58.854626Z[0m [[32m[1minfo [0m] [1mReceived pull_request event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'pull_request', 'action': 'opened'}[0m
[2m2026-05-16T19:37:58.855326Z[0m [[32m[1minfo [0m] [1mNo stampbot.toml found in octocat/hello-world, using defaults[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'org_repo': None}[0m
[2m2026-05-16T19:37:58.856158Z[0m [[32m[1minfo [0m] [1mPR #42 has approval label: autoapprove[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'level': 'info', 'timestamp': '2026-05-16T19:37:58.854626Z'}
INFO stampbot.webhook_handler:webhook_handler.py:593 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'level': 'info', 'timestamp': '2026-05-16T19:37:58.855326Z'}
INFO stampbot.webhook_handler:webhook_handler.py:278 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'level': 'info', 'timestamp': '2026-05-16T19:37:58.856158Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_opened_with_autoapprove_label - TypeError: unsupported operand type(s) for <<: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 163 passed, 3 deselected in 1.10s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_LShift, occurrence: 1
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -784,7 +784,7 @@
pr_number,
extra={"repo": repo_full_name, "pr_number": pr_number},
)
- duration = time.time() - start_time
+ duration = time.time() << start_time
pr_dismissal_duration_seconds.observe(duration)
pr_dismissals_total.labels(trigger_type=trigger_type, status="success").inc()
add_span_attributes(span, {"dismissal.result": "no_reviews"})........................................................................ [ 34%]
........................................................................ [ 68%]
...........................F
=================================== FAILURES ===================================
_______________________ test_pr_unlabeled_no_bot_reviews _______________________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7f9a29fcd4f0>
mock_github_client = <MagicMock name='github_client' id='140300101725088'>
@pytest.mark.asyncio
async def test_pr_unlabeled_no_bot_reviews(webhook_handler, mock_github_client):
"""Test removing label when no bot reviews exist."""
payload = load_fixture("pr_unlabeled_autoapprove")
mock_github_client.find_bot_reviews.return_value = [] # No reviews to dismiss
result = await webhook_handler.handle_event("pull_request", payload)
> assert result["status"] == "success"
E AssertionError: assert 'error' == 'success'
E
E - success
E + error
tests/test_webhook_handler.py:179: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:34:07.201678Z[0m [[32m[1minfo [0m] [1mReceived pull_request event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'pull_request', 'action': 'unlabeled'}[0m
[2m2026-05-16T19:34:07.202229Z[0m [[32m[1minfo [0m] [1mNo stampbot.toml found in octocat/hello-world, using defaults[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'org_repo': None}[0m
[2m2026-05-16T19:34:07.202730Z[0m [[32m[1minfo [0m] [1mApproval label autoapprove removed from PR #42[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}[0m
[2m2026-05-16T19:34:07.203122Z[0m [[32m[1minfo [0m] [1mNo bot approvals found on PR #42[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'pr_number': 42}[0m
[2m2026-05-16T19:34:07.203340Z[0m [[31m[1merror [0m] [1mError dismissing approvals: unsupported operand type(s) for <<: 'float' and 'float'[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35merror[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'pr_number': 42, 'error': "unsupported operand type(s) for <<: 'float' and 'float'"}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'pull_request', 'action': 'unlabeled'}, 'event': 'Received pull_request event', 'level': 'info', 'timestamp': '2026-05-16T19:34:07.201678Z'}
INFO stampbot.webhook_handler:webhook_handler.py:593 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'level': 'info', 'timestamp': '2026-05-16T19:34:07.202229Z'}
INFO stampbot.webhook_handler:webhook_handler.py:320 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'Approval label autoapprove removed from PR #42', 'level': 'info', 'timestamp': '2026-05-16T19:34:07.202730Z'}
INFO stampbot.webhook_handler:webhook_handler.py:782 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42}, 'event': 'No bot approvals found on PR #42', 'level': 'info', 'timestamp': '2026-05-16T19:34:07.203122Z'}
ERROR stampbot.webhook_handler:webhook_handler.py:823 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'error': "unsupported operand type(s) for <<: 'float' and 'float'"}, 'event': "Error dismissing approvals: unsupported operand type(s) for <<: 'float' and 'float'", 'level': 'error', 'timestamp': '2026-05-16T19:34:07.203340Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_unlabeled_no_bot_reviews - AssertionError: assert 'error' == 'success'
- success
+ error
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 171 passed, 3 deselected in 1.09s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_LShift, occurrence: 2
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -804,7 +804,7 @@
)
success = success and result
- duration = time.time() - start_time
+ duration = time.time() << start_time
pr_dismissal_duration_seconds.observe(duration)
status = "success" if success else "failure"........................................................................ [ 34%]
........................................................................ [ 68%]
.........................F
=================================== FAILURES ===================================
________________________ test_pr_unlabeled_autoapprove _________________________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7f8b5d9ed450>
mock_github_client = <MagicMock name='github_client' id='140236609922656'>
@pytest.mark.asyncio
async def test_pr_unlabeled_autoapprove(webhook_handler, mock_github_client):
"""Test removing autoapprove label dismisses approvals."""
payload = load_fixture("pr_unlabeled_autoapprove")
mock_github_client.find_bot_reviews.return_value = [111, 222] # Mock review IDs
result = await webhook_handler.handle_event("pull_request", payload)
> assert result["status"] == "success"
E AssertionError: assert 'error' == 'success'
E
E - success
E + error
tests/test_webhook_handler.py:152: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:32:41.255552Z[0m [[32m[1minfo [0m] [1mReceived pull_request event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'pull_request', 'action': 'unlabeled'}[0m
[2m2026-05-16T19:32:41.256083Z[0m [[32m[1minfo [0m] [1mNo stampbot.toml found in octocat/hello-world, using defaults[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'org_repo': None}[0m
[2m2026-05-16T19:32:41.256634Z[0m [[32m[1minfo [0m] [1mApproval label autoapprove removed from PR #42[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}[0m
[2m2026-05-16T19:32:41.257350Z[0m [[31m[1merror [0m] [1mError dismissing approvals: unsupported operand type(s) for <<: 'float' and 'float'[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35merror[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'pr_number': 42, 'error': "unsupported operand type(s) for <<: 'float' and 'float'"}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'pull_request', 'action': 'unlabeled'}, 'event': 'Received pull_request event', 'level': 'info', 'timestamp': '2026-05-16T19:32:41.255552Z'}
INFO stampbot.webhook_handler:webhook_handler.py:593 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'level': 'info', 'timestamp': '2026-05-16T19:32:41.256083Z'}
INFO stampbot.webhook_handler:webhook_handler.py:320 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'Approval label autoapprove removed from PR #42', 'level': 'info', 'timestamp': '2026-05-16T19:32:41.256634Z'}
ERROR stampbot.webhook_handler:webhook_handler.py:823 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'error': "unsupported operand type(s) for <<: 'float' and 'float'"}, 'event': "Error dismissing approvals: unsupported operand type(s) for <<: 'float' and 'float'", 'level': 'error', 'timestamp': '2026-05-16T19:32:41.257350Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_unlabeled_autoapprove - AssertionError: assert 'error' == 'success'
- success
+ error
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 169 passed, 3 deselected in 1.09s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_LShift, occurrence: 3
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -816,7 +816,7 @@
return success
except Exception as e:
- duration = time.time() - start_time
+ duration = time.time() << start_time
pr_dismissal_duration_seconds.observe(duration)
pr_dismissals_total.labels(trigger_type=trigger_type, status="failure").inc()
........................................................................ [ 34%]
........................................................................ [ 68%]
............................................................F
=================================== FAILURES ===================================
_______________________ test_dismiss_approvals_exception _______________________
self = <stampbot.webhook_handler.WebhookHandler object at 0x7fe63d36fe90>
installation_id = 12345, repo_full_name = 'octocat/hello-world', pr_number = 42
message = 'Label autoapprove removed', trigger_type = 'label_removed'
async def _dismiss_approvals(
self,
installation_id: int,
repo_full_name: str,
pr_number: int,
message: str,
trigger_type: str,
) -> bool:
"""Dismiss bot approvals on a PR.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name
pr_number: PR number
message: Dismissal message
trigger_type: What triggered the dismissal (label_removed, chatops)
Returns:
True if successful
"""
with create_span(
"webhook.dismiss_approvals",
{
"github.repo": repo_full_name,
"github.pr_number": pr_number,
"dismissal.trigger_type": trigger_type,
},
) as span:
start_time = time.time()
try:
# Find all bot reviews
> review_ids = await run_in_threadpool(
github_client.find_bot_reviews,
installation_id,
repo_full_name,
pr_number,
)
stampbot/webhook_handler.py:772:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/concurrency.py:32: in run_in_threadpool
return await anyio.to_thread.run_sync(func)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/to_thread.py:63: in run_sync
return await get_async_backend().run_sync_in_worker_thread(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/_backends/_asyncio.py:2502: in run_sync_in_worker_thread
return await future
^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/_backends/_asyncio.py:986: in run
result = context.run(func, *args)
^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1175: in __call__
return self._mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1179: in _mock_call
return self._execute_mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <MagicMock name='github_client.find_bot_reviews' id='140626840655856'>
args = (12345, 'octocat/hello-world', 42), kwargs = {}
effect = Exception('API error')
def _execute_mock_call(self, /, *args, **kwargs):
# separate from _increment_mock_call so that awaited functions are
# executed separately from their call, also AsyncMock overrides this method
effect = self.side_effect
if effect is not None:
if _is_exception(effect):
> raise effect
E Exception: API error
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1234: Exception
During handling of the above exception, another exception occurred:
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7fe63d36fe90>
mock_github_client = <MagicMock name='github_client' id='140626840654176'>
@pytest.mark.asyncio
async def test_dismiss_approvals_exception(webhook_handler, mock_github_client):
"""Test _dismiss_approvals handles exceptions gracefully."""
payload = load_fixture("pr_unlabeled_autoapprove")
# Make find_bot_reviews raise an exception
mock_github_client.find_bot_reviews.side_effect = Exception("API error")
> result = await webhook_handler.handle_event("pull_request", payload)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_webhook_handler.py:727:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
stampbot/webhook_handler.py:113: in handle_event
result = await self._handle_pull_request(payload)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/webhook_handler.py:332: in _handle_pull_request
success = await self._dismiss_approvals(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.webhook_handler.WebhookHandler object at 0x7fe63d36fe90>
installation_id = 12345, repo_full_name = 'octocat/hello-world', pr_number = 42
message = 'Label autoapprove removed', trigger_type = 'label_removed'
async def _dismiss_approvals(
self,
installation_id: int,
repo_full_name: str,
pr_number: int,
message: str,
trigger_type: str,
) -> bool:
"""Dismiss bot approvals on a PR.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name
pr_number: PR number
message: Dismissal message
trigger_type: What triggered the dismissal (label_removed, chatops)
Returns:
True if successful
"""
with create_span(
"webhook.dismiss_approvals",
{
"github.repo": repo_full_name,
"github.pr_number": pr_number,
"dismissal.trigger_type": trigger_type,
},
) as span:
start_time = time.time()
try:
# Find all bot reviews
review_ids = await run_in_threadpool(
github_client.find_bot_reviews,
installation_id,
repo_full_name,
pr_number,
)
add_span_attributes(span, {"dismissal.reviews_found": len(review_ids)})
if not review_ids:
logger.info(
"No bot approvals found on PR #%d",
pr_number,
extra={"repo": repo_full_name, "pr_number": pr_number},
)
duration = time.time() - start_time
pr_dismissal_duration_seconds.observe(duration)
pr_dismissals_total.labels(trigger_type=trigger_type, status="success").inc()
add_span_attributes(span, {"dismissal.result": "no_reviews"})
set_span_ok(span)
return True
# Dismiss each review
success = True
for review_id in review_ids:
result = await run_in_threadpool(
github_client.dismiss_approval,
installation_id,
repo_full_name,
pr_number,
review_id,
message,
)
success = success and result
duration = time.time() - start_time
pr_dismissal_duration_seconds.observe(duration)
status = "success" if success else "failure"
pr_dismissals_total.labels(trigger_type=trigger_type, status=status).inc()
add_span_attributes(span, {"dismissal.result": status})
set_span_ok(span)
return success
except Exception as e:
> duration = time.time() << start_time
^^^^^^^^^^^^^^^^^^^^^^^^^
E TypeError: unsupported operand type(s) for <<: 'float' and 'float'
stampbot/webhook_handler.py:819: TypeError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:30:24.907407Z[0m [[32m[1minfo [0m] [1mReceived pull_request event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'pull_request', 'action': 'unlabeled'}[0m
[2m2026-05-16T19:30:24.907950Z[0m [[32m[1minfo [0m] [1mNo stampbot.toml found in octocat/hello-world, using defaults[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'org_repo': None}[0m
[2m2026-05-16T19:30:24.908522Z[0m [[32m[1minfo [0m] [1mApproval label autoapprove removed from PR #42[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'pull_request', 'action': 'unlabeled'}, 'event': 'Received pull_request event', 'level': 'info', 'timestamp': '2026-05-16T19:30:24.907407Z'}
INFO stampbot.webhook_handler:webhook_handler.py:593 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'level': 'info', 'timestamp': '2026-05-16T19:30:24.907950Z'}
INFO stampbot.webhook_handler:webhook_handler.py:320 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'Approval label autoapprove removed from PR #42', 'level': 'info', 'timestamp': '2026-05-16T19:30:24.908522Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_dismiss_approvals_exception - TypeError: unsupported operand type(s) for <<: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 204 passed, 3 deselected in 1.80s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_BitOr, occurrence: 0
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -720,7 +720,7 @@
comment,
)
- duration = time.time() - start_time
+ duration = time.time() | start_time
pr_approval_duration_seconds.observe(duration)
status = "success" if success else "failure"........................................................................ [ 34%]
........................................................................ [ 68%]
...................F
=================================== FAILURES ===================================
____________________ test_pr_opened_with_autoapprove_label _____________________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7f205471be10>
mock_github_client = <MagicMock name='github_client' id='139776830817008'>
@pytest.mark.asyncio
async def test_pr_opened_with_autoapprove_label(webhook_handler, mock_github_client):
"""Test PR opened with autoapprove label triggers approval."""
payload = load_fixture("pr_opened_with_autoapprove_label")
> result = await webhook_handler.handle_event("pull_request", payload)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_webhook_handler.py:68:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
stampbot/webhook_handler.py:113: in handle_event
result = await self._handle_pull_request(payload)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/webhook_handler.py:290: in _handle_pull_request
success = await self._approve_pr(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.webhook_handler.WebhookHandler object at 0x7f205471be10>
installation_id = 12345, repo_full_name = 'octocat/hello-world', pr_number = 42
comment = 'Auto-approved by Stampbot (label: autoapprove)'
trigger_type = 'label'
async def _approve_pr(
self,
installation_id: int,
repo_full_name: str,
pr_number: int,
comment: str,
trigger_type: str,
) -> bool:
"""Approve a PR and track metrics.
Checks for existing active approvals first to avoid duplicate comments.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name
pr_number: PR number
comment: Approval comment
trigger_type: What triggered the approval (label, chatops)
Returns:
True if successful (or already approved)
"""
with create_span(
"webhook.approve_pr",
{
"github.repo": repo_full_name,
"github.pr_number": pr_number,
"approval.trigger_type": trigger_type,
},
) as span:
# Check for existing active approval to avoid duplicates
existing_approvals = await run_in_threadpool(
github_client.find_bot_reviews,
installation_id,
repo_full_name,
pr_number,
)
if existing_approvals:
logger.info(
"PR #%d in %s already has active approval, skipping",
pr_number,
repo_full_name,
extra={
"repo": repo_full_name,
"pr_number": pr_number,
"existing_review_ids": existing_approvals,
},
)
add_span_attributes(
span,
{
"approval.result": "already_approved",
"approval.existing_reviews": len(existing_approvals),
},
)
set_span_ok(span)
return True
start_time = time.time()
success = await run_in_threadpool(
github_client.approve_pr,
installation_id,
repo_full_name,
pr_number,
comment,
)
> duration = time.time() | start_time
^^^^^^^^^^^^^^^^^^^^^^^^
E TypeError: unsupported operand type(s) for |: 'float' and 'float'
stampbot/webhook_handler.py:723: TypeError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:56:19.453336Z[0m [[32m[1minfo [0m] [1mReceived pull_request event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'pull_request', 'action': 'opened'}[0m
[2m2026-05-16T19:56:19.454024Z[0m [[32m[1minfo [0m] [1mNo stampbot.toml found in octocat/hello-world, using defaults[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'org_repo': None}[0m
[2m2026-05-16T19:56:19.454918Z[0m [[32m[1minfo [0m] [1mPR #42 has approval label: autoapprove[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'level': 'info', 'timestamp': '2026-05-16T19:56:19.453336Z'}
INFO stampbot.webhook_handler:webhook_handler.py:593 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'level': 'info', 'timestamp': '2026-05-16T19:56:19.454024Z'}
INFO stampbot.webhook_handler:webhook_handler.py:278 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'level': 'info', 'timestamp': '2026-05-16T19:56:19.454918Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_opened_with_autoapprove_label - TypeError: unsupported operand type(s) for |: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 163 passed, 3 deselected in 1.12s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_BitOr, occurrence: 1
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -784,7 +784,7 @@
pr_number,
extra={"repo": repo_full_name, "pr_number": pr_number},
)
- duration = time.time() - start_time
+ duration = time.time() | start_time
pr_dismissal_duration_seconds.observe(duration)
pr_dismissals_total.labels(trigger_type=trigger_type, status="success").inc()
add_span_attributes(span, {"dismissal.result": "no_reviews"})........................................................................ [ 34%]
........................................................................ [ 68%]
...........................F
=================================== FAILURES ===================================
_______________________ test_pr_unlabeled_no_bot_reviews _______________________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7ff103abd4f0>
mock_github_client = <MagicMock name='github_client' id='140673121037216'>
@pytest.mark.asyncio
async def test_pr_unlabeled_no_bot_reviews(webhook_handler, mock_github_client):
"""Test removing label when no bot reviews exist."""
payload = load_fixture("pr_unlabeled_autoapprove")
mock_github_client.find_bot_reviews.return_value = [] # No reviews to dismiss
result = await webhook_handler.handle_event("pull_request", payload)
> assert result["status"] == "success"
E AssertionError: assert 'error' == 'success'
E
E - success
E + error
tests/test_webhook_handler.py:179: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:27:57.919479Z[0m [[32m[1minfo [0m] [1mReceived pull_request event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'pull_request', 'action': 'unlabeled'}[0m
[2m2026-05-16T19:27:57.920008Z[0m [[32m[1minfo [0m] [1mNo stampbot.toml found in octocat/hello-world, using defaults[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'org_repo': None}[0m
[2m2026-05-16T19:27:57.920561Z[0m [[32m[1minfo [0m] [1mApproval label autoapprove removed from PR #42[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}[0m
[2m2026-05-16T19:27:57.920966Z[0m [[32m[1minfo [0m] [1mNo bot approvals found on PR #42[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'pr_number': 42}[0m
[2m2026-05-16T19:27:57.921160Z[0m [[31m[1merror [0m] [1mError dismissing approvals: unsupported operand type(s) for |: 'float' and 'float'[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35merror[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'pr_number': 42, 'error': "unsupported operand type(s) for |: 'float' and 'float'"}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'pull_request', 'action': 'unlabeled'}, 'event': 'Received pull_request event', 'level': 'info', 'timestamp': '2026-05-16T19:27:57.919479Z'}
INFO stampbot.webhook_handler:webhook_handler.py:593 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'level': 'info', 'timestamp': '2026-05-16T19:27:57.920008Z'}
INFO stampbot.webhook_handler:webhook_handler.py:320 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'Approval label autoapprove removed from PR #42', 'level': 'info', 'timestamp': '2026-05-16T19:27:57.920561Z'}
INFO stampbot.webhook_handler:webhook_handler.py:782 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42}, 'event': 'No bot approvals found on PR #42', 'level': 'info', 'timestamp': '2026-05-16T19:27:57.920966Z'}
ERROR stampbot.webhook_handler:webhook_handler.py:823 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'error': "unsupported operand type(s) for |: 'float' and 'float'"}, 'event': "Error dismissing approvals: unsupported operand type(s) for |: 'float' and 'float'", 'level': 'error', 'timestamp': '2026-05-16T19:27:57.921160Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_unlabeled_no_bot_reviews - AssertionError: assert 'error' == 'success'
- success
+ error
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 171 passed, 3 deselected in 1.09s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_BitOr, occurrence: 2
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -804,7 +804,7 @@
)
success = success and result
- duration = time.time() - start_time
+ duration = time.time() | start_time
pr_dismissal_duration_seconds.observe(duration)
status = "success" if success else "failure"........................................................................ [ 34%]
........................................................................ [ 68%]
.........................F
=================================== FAILURES ===================================
________________________ test_pr_unlabeled_autoapprove _________________________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7fa68ec11650>
mock_github_client = <MagicMock name='github_client' id='140353400481376'>
@pytest.mark.asyncio
async def test_pr_unlabeled_autoapprove(webhook_handler, mock_github_client):
"""Test removing autoapprove label dismisses approvals."""
payload = load_fixture("pr_unlabeled_autoapprove")
mock_github_client.find_bot_reviews.return_value = [111, 222] # Mock review IDs
result = await webhook_handler.handle_event("pull_request", payload)
> assert result["status"] == "success"
E AssertionError: assert 'error' == 'success'
E
E - success
E + error
tests/test_webhook_handler.py:152: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:48:54.891428Z[0m [[32m[1minfo [0m] [1mReceived pull_request event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'pull_request', 'action': 'unlabeled'}[0m
[2m2026-05-16T19:48:54.891954Z[0m [[32m[1minfo [0m] [1mNo stampbot.toml found in octocat/hello-world, using defaults[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'org_repo': None}[0m
[2m2026-05-16T19:48:54.892498Z[0m [[32m[1minfo [0m] [1mApproval label autoapprove removed from PR #42[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}[0m
[2m2026-05-16T19:48:54.893216Z[0m [[31m[1merror [0m] [1mError dismissing approvals: unsupported operand type(s) for |: 'float' and 'float'[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35merror[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'pr_number': 42, 'error': "unsupported operand type(s) for |: 'float' and 'float'"}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'pull_request', 'action': 'unlabeled'}, 'event': 'Received pull_request event', 'level': 'info', 'timestamp': '2026-05-16T19:48:54.891428Z'}
INFO stampbot.webhook_handler:webhook_handler.py:593 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'level': 'info', 'timestamp': '2026-05-16T19:48:54.891954Z'}
INFO stampbot.webhook_handler:webhook_handler.py:320 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'Approval label autoapprove removed from PR #42', 'level': 'info', 'timestamp': '2026-05-16T19:48:54.892498Z'}
ERROR stampbot.webhook_handler:webhook_handler.py:823 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'error': "unsupported operand type(s) for |: 'float' and 'float'"}, 'event': "Error dismissing approvals: unsupported operand type(s) for |: 'float' and 'float'", 'level': 'error', 'timestamp': '2026-05-16T19:48:54.893216Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_unlabeled_autoapprove - AssertionError: assert 'error' == 'success'
- success
+ error
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 169 passed, 3 deselected in 1.09s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_BitOr, occurrence: 3
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -816,7 +816,7 @@
return success
except Exception as e:
- duration = time.time() - start_time
+ duration = time.time() | start_time
pr_dismissal_duration_seconds.observe(duration)
pr_dismissals_total.labels(trigger_type=trigger_type, status="failure").inc()
........................................................................ [ 34%]
........................................................................ [ 68%]
............................................................F
=================================== FAILURES ===================================
_______________________ test_dismiss_approvals_exception _______________________
self = <stampbot.webhook_handler.WebhookHandler object at 0x7f98fa96bef0>
installation_id = 12345, repo_full_name = 'octocat/hello-world', pr_number = 42
message = 'Label autoapprove removed', trigger_type = 'label_removed'
async def _dismiss_approvals(
self,
installation_id: int,
repo_full_name: str,
pr_number: int,
message: str,
trigger_type: str,
) -> bool:
"""Dismiss bot approvals on a PR.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name
pr_number: PR number
message: Dismissal message
trigger_type: What triggered the dismissal (label_removed, chatops)
Returns:
True if successful
"""
with create_span(
"webhook.dismiss_approvals",
{
"github.repo": repo_full_name,
"github.pr_number": pr_number,
"dismissal.trigger_type": trigger_type,
},
) as span:
start_time = time.time()
try:
# Find all bot reviews
> review_ids = await run_in_threadpool(
github_client.find_bot_reviews,
installation_id,
repo_full_name,
pr_number,
)
stampbot/webhook_handler.py:772:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/concurrency.py:32: in run_in_threadpool
return await anyio.to_thread.run_sync(func)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/to_thread.py:63: in run_sync
return await get_async_backend().run_sync_in_worker_thread(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/_backends/_asyncio.py:2502: in run_sync_in_worker_thread
return await future
^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/_backends/_asyncio.py:986: in run
result = context.run(func, *args)
^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1175: in __call__
return self._mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1179: in _mock_call
return self._execute_mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <MagicMock name='github_client.find_bot_reviews' id='140295010424816'>
args = (12345, 'octocat/hello-world', 42), kwargs = {}
effect = Exception('API error')
def _execute_mock_call(self, /, *args, **kwargs):
# separate from _increment_mock_call so that awaited functions are
# executed separately from their call, also AsyncMock overrides this method
effect = self.side_effect
if effect is not None:
if _is_exception(effect):
> raise effect
E Exception: API error
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1234: Exception
During handling of the above exception, another exception occurred:
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7f98fa96bef0>
mock_github_client = <MagicMock name='github_client' id='140295010423136'>
@pytest.mark.asyncio
async def test_dismiss_approvals_exception(webhook_handler, mock_github_client):
"""Test _dismiss_approvals handles exceptions gracefully."""
payload = load_fixture("pr_unlabeled_autoapprove")
# Make find_bot_reviews raise an exception
mock_github_client.find_bot_reviews.side_effect = Exception("API error")
> result = await webhook_handler.handle_event("pull_request", payload)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_webhook_handler.py:727:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
stampbot/webhook_handler.py:113: in handle_event
result = await self._handle_pull_request(payload)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/webhook_handler.py:332: in _handle_pull_request
success = await self._dismiss_approvals(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.webhook_handler.WebhookHandler object at 0x7f98fa96bef0>
installation_id = 12345, repo_full_name = 'octocat/hello-world', pr_number = 42
message = 'Label autoapprove removed', trigger_type = 'label_removed'
async def _dismiss_approvals(
self,
installation_id: int,
repo_full_name: str,
pr_number: int,
message: str,
trigger_type: str,
) -> bool:
"""Dismiss bot approvals on a PR.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name
pr_number: PR number
message: Dismissal message
trigger_type: What triggered the dismissal (label_removed, chatops)
Returns:
True if successful
"""
with create_span(
"webhook.dismiss_approvals",
{
"github.repo": repo_full_name,
"github.pr_number": pr_number,
"dismissal.trigger_type": trigger_type,
},
) as span:
start_time = time.time()
try:
# Find all bot reviews
review_ids = await run_in_threadpool(
github_client.find_bot_reviews,
installation_id,
repo_full_name,
pr_number,
)
add_span_attributes(span, {"dismissal.reviews_found": len(review_ids)})
if not review_ids:
logger.info(
"No bot approvals found on PR #%d",
pr_number,
extra={"repo": repo_full_name, "pr_number": pr_number},
)
duration = time.time() - start_time
pr_dismissal_duration_seconds.observe(duration)
pr_dismissals_total.labels(trigger_type=trigger_type, status="success").inc()
add_span_attributes(span, {"dismissal.result": "no_reviews"})
set_span_ok(span)
return True
# Dismiss each review
success = True
for review_id in review_ids:
result = await run_in_threadpool(
github_client.dismiss_approval,
installation_id,
repo_full_name,
pr_number,
review_id,
message,
)
success = success and result
duration = time.time() - start_time
pr_dismissal_duration_seconds.observe(duration)
status = "success" if success else "failure"
pr_dismissals_total.labels(trigger_type=trigger_type, status=status).inc()
add_span_attributes(span, {"dismissal.result": status})
set_span_ok(span)
return success
except Exception as e:
> duration = time.time() | start_time
^^^^^^^^^^^^^^^^^^^^^^^^
E TypeError: unsupported operand type(s) for |: 'float' and 'float'
stampbot/webhook_handler.py:819: TypeError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:53:07.746114Z[0m [[32m[1minfo [0m] [1mReceived pull_request event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'pull_request', 'action': 'unlabeled'}[0m
[2m2026-05-16T19:53:07.746701Z[0m [[32m[1minfo [0m] [1mNo stampbot.toml found in octocat/hello-world, using defaults[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'org_repo': None}[0m
[2m2026-05-16T19:53:07.747256Z[0m [[32m[1minfo [0m] [1mApproval label autoapprove removed from PR #42[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'pull_request', 'action': 'unlabeled'}, 'event': 'Received pull_request event', 'level': 'info', 'timestamp': '2026-05-16T19:53:07.746114Z'}
INFO stampbot.webhook_handler:webhook_handler.py:593 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'level': 'info', 'timestamp': '2026-05-16T19:53:07.746701Z'}
INFO stampbot.webhook_handler:webhook_handler.py:320 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'Approval label autoapprove removed from PR #42', 'level': 'info', 'timestamp': '2026-05-16T19:53:07.747256Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_dismiss_approvals_exception - TypeError: unsupported operand type(s) for |: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 204 passed, 3 deselected in 1.84s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_BitAnd, occurrence: 0
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -720,7 +720,7 @@
comment,
)
- duration = time.time() - start_time
+ duration = time.time() & start_time
pr_approval_duration_seconds.observe(duration)
status = "success" if success else "failure"........................................................................ [ 34%]
........................................................................ [ 68%]
...................F
=================================== FAILURES ===================================
____________________ test_pr_opened_with_autoapprove_label _____________________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7f1f1adebe10>
mock_github_client = <MagicMock name='github_client' id='139771567817456'>
@pytest.mark.asyncio
async def test_pr_opened_with_autoapprove_label(webhook_handler, mock_github_client):
"""Test PR opened with autoapprove label triggers approval."""
payload = load_fixture("pr_opened_with_autoapprove_label")
> result = await webhook_handler.handle_event("pull_request", payload)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_webhook_handler.py:68:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
stampbot/webhook_handler.py:113: in handle_event
result = await self._handle_pull_request(payload)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/webhook_handler.py:290: in _handle_pull_request
success = await self._approve_pr(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.webhook_handler.WebhookHandler object at 0x7f1f1adebe10>
installation_id = 12345, repo_full_name = 'octocat/hello-world', pr_number = 42
comment = 'Auto-approved by Stampbot (label: autoapprove)'
trigger_type = 'label'
async def _approve_pr(
self,
installation_id: int,
repo_full_name: str,
pr_number: int,
comment: str,
trigger_type: str,
) -> bool:
"""Approve a PR and track metrics.
Checks for existing active approvals first to avoid duplicate comments.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name
pr_number: PR number
comment: Approval comment
trigger_type: What triggered the approval (label, chatops)
Returns:
True if successful (or already approved)
"""
with create_span(
"webhook.approve_pr",
{
"github.repo": repo_full_name,
"github.pr_number": pr_number,
"approval.trigger_type": trigger_type,
},
) as span:
# Check for existing active approval to avoid duplicates
existing_approvals = await run_in_threadpool(
github_client.find_bot_reviews,
installation_id,
repo_full_name,
pr_number,
)
if existing_approvals:
logger.info(
"PR #%d in %s already has active approval, skipping",
pr_number,
repo_full_name,
extra={
"repo": repo_full_name,
"pr_number": pr_number,
"existing_review_ids": existing_approvals,
},
)
add_span_attributes(
span,
{
"approval.result": "already_approved",
"approval.existing_reviews": len(existing_approvals),
},
)
set_span_ok(span)
return True
start_time = time.time()
success = await run_in_threadpool(
github_client.approve_pr,
installation_id,
repo_full_name,
pr_number,
comment,
)
> duration = time.time() & start_time
^^^^^^^^^^^^^^^^^^^^^^^^
E TypeError: unsupported operand type(s) for &: 'float' and 'float'
stampbot/webhook_handler.py:723: TypeError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T20:08:11.654686Z[0m [[32m[1minfo [0m] [1mReceived pull_request event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'pull_request', 'action': 'opened'}[0m
[2m2026-05-16T20:08:11.655436Z[0m [[32m[1minfo [0m] [1mNo stampbot.toml found in octocat/hello-world, using defaults[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'org_repo': None}[0m
[2m2026-05-16T20:08:11.656337Z[0m [[32m[1minfo [0m] [1mPR #42 has approval label: autoapprove[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'level': 'info', 'timestamp': '2026-05-16T20:08:11.654686Z'}
INFO stampbot.webhook_handler:webhook_handler.py:593 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'level': 'info', 'timestamp': '2026-05-16T20:08:11.655436Z'}
INFO stampbot.webhook_handler:webhook_handler.py:278 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'level': 'info', 'timestamp': '2026-05-16T20:08:11.656337Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_opened_with_autoapprove_label - TypeError: unsupported operand type(s) for &: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 163 passed, 3 deselected in 1.20s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_BitAnd, occurrence: 1
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -784,7 +784,7 @@
pr_number,
extra={"repo": repo_full_name, "pr_number": pr_number},
)
- duration = time.time() - start_time
+ duration = time.time() & start_time
pr_dismissal_duration_seconds.observe(duration)
pr_dismissals_total.labels(trigger_type=trigger_type, status="success").inc()
add_span_attributes(span, {"dismissal.result": "no_reviews"})........................................................................ [ 34%]
........................................................................ [ 68%]
...........................F
=================================== FAILURES ===================================
_______________________ test_pr_unlabeled_no_bot_reviews _______________________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7fcabfbad4f0>
mock_github_client = <MagicMock name='github_client' id='140508772412320'>
@pytest.mark.asyncio
async def test_pr_unlabeled_no_bot_reviews(webhook_handler, mock_github_client):
"""Test removing label when no bot reviews exist."""
payload = load_fixture("pr_unlabeled_autoapprove")
mock_github_client.find_bot_reviews.return_value = [] # No reviews to dismiss
result = await webhook_handler.handle_event("pull_request", payload)
> assert result["status"] == "success"
E AssertionError: assert 'error' == 'success'
E
E - success
E + error
tests/test_webhook_handler.py:179: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:45:34.769239Z[0m [[32m[1minfo [0m] [1mReceived pull_request event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'pull_request', 'action': 'unlabeled'}[0m
[2m2026-05-16T19:45:34.769786Z[0m [[32m[1minfo [0m] [1mNo stampbot.toml found in octocat/hello-world, using defaults[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'org_repo': None}[0m
[2m2026-05-16T19:45:34.770335Z[0m [[32m[1minfo [0m] [1mApproval label autoapprove removed from PR #42[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}[0m
[2m2026-05-16T19:45:34.770767Z[0m [[32m[1minfo [0m] [1mNo bot approvals found on PR #42[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'pr_number': 42}[0m
[2m2026-05-16T19:45:34.770968Z[0m [[31m[1merror [0m] [1mError dismissing approvals: unsupported operand type(s) for &: 'float' and 'float'[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35merror[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'pr_number': 42, 'error': "unsupported operand type(s) for &: 'float' and 'float'"}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'pull_request', 'action': 'unlabeled'}, 'event': 'Received pull_request event', 'level': 'info', 'timestamp': '2026-05-16T19:45:34.769239Z'}
INFO stampbot.webhook_handler:webhook_handler.py:593 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'level': 'info', 'timestamp': '2026-05-16T19:45:34.769786Z'}
INFO stampbot.webhook_handler:webhook_handler.py:320 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'Approval label autoapprove removed from PR #42', 'level': 'info', 'timestamp': '2026-05-16T19:45:34.770335Z'}
INFO stampbot.webhook_handler:webhook_handler.py:782 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42}, 'event': 'No bot approvals found on PR #42', 'level': 'info', 'timestamp': '2026-05-16T19:45:34.770767Z'}
ERROR stampbot.webhook_handler:webhook_handler.py:823 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'error': "unsupported operand type(s) for &: 'float' and 'float'"}, 'event': "Error dismissing approvals: unsupported operand type(s) for &: 'float' and 'float'", 'level': 'error', 'timestamp': '2026-05-16T19:45:34.770968Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_unlabeled_no_bot_reviews - AssertionError: assert 'error' == 'success'
- success
+ error
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 171 passed, 3 deselected in 1.11s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_BitAnd, occurrence: 2
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -804,7 +804,7 @@
)
success = success and result
- duration = time.time() - start_time
+ duration = time.time() & start_time
pr_dismissal_duration_seconds.observe(duration)
status = "success" if success else "failure"........................................................................ [ 34%]
........................................................................ [ 68%]
.........................F
=================================== FAILURES ===================================
________________________ test_pr_unlabeled_autoapprove _________________________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7fb257ded550>
mock_github_client = <MagicMock name='github_client' id='140404026254944'>
@pytest.mark.asyncio
async def test_pr_unlabeled_autoapprove(webhook_handler, mock_github_client):
"""Test removing autoapprove label dismisses approvals."""
payload = load_fixture("pr_unlabeled_autoapprove")
mock_github_client.find_bot_reviews.return_value = [111, 222] # Mock review IDs
result = await webhook_handler.handle_event("pull_request", payload)
> assert result["status"] == "success"
E AssertionError: assert 'error' == 'success'
E
E - success
E + error
tests/test_webhook_handler.py:152: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:44:51.294267Z[0m [[32m[1minfo [0m] [1mReceived pull_request event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'pull_request', 'action': 'unlabeled'}[0m
[2m2026-05-16T19:44:51.294792Z[0m [[32m[1minfo [0m] [1mNo stampbot.toml found in octocat/hello-world, using defaults[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'org_repo': None}[0m
[2m2026-05-16T19:44:51.295359Z[0m [[32m[1minfo [0m] [1mApproval label autoapprove removed from PR #42[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}[0m
[2m2026-05-16T19:44:51.296028Z[0m [[31m[1merror [0m] [1mError dismissing approvals: unsupported operand type(s) for &: 'float' and 'float'[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35merror[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'pr_number': 42, 'error': "unsupported operand type(s) for &: 'float' and 'float'"}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'pull_request', 'action': 'unlabeled'}, 'event': 'Received pull_request event', 'level': 'info', 'timestamp': '2026-05-16T19:44:51.294267Z'}
INFO stampbot.webhook_handler:webhook_handler.py:593 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'level': 'info', 'timestamp': '2026-05-16T19:44:51.294792Z'}
INFO stampbot.webhook_handler:webhook_handler.py:320 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'Approval label autoapprove removed from PR #42', 'level': 'info', 'timestamp': '2026-05-16T19:44:51.295359Z'}
ERROR stampbot.webhook_handler:webhook_handler.py:823 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'error': "unsupported operand type(s) for &: 'float' and 'float'"}, 'event': "Error dismissing approvals: unsupported operand type(s) for &: 'float' and 'float'", 'level': 'error', 'timestamp': '2026-05-16T19:44:51.296028Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_unlabeled_autoapprove - AssertionError: assert 'error' == 'success'
- success
+ error
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 169 passed, 3 deselected in 1.10s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_BitAnd, occurrence: 3
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -816,7 +816,7 @@
return success
except Exception as e:
- duration = time.time() - start_time
+ duration = time.time() & start_time
pr_dismissal_duration_seconds.observe(duration)
pr_dismissals_total.labels(trigger_type=trigger_type, status="failure").inc()
........................................................................ [ 34%]
........................................................................ [ 68%]
............................................................F
=================================== FAILURES ===================================
_______________________ test_dismiss_approvals_exception _______________________
self = <stampbot.webhook_handler.WebhookHandler object at 0x7f5f2c89ff50>
installation_id = 12345, repo_full_name = 'octocat/hello-world', pr_number = 42
message = 'Label autoapprove removed', trigger_type = 'label_removed'
async def _dismiss_approvals(
self,
installation_id: int,
repo_full_name: str,
pr_number: int,
message: str,
trigger_type: str,
) -> bool:
"""Dismiss bot approvals on a PR.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name
pr_number: PR number
message: Dismissal message
trigger_type: What triggered the dismissal (label_removed, chatops)
Returns:
True if successful
"""
with create_span(
"webhook.dismiss_approvals",
{
"github.repo": repo_full_name,
"github.pr_number": pr_number,
"dismissal.trigger_type": trigger_type,
},
) as span:
start_time = time.time()
try:
# Find all bot reviews
> review_ids = await run_in_threadpool(
github_client.find_bot_reviews,
installation_id,
repo_full_name,
pr_number,
)
stampbot/webhook_handler.py:772:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/concurrency.py:32: in run_in_threadpool
return await anyio.to_thread.run_sync(func)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/to_thread.py:63: in run_sync
return await get_async_backend().run_sync_in_worker_thread(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/_backends/_asyncio.py:2502: in run_sync_in_worker_thread
return await future
^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/_backends/_asyncio.py:986: in run
result = context.run(func, *args)
^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1175: in __call__
return self._mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1179: in _mock_call
return self._execute_mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <MagicMock name='github_client.find_bot_reviews' id='140046738921456'>
args = (12345, 'octocat/hello-world', 42), kwargs = {}
effect = Exception('API error')
def _execute_mock_call(self, /, *args, **kwargs):
# separate from _increment_mock_call so that awaited functions are
# executed separately from their call, also AsyncMock overrides this method
effect = self.side_effect
if effect is not None:
if _is_exception(effect):
> raise effect
E Exception: API error
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1234: Exception
During handling of the above exception, another exception occurred:
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7f5f2c89ff50>
mock_github_client = <MagicMock name='github_client' id='140046738919776'>
@pytest.mark.asyncio
async def test_dismiss_approvals_exception(webhook_handler, mock_github_client):
"""Test _dismiss_approvals handles exceptions gracefully."""
payload = load_fixture("pr_unlabeled_autoapprove")
# Make find_bot_reviews raise an exception
mock_github_client.find_bot_reviews.side_effect = Exception("API error")
> result = await webhook_handler.handle_event("pull_request", payload)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_webhook_handler.py:727:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
stampbot/webhook_handler.py:113: in handle_event
result = await self._handle_pull_request(payload)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/webhook_handler.py:332: in _handle_pull_request
success = await self._dismiss_approvals(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.webhook_handler.WebhookHandler object at 0x7f5f2c89ff50>
installation_id = 12345, repo_full_name = 'octocat/hello-world', pr_number = 42
message = 'Label autoapprove removed', trigger_type = 'label_removed'
async def _dismiss_approvals(
self,
installation_id: int,
repo_full_name: str,
pr_number: int,
message: str,
trigger_type: str,
) -> bool:
"""Dismiss bot approvals on a PR.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name
pr_number: PR number
message: Dismissal message
trigger_type: What triggered the dismissal (label_removed, chatops)
Returns:
True if successful
"""
with create_span(
"webhook.dismiss_approvals",
{
"github.repo": repo_full_name,
"github.pr_number": pr_number,
"dismissal.trigger_type": trigger_type,
},
) as span:
start_time = time.time()
try:
# Find all bot reviews
review_ids = await run_in_threadpool(
github_client.find_bot_reviews,
installation_id,
repo_full_name,
pr_number,
)
add_span_attributes(span, {"dismissal.reviews_found": len(review_ids)})
if not review_ids:
logger.info(
"No bot approvals found on PR #%d",
pr_number,
extra={"repo": repo_full_name, "pr_number": pr_number},
)
duration = time.time() - start_time
pr_dismissal_duration_seconds.observe(duration)
pr_dismissals_total.labels(trigger_type=trigger_type, status="success").inc()
add_span_attributes(span, {"dismissal.result": "no_reviews"})
set_span_ok(span)
return True
# Dismiss each review
success = True
for review_id in review_ids:
result = await run_in_threadpool(
github_client.dismiss_approval,
installation_id,
repo_full_name,
pr_number,
review_id,
message,
)
success = success and result
duration = time.time() - start_time
pr_dismissal_duration_seconds.observe(duration)
status = "success" if success else "failure"
pr_dismissals_total.labels(trigger_type=trigger_type, status=status).inc()
add_span_attributes(span, {"dismissal.result": status})
set_span_ok(span)
return success
except Exception as e:
> duration = time.time() & start_time
^^^^^^^^^^^^^^^^^^^^^^^^
E TypeError: unsupported operand type(s) for &: 'float' and 'float'
stampbot/webhook_handler.py:819: TypeError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:45:14.684695Z[0m [[32m[1minfo [0m] [1mReceived pull_request event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'pull_request', 'action': 'unlabeled'}[0m
[2m2026-05-16T19:45:14.685263Z[0m [[32m[1minfo [0m] [1mNo stampbot.toml found in octocat/hello-world, using defaults[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'org_repo': None}[0m
[2m2026-05-16T19:45:14.685790Z[0m [[32m[1minfo [0m] [1mApproval label autoapprove removed from PR #42[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'pull_request', 'action': 'unlabeled'}, 'event': 'Received pull_request event', 'level': 'info', 'timestamp': '2026-05-16T19:45:14.684695Z'}
INFO stampbot.webhook_handler:webhook_handler.py:593 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'level': 'info', 'timestamp': '2026-05-16T19:45:14.685263Z'}
INFO stampbot.webhook_handler:webhook_handler.py:320 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'Approval label autoapprove removed from PR #42', 'level': 'info', 'timestamp': '2026-05-16T19:45:14.685790Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_dismiss_approvals_exception - TypeError: unsupported operand type(s) for &: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 204 passed, 3 deselected in 1.81s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_BitXor, occurrence: 0
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -720,7 +720,7 @@
comment,
)
- duration = time.time() - start_time
+ duration = time.time() ^ start_time
pr_approval_duration_seconds.observe(duration)
status = "success" if success else "failure"........................................................................ [ 34%]
........................................................................ [ 68%]
...................F
=================================== FAILURES ===================================
____________________ test_pr_opened_with_autoapprove_label _____________________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7fa3d150be10>
mock_github_client = <MagicMock name='github_client' id='140341566522096'>
@pytest.mark.asyncio
async def test_pr_opened_with_autoapprove_label(webhook_handler, mock_github_client):
"""Test PR opened with autoapprove label triggers approval."""
payload = load_fixture("pr_opened_with_autoapprove_label")
> result = await webhook_handler.handle_event("pull_request", payload)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_webhook_handler.py:68:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
stampbot/webhook_handler.py:113: in handle_event
result = await self._handle_pull_request(payload)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/webhook_handler.py:290: in _handle_pull_request
success = await self._approve_pr(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.webhook_handler.WebhookHandler object at 0x7fa3d150be10>
installation_id = 12345, repo_full_name = 'octocat/hello-world', pr_number = 42
comment = 'Auto-approved by Stampbot (label: autoapprove)'
trigger_type = 'label'
async def _approve_pr(
self,
installation_id: int,
repo_full_name: str,
pr_number: int,
comment: str,
trigger_type: str,
) -> bool:
"""Approve a PR and track metrics.
Checks for existing active approvals first to avoid duplicate comments.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name
pr_number: PR number
comment: Approval comment
trigger_type: What triggered the approval (label, chatops)
Returns:
True if successful (or already approved)
"""
with create_span(
"webhook.approve_pr",
{
"github.repo": repo_full_name,
"github.pr_number": pr_number,
"approval.trigger_type": trigger_type,
},
) as span:
# Check for existing active approval to avoid duplicates
existing_approvals = await run_in_threadpool(
github_client.find_bot_reviews,
installation_id,
repo_full_name,
pr_number,
)
if existing_approvals:
logger.info(
"PR #%d in %s already has active approval, skipping",
pr_number,
repo_full_name,
extra={
"repo": repo_full_name,
"pr_number": pr_number,
"existing_review_ids": existing_approvals,
},
)
add_span_attributes(
span,
{
"approval.result": "already_approved",
"approval.existing_reviews": len(existing_approvals),
},
)
set_span_ok(span)
return True
start_time = time.time()
success = await run_in_threadpool(
github_client.approve_pr,
installation_id,
repo_full_name,
pr_number,
comment,
)
> duration = time.time() ^ start_time
^^^^^^^^^^^^^^^^^^^^^^^^
E TypeError: unsupported operand type(s) for ^: 'float' and 'float'
stampbot/webhook_handler.py:723: TypeError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:36:19.612764Z[0m [[32m[1minfo [0m] [1mReceived pull_request event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'pull_request', 'action': 'opened'}[0m
[2m2026-05-16T19:36:19.613493Z[0m [[32m[1minfo [0m] [1mNo stampbot.toml found in octocat/hello-world, using defaults[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'org_repo': None}[0m
[2m2026-05-16T19:36:19.614381Z[0m [[32m[1minfo [0m] [1mPR #42 has approval label: autoapprove[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'level': 'info', 'timestamp': '2026-05-16T19:36:19.612764Z'}
INFO stampbot.webhook_handler:webhook_handler.py:593 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'level': 'info', 'timestamp': '2026-05-16T19:36:19.613493Z'}
INFO stampbot.webhook_handler:webhook_handler.py:278 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'level': 'info', 'timestamp': '2026-05-16T19:36:19.614381Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_opened_with_autoapprove_label - TypeError: unsupported operand type(s) for ^: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 163 passed, 3 deselected in 1.15s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_BitXor, occurrence: 1
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -784,7 +784,7 @@
pr_number,
extra={"repo": repo_full_name, "pr_number": pr_number},
)
- duration = time.time() - start_time
+ duration = time.time() ^ start_time
pr_dismissal_duration_seconds.observe(duration)
pr_dismissals_total.labels(trigger_type=trigger_type, status="success").inc()
add_span_attributes(span, {"dismissal.result": "no_reviews"})........................................................................ [ 34%]
........................................................................ [ 68%]
...........................F
=================================== FAILURES ===================================
_______________________ test_pr_unlabeled_no_bot_reviews _______________________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7f7ffc3894f0>
mock_github_client = <MagicMock name='github_client' id='140187596565408'>
@pytest.mark.asyncio
async def test_pr_unlabeled_no_bot_reviews(webhook_handler, mock_github_client):
"""Test removing label when no bot reviews exist."""
payload = load_fixture("pr_unlabeled_autoapprove")
mock_github_client.find_bot_reviews.return_value = [] # No reviews to dismiss
result = await webhook_handler.handle_event("pull_request", payload)
> assert result["status"] == "success"
E AssertionError: assert 'error' == 'success'
E
E - success
E + error
tests/test_webhook_handler.py:179: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:31:57.125724Z[0m [[32m[1minfo [0m] [1mReceived pull_request event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'pull_request', 'action': 'unlabeled'}[0m
[2m2026-05-16T19:31:57.126278Z[0m [[32m[1minfo [0m] [1mNo stampbot.toml found in octocat/hello-world, using defaults[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'org_repo': None}[0m
[2m2026-05-16T19:31:57.126793Z[0m [[32m[1minfo [0m] [1mApproval label autoapprove removed from PR #42[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}[0m
[2m2026-05-16T19:31:57.127221Z[0m [[32m[1minfo [0m] [1mNo bot approvals found on PR #42[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'pr_number': 42}[0m
[2m2026-05-16T19:31:57.127422Z[0m [[31m[1merror [0m] [1mError dismissing approvals: unsupported operand type(s) for ^: 'float' and 'float'[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35merror[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'pr_number': 42, 'error': "unsupported operand type(s) for ^: 'float' and 'float'"}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'pull_request', 'action': 'unlabeled'}, 'event': 'Received pull_request event', 'level': 'info', 'timestamp': '2026-05-16T19:31:57.125724Z'}
INFO stampbot.webhook_handler:webhook_handler.py:593 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'level': 'info', 'timestamp': '2026-05-16T19:31:57.126278Z'}
INFO stampbot.webhook_handler:webhook_handler.py:320 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'Approval label autoapprove removed from PR #42', 'level': 'info', 'timestamp': '2026-05-16T19:31:57.126793Z'}
INFO stampbot.webhook_handler:webhook_handler.py:782 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42}, 'event': 'No bot approvals found on PR #42', 'level': 'info', 'timestamp': '2026-05-16T19:31:57.127221Z'}
ERROR stampbot.webhook_handler:webhook_handler.py:823 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'error': "unsupported operand type(s) for ^: 'float' and 'float'"}, 'event': "Error dismissing approvals: unsupported operand type(s) for ^: 'float' and 'float'", 'level': 'error', 'timestamp': '2026-05-16T19:31:57.127422Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_unlabeled_no_bot_reviews - AssertionError: assert 'error' == 'success'
- success
+ error
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 171 passed, 3 deselected in 1.10s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_BitXor, occurrence: 2
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -804,7 +804,7 @@
)
success = success and result
- duration = time.time() - start_time
+ duration = time.time() ^ start_time
pr_dismissal_duration_seconds.observe(duration)
status = "success" if success else "failure"........................................................................ [ 34%]
........................................................................ [ 68%]
.........................F
=================================== FAILURES ===================================
________________________ test_pr_unlabeled_autoapprove _________________________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7f0888ae5650>
mock_github_client = <MagicMock name='github_client' id='139674695329376'>
@pytest.mark.asyncio
async def test_pr_unlabeled_autoapprove(webhook_handler, mock_github_client):
"""Test removing autoapprove label dismisses approvals."""
payload = load_fixture("pr_unlabeled_autoapprove")
mock_github_client.find_bot_reviews.return_value = [111, 222] # Mock review IDs
result = await webhook_handler.handle_event("pull_request", payload)
> assert result["status"] == "success"
E AssertionError: assert 'error' == 'success'
E
E - success
E + error
tests/test_webhook_handler.py:152: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:34:58.311531Z[0m [[32m[1minfo [0m] [1mReceived pull_request event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'pull_request', 'action': 'unlabeled'}[0m
[2m2026-05-16T19:34:58.312095Z[0m [[32m[1minfo [0m] [1mNo stampbot.toml found in octocat/hello-world, using defaults[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'org_repo': None}[0m
[2m2026-05-16T19:34:58.312668Z[0m [[32m[1minfo [0m] [1mApproval label autoapprove removed from PR #42[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}[0m
[2m2026-05-16T19:34:58.313544Z[0m [[31m[1merror [0m] [1mError dismissing approvals: unsupported operand type(s) for ^: 'float' and 'float'[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35merror[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'pr_number': 42, 'error': "unsupported operand type(s) for ^: 'float' and 'float'"}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'pull_request', 'action': 'unlabeled'}, 'event': 'Received pull_request event', 'level': 'info', 'timestamp': '2026-05-16T19:34:58.311531Z'}
INFO stampbot.webhook_handler:webhook_handler.py:593 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'level': 'info', 'timestamp': '2026-05-16T19:34:58.312095Z'}
INFO stampbot.webhook_handler:webhook_handler.py:320 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'Approval label autoapprove removed from PR #42', 'level': 'info', 'timestamp': '2026-05-16T19:34:58.312668Z'}
ERROR stampbot.webhook_handler:webhook_handler.py:823 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'error': "unsupported operand type(s) for ^: 'float' and 'float'"}, 'event': "Error dismissing approvals: unsupported operand type(s) for ^: 'float' and 'float'", 'level': 'error', 'timestamp': '2026-05-16T19:34:58.313544Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_unlabeled_autoapprove - AssertionError: assert 'error' == 'success'
- success
+ error
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 169 passed, 3 deselected in 1.09s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceBinaryOperator_Sub_BitXor, occurrence: 3
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -816,7 +816,7 @@
return success
except Exception as e:
- duration = time.time() - start_time
+ duration = time.time() ^ start_time
pr_dismissal_duration_seconds.observe(duration)
pr_dismissals_total.labels(trigger_type=trigger_type, status="failure").inc()
........................................................................ [ 34%]
........................................................................ [ 68%]
............................................................F
=================================== FAILURES ===================================
_______________________ test_dismiss_approvals_exception _______________________
self = <stampbot.webhook_handler.WebhookHandler object at 0x7f85de98fef0>
installation_id = 12345, repo_full_name = 'octocat/hello-world', pr_number = 42
message = 'Label autoapprove removed', trigger_type = 'label_removed'
async def _dismiss_approvals(
self,
installation_id: int,
repo_full_name: str,
pr_number: int,
message: str,
trigger_type: str,
) -> bool:
"""Dismiss bot approvals on a PR.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name
pr_number: PR number
message: Dismissal message
trigger_type: What triggered the dismissal (label_removed, chatops)
Returns:
True if successful
"""
with create_span(
"webhook.dismiss_approvals",
{
"github.repo": repo_full_name,
"github.pr_number": pr_number,
"dismissal.trigger_type": trigger_type,
},
) as span:
start_time = time.time()
try:
# Find all bot reviews
> review_ids = await run_in_threadpool(
github_client.find_bot_reviews,
installation_id,
repo_full_name,
pr_number,
)
stampbot/webhook_handler.py:772:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/concurrency.py:32: in run_in_threadpool
return await anyio.to_thread.run_sync(func)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/to_thread.py:63: in run_sync
return await get_async_backend().run_sync_in_worker_thread(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/_backends/_asyncio.py:2502: in run_sync_in_worker_thread
return await future
^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/_backends/_asyncio.py:986: in run
result = context.run(func, *args)
^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1175: in __call__
return self._mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1179: in _mock_call
return self._execute_mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <MagicMock name='github_client.find_bot_reviews' id='140212936185840'>
args = (12345, 'octocat/hello-world', 42), kwargs = {}
effect = Exception('API error')
def _execute_mock_call(self, /, *args, **kwargs):
# separate from _increment_mock_call so that awaited functions are
# executed separately from their call, also AsyncMock overrides this method
effect = self.side_effect
if effect is not None:
if _is_exception(effect):
> raise effect
E Exception: API error
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1234: Exception
During handling of the above exception, another exception occurred:
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7f85de98fef0>
mock_github_client = <MagicMock name='github_client' id='140212936184160'>
@pytest.mark.asyncio
async def test_dismiss_approvals_exception(webhook_handler, mock_github_client):
"""Test _dismiss_approvals handles exceptions gracefully."""
payload = load_fixture("pr_unlabeled_autoapprove")
# Make find_bot_reviews raise an exception
mock_github_client.find_bot_reviews.side_effect = Exception("API error")
> result = await webhook_handler.handle_event("pull_request", payload)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_webhook_handler.py:727:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
stampbot/webhook_handler.py:113: in handle_event
result = await self._handle_pull_request(payload)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/webhook_handler.py:332: in _handle_pull_request
success = await self._dismiss_approvals(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.webhook_handler.WebhookHandler object at 0x7f85de98fef0>
installation_id = 12345, repo_full_name = 'octocat/hello-world', pr_number = 42
message = 'Label autoapprove removed', trigger_type = 'label_removed'
async def _dismiss_approvals(
self,
installation_id: int,
repo_full_name: str,
pr_number: int,
message: str,
trigger_type: str,
) -> bool:
"""Dismiss bot approvals on a PR.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name
pr_number: PR number
message: Dismissal message
trigger_type: What triggered the dismissal (label_removed, chatops)
Returns:
True if successful
"""
with create_span(
"webhook.dismiss_approvals",
{
"github.repo": repo_full_name,
"github.pr_number": pr_number,
"dismissal.trigger_type": trigger_type,
},
) as span:
start_time = time.time()
try:
# Find all bot reviews
review_ids = await run_in_threadpool(
github_client.find_bot_reviews,
installation_id,
repo_full_name,
pr_number,
)
add_span_attributes(span, {"dismissal.reviews_found": len(review_ids)})
if not review_ids:
logger.info(
"No bot approvals found on PR #%d",
pr_number,
extra={"repo": repo_full_name, "pr_number": pr_number},
)
duration = time.time() - start_time
pr_dismissal_duration_seconds.observe(duration)
pr_dismissals_total.labels(trigger_type=trigger_type, status="success").inc()
add_span_attributes(span, {"dismissal.result": "no_reviews"})
set_span_ok(span)
return True
# Dismiss each review
success = True
for review_id in review_ids:
result = await run_in_threadpool(
github_client.dismiss_approval,
installation_id,
repo_full_name,
pr_number,
review_id,
message,
)
success = success and result
duration = time.time() - start_time
pr_dismissal_duration_seconds.observe(duration)
status = "success" if success else "failure"
pr_dismissals_total.labels(trigger_type=trigger_type, status=status).inc()
add_span_attributes(span, {"dismissal.result": status})
set_span_ok(span)
return success
except Exception as e:
> duration = time.time() ^ start_time
^^^^^^^^^^^^^^^^^^^^^^^^
E TypeError: unsupported operand type(s) for ^: 'float' and 'float'
stampbot/webhook_handler.py:819: TypeError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:39:41.977672Z[0m [[32m[1minfo [0m] [1mReceived pull_request event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'pull_request', 'action': 'unlabeled'}[0m
[2m2026-05-16T19:39:41.978241Z[0m [[32m[1minfo [0m] [1mNo stampbot.toml found in octocat/hello-world, using defaults[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'org_repo': None}[0m
[2m2026-05-16T19:39:41.978765Z[0m [[32m[1minfo [0m] [1mApproval label autoapprove removed from PR #42[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'pull_request', 'action': 'unlabeled'}, 'event': 'Received pull_request event', 'level': 'info', 'timestamp': '2026-05-16T19:39:41.977672Z'}
INFO stampbot.webhook_handler:webhook_handler.py:593 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'level': 'info', 'timestamp': '2026-05-16T19:39:41.978241Z'}
INFO stampbot.webhook_handler:webhook_handler.py:320 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'Approval label autoapprove removed from PR #42', 'level': 'info', 'timestamp': '2026-05-16T19:39:41.978765Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_dismiss_approvals_exception - TypeError: unsupported operand type(s) for ^: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 204 passed, 3 deselected in 1.82s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_Add, occurrence: 0
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -536,7 +536,7 @@
installation_id: int,
repo_full_name: str,
default_branch: str,
- owner_login: str | None,
+ owner_login: str + None,
owner_type: str | None,
) -> RepoConfig:
"""Get repository configuration from stampbot.toml......................................................................... [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.62s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_Add, occurrence: 1
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -537,7 +537,7 @@
repo_full_name: str,
default_branch: str,
owner_login: str | None,
- owner_type: str | None,
+ owner_type: str + None,
) -> RepoConfig:
"""Get repository configuration from stampbot.toml.
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.64s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_Sub, occurrence: 0
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -536,7 +536,7 @@
installation_id: int,
repo_full_name: str,
default_branch: str,
- owner_login: str | None,
+ owner_login: str - None,
owner_type: str | None,
) -> RepoConfig:
"""Get repository configuration from stampbot.toml......................................................................... [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.62s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_Sub, occurrence: 1
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -537,7 +537,7 @@
repo_full_name: str,
default_branch: str,
owner_login: str | None,
- owner_type: str | None,
+ owner_type: str - None,
) -> RepoConfig:
"""Get repository configuration from stampbot.toml.
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.62s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_Mul, occurrence: 0
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -536,7 +536,7 @@
installation_id: int,
repo_full_name: str,
default_branch: str,
- owner_login: str | None,
+ owner_login: str * None,
owner_type: str | None,
) -> RepoConfig:
"""Get repository configuration from stampbot.toml......................................................................... [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.64s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_Mul, occurrence: 1
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -537,7 +537,7 @@
repo_full_name: str,
default_branch: str,
owner_login: str | None,
- owner_type: str | None,
+ owner_type: str * None,
) -> RepoConfig:
"""Get repository configuration from stampbot.toml.
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_Div, occurrence: 0
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -536,7 +536,7 @@
installation_id: int,
repo_full_name: str,
default_branch: str,
- owner_login: str | None,
+ owner_login: str / None,
owner_type: str | None,
) -> RepoConfig:
"""Get repository configuration from stampbot.toml......................................................................... [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.67s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_Div, occurrence: 1
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -537,7 +537,7 @@
repo_full_name: str,
default_branch: str,
owner_login: str | None,
- owner_type: str | None,
+ owner_type: str / None,
) -> RepoConfig:
"""Get repository configuration from stampbot.toml.
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.62s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_FloorDiv, occurrence: 0
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -536,7 +536,7 @@
installation_id: int,
repo_full_name: str,
default_branch: str,
- owner_login: str | None,
+ owner_login: str // None,
owner_type: str | None,
) -> RepoConfig:
"""Get repository configuration from stampbot.toml......................................................................... [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.63s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_FloorDiv, occurrence: 1
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -537,7 +537,7 @@
repo_full_name: str,
default_branch: str,
owner_login: str | None,
- owner_type: str | None,
+ owner_type: str // None,
) -> RepoConfig:
"""Get repository configuration from stampbot.toml.
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.62s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_Mod, occurrence: 0
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -536,7 +536,7 @@
installation_id: int,
repo_full_name: str,
default_branch: str,
- owner_login: str | None,
+ owner_login: str % None,
owner_type: str | None,
) -> RepoConfig:
"""Get repository configuration from stampbot.toml......................................................................... [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.60s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_Mod, occurrence: 1
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -537,7 +537,7 @@
repo_full_name: str,
default_branch: str,
owner_login: str | None,
- owner_type: str | None,
+ owner_type: str % None,
) -> RepoConfig:
"""Get repository configuration from stampbot.toml.
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.60s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_Pow, occurrence: 0
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -536,7 +536,7 @@
installation_id: int,
repo_full_name: str,
default_branch: str,
- owner_login: str | None,
+ owner_login: str ** None,
owner_type: str | None,
) -> RepoConfig:
"""Get repository configuration from stampbot.toml......................................................................... [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_Pow, occurrence: 1
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -537,7 +537,7 @@
repo_full_name: str,
default_branch: str,
owner_login: str | None,
- owner_type: str | None,
+ owner_type: str ** None,
) -> RepoConfig:
"""Get repository configuration from stampbot.toml.
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.65s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_RShift, occurrence: 0
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -536,7 +536,7 @@
installation_id: int,
repo_full_name: str,
default_branch: str,
- owner_login: str | None,
+ owner_login: str >> None,
owner_type: str | None,
) -> RepoConfig:
"""Get repository configuration from stampbot.toml......................................................................... [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_RShift, occurrence: 1
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -537,7 +537,7 @@
repo_full_name: str,
default_branch: str,
owner_login: str | None,
- owner_type: str | None,
+ owner_type: str >> None,
) -> RepoConfig:
"""Get repository configuration from stampbot.toml.
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.64s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_LShift, occurrence: 0
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -536,7 +536,7 @@
installation_id: int,
repo_full_name: str,
default_branch: str,
- owner_login: str | None,
+ owner_login: str << None,
owner_type: str | None,
) -> RepoConfig:
"""Get repository configuration from stampbot.toml......................................................................... [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.63s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_LShift, occurrence: 1
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -537,7 +537,7 @@
repo_full_name: str,
default_branch: str,
owner_login: str | None,
- owner_type: str | None,
+ owner_type: str << None,
) -> RepoConfig:
"""Get repository configuration from stampbot.toml.
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_BitAnd, occurrence: 0
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -536,7 +536,7 @@
installation_id: int,
repo_full_name: str,
default_branch: str,
- owner_login: str | None,
+ owner_login: str & None,
owner_type: str | None,
) -> RepoConfig:
"""Get repository configuration from stampbot.toml......................................................................... [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.65s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_BitAnd, occurrence: 1
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -537,7 +537,7 @@
repo_full_name: str,
default_branch: str,
owner_login: str | None,
- owner_type: str | None,
+ owner_type: str & None,
) -> RepoConfig:
"""Get repository configuration from stampbot.toml.
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_BitXor, occurrence: 0
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -536,7 +536,7 @@
installation_id: int,
repo_full_name: str,
default_branch: str,
- owner_login: str | None,
+ owner_login: str ^ None,
owner_type: str | None,
) -> RepoConfig:
"""Get repository configuration from stampbot.toml......................................................................... [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceBinaryOperator_BitOr_BitXor, occurrence: 1
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -537,7 +537,7 @@
repo_full_name: str,
default_branch: str,
owner_login: str | None,
- owner_type: str | None,
+ owner_type: str ^ None,
) -> RepoConfig:
"""Get repository configuration from stampbot.toml.
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.60s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceComparisonOperator_Eq_NotEq, occurrence: 0
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -109,7 +109,7 @@
)
# Route to appropriate handler
- if event_type == "pull_request":
+ if event_type != "pull_request":
result = await self._handle_pull_request(payload)
elif event_type == "pull_request_review_comment":
result = await self._handle_pr_comment(payload)........................................................................ [ 34%]
.............................F
=================================== FAILURES ===================================
___________________________ test_webhook_valid_ping ____________________________
test_client = <starlette.testclient.TestClient object at 0x7f96fa3be9c0>
def test_webhook_valid_ping(test_client: TestClient):
"""Test webhook accepts valid ping event."""
import hashlib
import hmac
from stampbot.webhook_handler import webhook_handler
body = json.dumps({"zen": "Design for failure."}).encode()
signature = (
"sha256="
+ hmac.new(
webhook_handler.webhook_secret,
body,
hashlib.sha256,
).hexdigest()
)
response = test_client.post(
"/webhook",
content=body,
headers={
"Content-Type": "application/json",
"X-GitHub-Event": "ping",
"X-Hub-Signature-256": signature,
},
)
assert response.status_code == 200
data = response.json()
> assert data["status"] == "ok"
E AssertionError: assert 'error' == 'ok'
E
E - ok
E + error
tests/test_main.py:177: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:45:36.750130Z[0m [[32m[1minfo [0m] [1mReceived ping event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mclient_ip[0m=[35mtestclient[0m [36mextra[0m=[35m{'event_type': 'ping', 'action': ''}[0m
[2m2026-05-16T19:45:36.750409Z[0m [[33m[1mwarning [0m] [1mMissing required fields in PR event[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35mwarning[0m [36mclient_ip[0m=[35mtestclient[0m
[2m2026-05-16T19:45:36.751228Z[0m [[32m[1minfo [0m] [1mHTTP Request: POST http://testserver/webhook "HTTP/1.1 200 OK"[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'ping', 'action': ''}, 'event': 'Received ping event', 'client_ip': 'testclient', 'level': 'info', 'timestamp': '2026-05-16T19:45:36.750130Z'}
WARNING stampbot.webhook_handler:webhook_handler.py:161 {'event': 'Missing required fields in PR event', 'client_ip': 'testclient', 'level': 'warning', 'timestamp': '2026-05-16T19:45:36.750409Z'}
INFO httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 200 OK"
=========================== short test summary info ============================
FAILED tests/test_main.py::test_webhook_valid_ping - AssertionError: assert 'error' == 'ok'
- ok
+ error
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 101 passed, 3 deselected in 0.86s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceComparisonOperator_Eq_NotEq, occurrence: 1
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -111,7 +111,7 @@
# Route to appropriate handler
if event_type == "pull_request":
result = await self._handle_pull_request(payload)
- elif event_type == "pull_request_review_comment":
+ elif event_type != "pull_request_review_comment":
result = await self._handle_pr_comment(payload)
elif event_type == "issue_comment":
# Issue comments can be on PRs too........................................................................ [ 34%]
.............................F
=================================== FAILURES ===================================
___________________________ test_webhook_valid_ping ____________________________
test_client = <starlette.testclient.TestClient object at 0x7f2fb3ea29c0>
def test_webhook_valid_ping(test_client: TestClient):
"""Test webhook accepts valid ping event."""
import hashlib
import hmac
from stampbot.webhook_handler import webhook_handler
body = json.dumps({"zen": "Design for failure."}).encode()
signature = (
"sha256="
+ hmac.new(
webhook_handler.webhook_secret,
body,
hashlib.sha256,
).hexdigest()
)
response = test_client.post(
"/webhook",
content=body,
headers={
"Content-Type": "application/json",
"X-GitHub-Event": "ping",
"X-Hub-Signature-256": signature,
},
)
assert response.status_code == 200
data = response.json()
> assert data["status"] == "ok"
E AssertionError: assert 'ignored' == 'ok'
E
E - ok
E + ignored
tests/test_main.py:177: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:42:55.251435Z[0m [[32m[1minfo [0m] [1mReceived ping event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mclient_ip[0m=[35mtestclient[0m [36mextra[0m=[35m{'event_type': 'ping', 'action': ''}[0m
[2m2026-05-16T19:42:55.252313Z[0m [[32m[1minfo [0m] [1mHTTP Request: POST http://testserver/webhook "HTTP/1.1 200 OK"[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'ping', 'action': ''}, 'event': 'Received ping event', 'client_ip': 'testclient', 'level': 'info', 'timestamp': '2026-05-16T19:42:55.251435Z'}
INFO httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 200 OK"
=========================== short test summary info ============================
FAILED tests/test_main.py::test_webhook_valid_ping - AssertionError: assert 'ignored' == 'ok'
- ok
+ ignored
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 101 passed, 3 deselected in 0.86s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceComparisonOperator_Eq_NotEq, occurrence: 2
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -113,7 +113,7 @@
result = await self._handle_pull_request(payload)
elif event_type == "pull_request_review_comment":
result = await self._handle_pr_comment(payload)
- elif event_type == "issue_comment":
+ elif event_type != "issue_comment":
# Issue comments can be on PRs too
if "pull_request" in payload.get("issue", {}):
result = await self._handle_pr_comment(payload)........................................................................ [ 34%]
.............................F
=================================== FAILURES ===================================
___________________________ test_webhook_valid_ping ____________________________
test_client = <starlette.testclient.TestClient object at 0x7ff41d5929c0>
def test_webhook_valid_ping(test_client: TestClient):
"""Test webhook accepts valid ping event."""
import hashlib
import hmac
from stampbot.webhook_handler import webhook_handler
body = json.dumps({"zen": "Design for failure."}).encode()
signature = (
"sha256="
+ hmac.new(
webhook_handler.webhook_secret,
body,
hashlib.sha256,
).hexdigest()
)
response = test_client.post(
"/webhook",
content=body,
headers={
"Content-Type": "application/json",
"X-GitHub-Event": "ping",
"X-Hub-Signature-256": signature,
},
)
assert response.status_code == 200
data = response.json()
> assert data["status"] == "ok"
E AssertionError: assert 'ignored' == 'ok'
E
E - ok
E + ignored
tests/test_main.py:177: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:53:40.554662Z[0m [[32m[1minfo [0m] [1mReceived ping event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mclient_ip[0m=[35mtestclient[0m [36mextra[0m=[35m{'event_type': 'ping', 'action': ''}[0m
[2m2026-05-16T19:53:40.555559Z[0m [[32m[1minfo [0m] [1mHTTP Request: POST http://testserver/webhook "HTTP/1.1 200 OK"[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'ping', 'action': ''}, 'event': 'Received ping event', 'client_ip': 'testclient', 'level': 'info', 'timestamp': '2026-05-16T19:53:40.554662Z'}
INFO httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 200 OK"
=========================== short test summary info ============================
FAILED tests/test_main.py::test_webhook_valid_ping - AssertionError: assert 'ignored' == 'ok'
- ok
+ ignored
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 101 passed, 3 deselected in 0.87s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceComparisonOperator_Eq_NotEq, occurrence: 3
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -119,7 +119,7 @@
result = await self._handle_pr_comment(payload)
else:
result = {"status": "ignored", "message": "Not a PR comment"}
- elif event_type == "ping":
+ elif event_type != "ping":
result = {"status": "ok", "message": "pong"}
else:
result = {"status": "ignored", "message": f"Event type {event_type} not handled"}........................................................................ [ 34%]
.............................F
=================================== FAILURES ===================================
___________________________ test_webhook_valid_ping ____________________________
test_client = <starlette.testclient.TestClient object at 0x7f725acae9c0>
def test_webhook_valid_ping(test_client: TestClient):
"""Test webhook accepts valid ping event."""
import hashlib
import hmac
from stampbot.webhook_handler import webhook_handler
body = json.dumps({"zen": "Design for failure."}).encode()
signature = (
"sha256="
+ hmac.new(
webhook_handler.webhook_secret,
body,
hashlib.sha256,
).hexdigest()
)
response = test_client.post(
"/webhook",
content=body,
headers={
"Content-Type": "application/json",
"X-GitHub-Event": "ping",
"X-Hub-Signature-256": signature,
},
)
assert response.status_code == 200
data = response.json()
> assert data["status"] == "ok"
E AssertionError: assert 'ignored' == 'ok'
E
E - ok
E + ignored
tests/test_main.py:177: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:49:51.557514Z[0m [[32m[1minfo [0m] [1mReceived ping event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mclient_ip[0m=[35mtestclient[0m [36mextra[0m=[35m{'event_type': 'ping', 'action': ''}[0m
[2m2026-05-16T19:49:51.558396Z[0m [[32m[1minfo [0m] [1mHTTP Request: POST http://testserver/webhook "HTTP/1.1 200 OK"[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'ping', 'action': ''}, 'event': 'Received ping event', 'client_ip': 'testclient', 'level': 'info', 'timestamp': '2026-05-16T19:49:51.557514Z'}
INFO httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 200 OK"
=========================== short test summary info ============================
FAILED tests/test_main.py::test_webhook_valid_ping - AssertionError: assert 'ignored' == 'ok'
- ok
+ ignored
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 101 passed, 3 deselected in 0.86s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceComparisonOperator_Eq_NotEq, occurrence: 4
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -187,7 +187,7 @@
},
)
- if action == "opened":
+ if action != "opened":
await run_in_threadpool(
github_client.create_pr_review_comment,
installation_id,........................................................................ [ 34%]
........................................................................ [ 68%]
................................................F
=================================== FAILURES ===================================
____________________ test_invalid_repo_config_posts_review _____________________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7ffb89e87530>
mock_github_client = <MagicMock name='github_client' id='140718328261536'>
@pytest.mark.asyncio
async def test_invalid_repo_config_posts_review(webhook_handler, mock_github_client):
"""Test invalid repo config logs and posts a review comment."""
payload = load_fixture("pr_opened_with_autoapprove_label")
mock_github_client.get_repo_file.return_value = 'chatops_required_permission = "invalid"'
result = await webhook_handler.handle_event("pull_request", payload)
assert result["status"] == "error"
assert "invalid" in result["message"].lower()
> mock_github_client.create_pr_review_comment.assert_called_once()
tests/test_webhook_handler.py:518:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <MagicMock name='github_client.create_pr_review_comment' id='140718328262544'>
def assert_called_once(self):
"""assert that the mock was called only once.
"""
if not self.call_count == 1:
msg = ("Expected '%s' to have been called once. Called %s times.%s"
% (self._mock_name or 'mock',
self.call_count,
self._calls_repr()))
> raise AssertionError(msg)
E AssertionError: Expected 'create_pr_review_comment' to have been called once. Called 0 times.
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:964: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:48:52.636892Z[0m [[32m[1minfo [0m] [1mReceived pull_request event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'pull_request', 'action': 'opened'}[0m
[2m2026-05-16T19:48:52.638263Z[0m [[33m[1mwarning [0m] [1mInvalid stampbot.toml in octocat/hello-world: Invalid chatops_required_permission: invalid. Valid values: none, read, triage, write, maintain, admin[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35mwarning[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'error': 'Invalid chatops_required_permission: invalid. Valid values: none, read, triage, write, maintain, admin'}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'level': 'info', 'timestamp': '2026-05-16T19:48:52.636892Z'}
WARNING stampbot.webhook_handler:webhook_handler.py:176 {'extra': {'repo': 'octocat/hello-world', 'error': 'Invalid chatops_required_permission: invalid. Valid values: none, read, triage, write, maintain, admin'}, 'event': 'Invalid stampbot.toml in octocat/hello-world: Invalid chatops_required_permission: invalid. Valid values: none, read, triage, write, maintain, admin', 'level': 'warning', 'timestamp': '2026-05-16T19:48:52.638263Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_invalid_repo_config_posts_review - AssertionError: Expected 'create_pr_review_comment' to have been called once. Called 0 times.
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 192 passed, 3 deselected in 1.63s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceComparisonOperator_Eq_NotEq, occurrence: 5
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -206,7 +206,7 @@
"message": "Invalid repository configuration",
}
- if action == "opened":
+ if action != "opened":
for label in repo_config.approval_labels:
label_exists = await run_in_threadpool(
github_client.repo_has_label,........................................................................ [ 34%]
........................................................................ [ 68%]
.....................F
=================================== FAILURES ===================================
__________________ test_pr_opened_missing_label_logs_warning ___________________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7f9b3a1a0830>
mock_github_client = <MagicMock name='github_client' id='140304611388656'>
@pytest.mark.asyncio
async def test_pr_opened_missing_label_logs_warning(webhook_handler, mock_github_client):
"""Test missing approval label triggers label check."""
payload = load_fixture("pr_opened_with_autoapprove_label")
mock_github_client.repo_has_label.return_value = False
result = await webhook_handler.handle_event("pull_request", payload)
assert result["status"] == "success"
> assert mock_github_client.repo_has_label.call_count == 2
E AssertionError: assert 0 == 2
E + where 0 = <MagicMock name='github_client.repo_has_label' id='140304611385968'>.call_count
E + where <MagicMock name='github_client.repo_has_label' id='140304611385968'> = <MagicMock name='github_client' id='140304611388656'>.repo_has_label
tests/test_webhook_handler.py:101: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:53:15.824602Z[0m [[32m[1minfo [0m] [1mReceived pull_request event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'pull_request', 'action': 'opened'}[0m
[2m2026-05-16T19:53:15.825136Z[0m [[32m[1minfo [0m] [1mNo stampbot.toml found in octocat/hello-world, using defaults[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'org_repo': None}[0m
[2m2026-05-16T19:53:15.825665Z[0m [[32m[1minfo [0m] [1mPR #42 has approval label: autoapprove[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'level': 'info', 'timestamp': '2026-05-16T19:53:15.824602Z'}
INFO stampbot.webhook_handler:webhook_handler.py:593 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'level': 'info', 'timestamp': '2026-05-16T19:53:15.825136Z'}
INFO stampbot.webhook_handler:webhook_handler.py:278 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'level': 'info', 'timestamp': '2026-05-16T19:53:15.825665Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_opened_missing_label_logs_warning - AssertionError: assert 0 == 2
+ where 0 = <MagicMock name='github_client.repo_has_label' id='140304611385968'>.call_count
+ where <MagicMock name='github_client.repo_has_label' id='140304611385968'> = <MagicMock name='github_client' id='140304611388656'>.repo_has_label
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 165 passed, 3 deselected in 1.08s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceComparisonOperator_Eq_NotEq, occurrence: 6
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -314,7 +314,7 @@
}
# Check if we should remove approval when label is removed
- if repo_config.auto_approve_on_label and action == "unlabeled":
+ if repo_config.auto_approve_on_label and action != "unlabeled":
removed_label = payload.get("label", {}).get("name")
if removed_label in repo_config.approval_labels:
logger.info(........................................................................ [ 34%]
........................................................................ [ 68%]
.........................F
=================================== FAILURES ===================================
________________________ test_pr_unlabeled_autoapprove _________________________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7f5096ff1250>
mock_github_client = <MagicMock name='github_client' id='139984169526880'>
@pytest.mark.asyncio
async def test_pr_unlabeled_autoapprove(webhook_handler, mock_github_client):
"""Test removing autoapprove label dismisses approvals."""
payload = load_fixture("pr_unlabeled_autoapprove")
mock_github_client.find_bot_reviews.return_value = [111, 222] # Mock review IDs
result = await webhook_handler.handle_event("pull_request", payload)
> assert result["status"] == "success"
E AssertionError: assert 'ignored' == 'success'
E
E - success
E + ignored
tests/test_webhook_handler.py:152: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T20:09:50.493112Z[0m [[32m[1minfo [0m] [1mReceived pull_request event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'pull_request', 'action': 'unlabeled'}[0m
[2m2026-05-16T20:09:50.493672Z[0m [[32m[1minfo [0m] [1mNo stampbot.toml found in octocat/hello-world, using defaults[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'org_repo': None}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'pull_request', 'action': 'unlabeled'}, 'event': 'Received pull_request event', 'level': 'info', 'timestamp': '2026-05-16T20:09:50.493112Z'}
INFO stampbot.webhook_handler:webhook_handler.py:593 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'level': 'info', 'timestamp': '2026-05-16T20:09:50.493672Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_unlabeled_autoapprove - AssertionError: assert 'ignored' == 'success'
- success
+ ignored
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 169 passed, 3 deselected in 1.14s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceComparisonOperator_Eq_NotEq, occurrence: 7
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -572,7 +572,7 @@
org_repo_full_name = None
if (
- owner_type == "Organization"
+ owner_type != "Organization"
and owner_login
and repo_full_name != f"{owner_login}/.github"
):........................................................................ [ 34%]
........................................................................ [ 68%]
............................................F
=================================== FAILURES ===================================
_____________________ test_repo_config_uses_default_branch _____________________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7f3a5aa83590>
mock_github_client = <MagicMock name='github_client' id='139888604723056'>
@pytest.mark.asyncio
async def test_repo_config_uses_default_branch(webhook_handler, mock_github_client):
"""Test repo config is loaded from the default branch."""
payload = load_fixture("pr_opened_with_autoapprove_label")
payload["repository"]["default_branch"] = "develop"
payload["pull_request"]["base"]["ref"] = "release"
result = await webhook_handler.handle_event("pull_request", payload)
assert result["status"] == "success"
args = mock_github_client.get_repo_file.call_args[0]
> assert args[3] == "develop"
E AssertionError: assert None == 'develop'
tests/test_webhook_handler.py:434: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:51:42.564427Z[0m [[32m[1minfo [0m] [1mReceived pull_request event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'pull_request', 'action': 'opened'}[0m
[2m2026-05-16T19:51:42.565091Z[0m [[32m[1minfo [0m] [1mNo stampbot.toml found in octocat/hello-world or octocat/.github, using defaults[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'org_repo': 'octocat/.github'}[0m
[2m2026-05-16T19:51:42.565894Z[0m [[32m[1minfo [0m] [1mPR #42 has approval label: autoapprove[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'level': 'info', 'timestamp': '2026-05-16T19:51:42.564427Z'}
INFO stampbot.webhook_handler:webhook_handler.py:593 {'extra': {'repo': 'octocat/hello-world', 'org_repo': 'octocat/.github'}, 'event': 'No stampbot.toml found in octocat/hello-world or octocat/.github, using defaults', 'level': 'info', 'timestamp': '2026-05-16T19:51:42.565091Z'}
INFO stampbot.webhook_handler:webhook_handler.py:278 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'level': 'info', 'timestamp': '2026-05-16T19:51:42.565894Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_repo_config_uses_default_branch - AssertionError: assert None == 'develop'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 188 passed, 3 deselected in 1.54s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceComparisonOperator_Eq_Lt, occurrence: 0
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -109,7 +109,7 @@
)
# Route to appropriate handler
- if event_type == "pull_request":
+ if event_type < "pull_request":
result = await self._handle_pull_request(payload)
elif event_type == "pull_request_review_comment":
result = await self._handle_pr_comment(payload)........................................................................ [ 34%]
.............................F
=================================== FAILURES ===================================
___________________________ test_webhook_valid_ping ____________________________
test_client = <starlette.testclient.TestClient object at 0x7fdf877a69c0>
def test_webhook_valid_ping(test_client: TestClient):
"""Test webhook accepts valid ping event."""
import hashlib
import hmac
from stampbot.webhook_handler import webhook_handler
body = json.dumps({"zen": "Design for failure."}).encode()
signature = (
"sha256="
+ hmac.new(
webhook_handler.webhook_secret,
body,
hashlib.sha256,
).hexdigest()
)
response = test_client.post(
"/webhook",
content=body,
headers={
"Content-Type": "application/json",
"X-GitHub-Event": "ping",
"X-Hub-Signature-256": signature,
},
)
assert response.status_code == 200
data = response.json()
> assert data["status"] == "ok"
E AssertionError: assert 'error' == 'ok'
E
E - ok
E + error
tests/test_main.py:177: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:24:52.870047Z[0m [[32m[1minfo [0m] [1mReceived ping event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mclient_ip[0m=[35mtestclient[0m [36mextra[0m=[35m{'event_type': 'ping', 'action': ''}[0m
[2m2026-05-16T19:24:52.870311Z[0m [[33m[1mwarning [0m] [1mMissing required fields in PR event[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35mwarning[0m [36mclient_ip[0m=[35mtestclient[0m
[2m2026-05-16T19:24:52.871108Z[0m [[32m[1minfo [0m] [1mHTTP Request: POST http://testserver/webhook "HTTP/1.1 200 OK"[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'ping', 'action': ''}, 'event': 'Received ping event', 'client_ip': 'testclient', 'level': 'info', 'timestamp': '2026-05-16T19:24:52.870047Z'}
WARNING stampbot.webhook_handler:webhook_handler.py:161 {'event': 'Missing required fields in PR event', 'client_ip': 'testclient', 'level': 'warning', 'timestamp': '2026-05-16T19:24:52.870311Z'}
INFO httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 200 OK"
=========================== short test summary info ============================
FAILED tests/test_main.py::test_webhook_valid_ping - AssertionError: assert 'error' == 'ok'
- ok
+ error
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 101 passed, 3 deselected in 0.87s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceComparisonOperator_Eq_Lt, occurrence: 1
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -111,7 +111,7 @@
# Route to appropriate handler
if event_type == "pull_request":
result = await self._handle_pull_request(payload)
- elif event_type == "pull_request_review_comment":
+ elif event_type < "pull_request_review_comment":
result = await self._handle_pr_comment(payload)
elif event_type == "issue_comment":
# Issue comments can be on PRs too........................................................................ [ 34%]
.............................F
=================================== FAILURES ===================================
___________________________ test_webhook_valid_ping ____________________________
test_client = <starlette.testclient.TestClient object at 0x7f79dfba69c0>
def test_webhook_valid_ping(test_client: TestClient):
"""Test webhook accepts valid ping event."""
import hashlib
import hmac
from stampbot.webhook_handler import webhook_handler
body = json.dumps({"zen": "Design for failure."}).encode()
signature = (
"sha256="
+ hmac.new(
webhook_handler.webhook_secret,
body,
hashlib.sha256,
).hexdigest()
)
response = test_client.post(
"/webhook",
content=body,
headers={
"Content-Type": "application/json",
"X-GitHub-Event": "ping",
"X-Hub-Signature-256": signature,
},
)
assert response.status_code == 200
data = response.json()
> assert data["status"] == "ok"
E AssertionError: assert 'ignored' == 'ok'
E
E - ok
E + ignored
tests/test_main.py:177: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:38:27.890488Z[0m [[32m[1minfo [0m] [1mReceived ping event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mclient_ip[0m=[35mtestclient[0m [36mextra[0m=[35m{'event_type': 'ping', 'action': ''}[0m
[2m2026-05-16T19:38:27.891396Z[0m [[32m[1minfo [0m] [1mHTTP Request: POST http://testserver/webhook "HTTP/1.1 200 OK"[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'ping', 'action': ''}, 'event': 'Received ping event', 'client_ip': 'testclient', 'level': 'info', 'timestamp': '2026-05-16T19:38:27.890488Z'}
INFO httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 200 OK"
=========================== short test summary info ============================
FAILED tests/test_main.py::test_webhook_valid_ping - AssertionError: assert 'ignored' == 'ok'
- ok
+ ignored
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 101 passed, 3 deselected in 0.87s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceComparisonOperator_Eq_Lt, occurrence: 2
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -113,7 +113,7 @@
result = await self._handle_pull_request(payload)
elif event_type == "pull_request_review_comment":
result = await self._handle_pr_comment(payload)
- elif event_type == "issue_comment":
+ elif event_type < "issue_comment":
# Issue comments can be on PRs too
if "pull_request" in payload.get("issue", {}):
result = await self._handle_pr_comment(payload)........................................................................ [ 34%]
........................................................................ [ 68%]
.................................F
=================================== FAILURES ===================================
__________________________ test_issue_comment_approve __________________________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7f76f4533330>
mock_github_client = <MagicMock name='github_client' id='140148882452368'>
@pytest.mark.asyncio
async def test_issue_comment_approve(webhook_handler, mock_github_client):
"""Test @stampbot approve command."""
payload = load_fixture("issue_comment_approve")
result = await webhook_handler.handle_event("issue_comment", payload)
> assert result["status"] == "success"
E AssertionError: assert 'ignored' == 'success'
E
E - success
E + ignored
tests/test_webhook_handler.py:259: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:47:44.126574Z[0m [[32m[1minfo [0m] [1mReceived issue_comment event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'issue_comment', 'action': 'created'}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'level': 'info', 'timestamp': '2026-05-16T19:47:44.126574Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_issue_comment_approve - AssertionError: assert 'ignored' == 'success'
- success
+ ignored
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 177 passed, 3 deselected in 1.11s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceComparisonOperator_Eq_Lt, occurrence: 3
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -119,7 +119,7 @@
result = await self._handle_pr_comment(payload)
else:
result = {"status": "ignored", "message": "Not a PR comment"}
- elif event_type == "ping":
+ elif event_type < "ping":
result = {"status": "ok", "message": "pong"}
else:
result = {"status": "ignored", "message": f"Event type {event_type} not handled"}........................................................................ [ 34%]
.............................F
=================================== FAILURES ===================================
___________________________ test_webhook_valid_ping ____________________________
test_client = <starlette.testclient.TestClient object at 0x7f18145b69c0>
def test_webhook_valid_ping(test_client: TestClient):
"""Test webhook accepts valid ping event."""
import hashlib
import hmac
from stampbot.webhook_handler import webhook_handler
body = json.dumps({"zen": "Design for failure."}).encode()
signature = (
"sha256="
+ hmac.new(
webhook_handler.webhook_secret,
body,
hashlib.sha256,
).hexdigest()
)
response = test_client.post(
"/webhook",
content=body,
headers={
"Content-Type": "application/json",
"X-GitHub-Event": "ping",
"X-Hub-Signature-256": signature,
},
)
assert response.status_code == 200
data = response.json()
> assert data["status"] == "ok"
E AssertionError: assert 'ignored' == 'ok'
E
E - ok
E + ignored
tests/test_main.py:177: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:29:01.452336Z[0m [[32m[1minfo [0m] [1mReceived ping event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mclient_ip[0m=[35mtestclient[0m [36mextra[0m=[35m{'event_type': 'ping', 'action': ''}[0m
[2m2026-05-16T19:29:01.453205Z[0m [[32m[1minfo [0m] [1mHTTP Request: POST http://testserver/webhook "HTTP/1.1 200 OK"[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'ping', 'action': ''}, 'event': 'Received ping event', 'client_ip': 'testclient', 'level': 'info', 'timestamp': '2026-05-16T19:29:01.452336Z'}
INFO httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 200 OK"
=========================== short test summary info ============================
FAILED tests/test_main.py::test_webhook_valid_ping - AssertionError: assert 'ignored' == 'ok'
- ok
+ ignored
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 101 passed, 3 deselected in 0.84s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceComparisonOperator_Eq_Lt, occurrence: 4
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -187,7 +187,7 @@
},
)
- if action == "opened":
+ if action < "opened":
await run_in_threadpool(
github_client.create_pr_review_comment,
installation_id,........................................................................ [ 34%]
........................................................................ [ 68%]
................................................F
=================================== FAILURES ===================================
____________________ test_invalid_repo_config_posts_review _____________________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7ff6c4c87590>
mock_github_client = <MagicMock name='github_client' id='140697841183648'>
@pytest.mark.asyncio
async def test_invalid_repo_config_posts_review(webhook_handler, mock_github_client):
"""Test invalid repo config logs and posts a review comment."""
payload = load_fixture("pr_opened_with_autoapprove_label")
mock_github_client.get_repo_file.return_value = 'chatops_required_permission = "invalid"'
result = await webhook_handler.handle_event("pull_request", payload)
assert result["status"] == "error"
assert "invalid" in result["message"].lower()
> mock_github_client.create_pr_review_comment.assert_called_once()
tests/test_webhook_handler.py:518:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <MagicMock name='github_client.create_pr_review_comment' id='140697841184656'>
def assert_called_once(self):
"""assert that the mock was called only once.
"""
if not self.call_count == 1:
msg = ("Expected '%s' to have been called once. Called %s times.%s"
% (self._mock_name or 'mock',
self.call_count,
self._calls_repr()))
> raise AssertionError(msg)
E AssertionError: Expected 'create_pr_review_comment' to have been called once. Called 0 times.
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:964: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:56:38.711491Z[0m [[32m[1minfo [0m] [1mReceived pull_request event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'pull_request', 'action': 'opened'}[0m
[2m2026-05-16T19:56:38.712850Z[0m [[33m[1mwarning [0m] [1mInvalid stampbot.toml in octocat/hello-world: Invalid chatops_required_permission: invalid. Valid values: none, read, triage, write, maintain, admin[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35mwarning[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'error': 'Invalid chatops_required_permission: invalid. Valid values: none, read, triage, write, maintain, admin'}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'level': 'info', 'timestamp': '2026-05-16T19:56:38.711491Z'}
WARNING stampbot.webhook_handler:webhook_handler.py:176 {'extra': {'repo': 'octocat/hello-world', 'error': 'Invalid chatops_required_permission: invalid. Valid values: none, read, triage, write, maintain, admin'}, 'event': 'Invalid stampbot.toml in octocat/hello-world: Invalid chatops_required_permission: invalid. Valid values: none, read, triage, write, maintain, admin', 'level': 'warning', 'timestamp': '2026-05-16T19:56:38.712850Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_invalid_repo_config_posts_review - AssertionError: Expected 'create_pr_review_comment' to have been called once. Called 0 times.
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 192 passed, 3 deselected in 1.63s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceComparisonOperator_Eq_Lt, occurrence: 5
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -206,7 +206,7 @@
"message": "Invalid repository configuration",
}
- if action == "opened":
+ if action < "opened":
for label in repo_config.approval_labels:
label_exists = await run_in_threadpool(
github_client.repo_has_label,........................................................................ [ 34%]
........................................................................ [ 68%]
.....................F
=================================== FAILURES ===================================
__________________ test_pr_opened_missing_label_logs_warning ___________________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7f4fabe94830>
mock_github_client = <MagicMock name='github_client' id='139980103462128'>
@pytest.mark.asyncio
async def test_pr_opened_missing_label_logs_warning(webhook_handler, mock_github_client):
"""Test missing approval label triggers label check."""
payload = load_fixture("pr_opened_with_autoapprove_label")
mock_github_client.repo_has_label.return_value = False
result = await webhook_handler.handle_event("pull_request", payload)
assert result["status"] == "success"
> assert mock_github_client.repo_has_label.call_count == 2
E AssertionError: assert 0 == 2
E + where 0 = <MagicMock name='github_client.repo_has_label' id='139980103459440'>.call_count
E + where <MagicMock name='github_client.repo_has_label' id='139980103459440'> = <MagicMock name='github_client' id='139980103462128'>.repo_has_label
tests/test_webhook_handler.py:101: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:36:12.709649Z[0m [[32m[1minfo [0m] [1mReceived pull_request event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'pull_request', 'action': 'opened'}[0m
[2m2026-05-16T19:36:12.710185Z[0m [[32m[1minfo [0m] [1mNo stampbot.toml found in octocat/hello-world, using defaults[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'org_repo': None}[0m
[2m2026-05-16T19:36:12.710743Z[0m [[32m[1minfo [0m] [1mPR #42 has approval label: autoapprove[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'level': 'info', 'timestamp': '2026-05-16T19:36:12.709649Z'}
INFO stampbot.webhook_handler:webhook_handler.py:593 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'level': 'info', 'timestamp': '2026-05-16T19:36:12.710185Z'}
INFO stampbot.webhook_handler:webhook_handler.py:278 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'level': 'info', 'timestamp': '2026-05-16T19:36:12.710743Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_opened_missing_label_logs_warning - AssertionError: assert 0 == 2
+ where 0 = <MagicMock name='github_client.repo_has_label' id='139980103459440'>.call_count
+ where <MagicMock name='github_client.repo_has_label' id='139980103459440'> = <MagicMock name='github_client' id='139980103462128'>.repo_has_label
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 165 passed, 3 deselected in 1.11s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceComparisonOperator_Eq_Lt, occurrence: 6
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -314,7 +314,7 @@
}
# Check if we should remove approval when label is removed
- if repo_config.auto_approve_on_label and action == "unlabeled":
+ if repo_config.auto_approve_on_label and action < "unlabeled":
removed_label = payload.get("label", {}).get("name")
if removed_label in repo_config.approval_labels:
logger.info(........................................................................ [ 34%]
........................................................................ [ 68%]
.........................F
=================================== FAILURES ===================================
________________________ test_pr_unlabeled_autoapprove _________________________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7f06ea6f5650>
mock_github_client = <MagicMock name='github_client' id='139667744958048'>
@pytest.mark.asyncio
async def test_pr_unlabeled_autoapprove(webhook_handler, mock_github_client):
"""Test removing autoapprove label dismisses approvals."""
payload = load_fixture("pr_unlabeled_autoapprove")
mock_github_client.find_bot_reviews.return_value = [111, 222] # Mock review IDs
result = await webhook_handler.handle_event("pull_request", payload)
> assert result["status"] == "success"
E AssertionError: assert 'ignored' == 'success'
E
E - success
E + ignored
tests/test_webhook_handler.py:152: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:31:37.892361Z[0m [[32m[1minfo [0m] [1mReceived pull_request event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'pull_request', 'action': 'unlabeled'}[0m
[2m2026-05-16T19:31:37.892889Z[0m [[32m[1minfo [0m] [1mNo stampbot.toml found in octocat/hello-world, using defaults[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'org_repo': None}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'pull_request', 'action': 'unlabeled'}, 'event': 'Received pull_request event', 'level': 'info', 'timestamp': '2026-05-16T19:31:37.892361Z'}
INFO stampbot.webhook_handler:webhook_handler.py:593 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'level': 'info', 'timestamp': '2026-05-16T19:31:37.892889Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_unlabeled_autoapprove - AssertionError: assert 'ignored' == 'success'
- success
+ ignored
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 169 passed, 3 deselected in 1.08s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceComparisonOperator_Eq_Lt, occurrence: 7
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -572,7 +572,7 @@
org_repo_full_name = None
if (
- owner_type == "Organization"
+ owner_type < "Organization"
and owner_login
and repo_full_name != f"{owner_login}/.github"
):........................................................................ [ 34%]
........................................................................ [ 68%]
.............................................F
=================================== FAILURES ===================================
_____________________ test_org_github_repo_config_fallback _____________________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7f60abfbb770>
mock_github_client = <MagicMock name='github_client' id='140053179778112'>
@pytest.mark.asyncio
async def test_org_github_repo_config_fallback(webhook_handler, mock_github_client):
"""Test org .github stampbot.toml is used when repo config is missing."""
payload = load_fixture("pr_opened_with_autoapprove_label")
payload["repository"]["full_name"] = "acme/widgets"
payload["repository"]["default_branch"] = "main"
payload["repository"]["owner"] = {"login": "acme", "type": "Organization"}
payload["pull_request"]["labels"] = [{"name": "org-approve"}]
def get_repo_file_side_effect(_installation_id, repo_full_name, _file_path, _ref):
if repo_full_name == "acme/widgets":
return None
if repo_full_name == "acme/.github":
return 'approval_labels = ["org-approve"]'
return None
mock_github_client.get_repo_file.side_effect = get_repo_file_side_effect
result = await webhook_handler.handle_event("pull_request", payload)
> assert result["status"] == "success"
E AssertionError: assert 'ignored' == 'success'
E
E - success
E + ignored
tests/test_webhook_handler.py:457: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:52:36.864734Z[0m [[32m[1minfo [0m] [1mReceived pull_request event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'pull_request', 'action': 'opened'}[0m
[2m2026-05-16T19:52:36.865294Z[0m [[32m[1minfo [0m] [1mNo stampbot.toml found in acme/widgets, using defaults[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'acme/widgets', 'org_repo': None}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'level': 'info', 'timestamp': '2026-05-16T19:52:36.864734Z'}
INFO stampbot.webhook_handler:webhook_handler.py:593 {'extra': {'repo': 'acme/widgets', 'org_repo': None}, 'event': 'No stampbot.toml found in acme/widgets, using defaults', 'level': 'info', 'timestamp': '2026-05-16T19:52:36.865294Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_org_github_repo_config_fallback - AssertionError: assert 'ignored' == 'success'
- success
+ ignored
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 189 passed, 3 deselected in 1.56s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceComparisonOperator_Eq_LtE, occurrence: 0
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -109,7 +109,7 @@
)
# Route to appropriate handler
- if event_type == "pull_request":
+ if event_type <= "pull_request":
result = await self._handle_pull_request(payload)
elif event_type == "pull_request_review_comment":
result = await self._handle_pr_comment(payload)........................................................................ [ 34%]
.............................F
=================================== FAILURES ===================================
___________________________ test_webhook_valid_ping ____________________________
test_client = <starlette.testclient.TestClient object at 0x7f900cdba9c0>
def test_webhook_valid_ping(test_client: TestClient):
"""Test webhook accepts valid ping event."""
import hashlib
import hmac
from stampbot.webhook_handler import webhook_handler
body = json.dumps({"zen": "Design for failure."}).encode()
signature = (
"sha256="
+ hmac.new(
webhook_handler.webhook_secret,
body,
hashlib.sha256,
).hexdigest()
)
response = test_client.post(
"/webhook",
content=body,
headers={
"Content-Type": "application/json",
"X-GitHub-Event": "ping",
"X-Hub-Signature-256": signature,
},
)
assert response.status_code == 200
data = response.json()
> assert data["status"] == "ok"
E AssertionError: assert 'error' == 'ok'
E
E - ok
E + error
tests/test_main.py:177: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:41:46.001837Z[0m [[32m[1minfo [0m] [1mReceived ping event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mclient_ip[0m=[35mtestclient[0m [36mextra[0m=[35m{'event_type': 'ping', 'action': ''}[0m
[2m2026-05-16T19:41:46.002073Z[0m [[33m[1mwarning [0m] [1mMissing required fields in PR event[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35mwarning[0m [36mclient_ip[0m=[35mtestclient[0m
[2m2026-05-16T19:41:46.002905Z[0m [[32m[1minfo [0m] [1mHTTP Request: POST http://testserver/webhook "HTTP/1.1 200 OK"[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'ping', 'action': ''}, 'event': 'Received ping event', 'client_ip': 'testclient', 'level': 'info', 'timestamp': '2026-05-16T19:41:46.001837Z'}
WARNING stampbot.webhook_handler:webhook_handler.py:161 {'event': 'Missing required fields in PR event', 'client_ip': 'testclient', 'level': 'warning', 'timestamp': '2026-05-16T19:41:46.002073Z'}
INFO httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 200 OK"
=========================== short test summary info ============================
FAILED tests/test_main.py::test_webhook_valid_ping - AssertionError: assert 'error' == 'ok'
- ok
+ error
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 101 passed, 3 deselected in 0.86s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceComparisonOperator_Eq_LtE, occurrence: 1
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -111,7 +111,7 @@
# Route to appropriate handler
if event_type == "pull_request":
result = await self._handle_pull_request(payload)
- elif event_type == "pull_request_review_comment":
+ elif event_type <= "pull_request_review_comment":
result = await self._handle_pr_comment(payload)
elif event_type == "issue_comment":
# Issue comments can be on PRs too........................................................................ [ 34%]
.............................F
=================================== FAILURES ===================================
___________________________ test_webhook_valid_ping ____________________________
test_client = <starlette.testclient.TestClient object at 0x7fc1226c69c0>
def test_webhook_valid_ping(test_client: TestClient):
"""Test webhook accepts valid ping event."""
import hashlib
import hmac
from stampbot.webhook_handler import webhook_handler
body = json.dumps({"zen": "Design for failure."}).encode()
signature = (
"sha256="
+ hmac.new(
webhook_handler.webhook_secret,
body,
hashlib.sha256,
).hexdigest()
)
response = test_client.post(
"/webhook",
content=body,
headers={
"Content-Type": "application/json",
"X-GitHub-Event": "ping",
"X-Hub-Signature-256": signature,
},
)
assert response.status_code == 200
data = response.json()
> assert data["status"] == "ok"
E AssertionError: assert 'ignored' == 'ok'
E
E - ok
E + ignored
tests/test_main.py:177: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:30:22.264327Z[0m [[32m[1minfo [0m] [1mReceived ping event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mclient_ip[0m=[35mtestclient[0m [36mextra[0m=[35m{'event_type': 'ping', 'action': ''}[0m
[2m2026-05-16T19:30:22.265225Z[0m [[32m[1minfo [0m] [1mHTTP Request: POST http://testserver/webhook "HTTP/1.1 200 OK"[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'ping', 'action': ''}, 'event': 'Received ping event', 'client_ip': 'testclient', 'level': 'info', 'timestamp': '2026-05-16T19:30:22.264327Z'}
INFO httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 200 OK"
=========================== short test summary info ============================
FAILED tests/test_main.py::test_webhook_valid_ping - AssertionError: assert 'ignored' == 'ok'
- ok
+ ignored
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 101 passed, 3 deselected in 0.85s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceComparisonOperator_Eq_LtE, occurrence: 2
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -113,7 +113,7 @@
result = await self._handle_pull_request(payload)
elif event_type == "pull_request_review_comment":
result = await self._handle_pr_comment(payload)
- elif event_type == "issue_comment":
+ elif event_type <= "issue_comment":
# Issue comments can be on PRs too
if "pull_request" in payload.get("issue", {}):
result = await self._handle_pr_comment(payload)........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceComparisonOperator_Eq_LtE, occurrence: 3
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -119,7 +119,7 @@
result = await self._handle_pr_comment(payload)
else:
result = {"status": "ignored", "message": "Not a PR comment"}
- elif event_type == "ping":
+ elif event_type <= "ping":
result = {"status": "ok", "message": "pong"}
else:
result = {"status": "ignored", "message": f"Event type {event_type} not handled"}........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceComparisonOperator_Eq_LtE, occurrence: 4
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -187,7 +187,7 @@
},
)
- if action == "opened":
+ if action <= "opened":
await run_in_threadpool(
github_client.create_pr_review_comment,
installation_id,........................................................................ [ 34%]
........................................................................ [ 68%]
.................................................F
=================================== FAILURES ===================================
_______________ test_invalid_repo_config_no_review_on_non_opened _______________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7f65524b3770>
mock_github_client = <MagicMock name='github_client' id='140073149937520'>
@pytest.mark.asyncio
async def test_invalid_repo_config_no_review_on_non_opened(webhook_handler, mock_github_client):
"""Test invalid config skips review comment for non-opened events."""
payload = load_fixture("pr_opened_with_autoapprove_label")
payload["action"] = "labeled"
mock_github_client.get_repo_file.return_value = 'chatops_required_permission = "invalid"'
result = await webhook_handler.handle_event("pull_request", payload)
assert result["status"] == "error"
> mock_github_client.create_pr_review_comment.assert_not_called()
tests/test_webhook_handler.py:532:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <MagicMock name='github_client.create_pr_review_comment' id='140073149938528'>
def assert_not_called(self):
"""assert that the mock was never called.
"""
if self.call_count != 0:
msg = ("Expected '%s' to not have been called. Called %s times.%s"
% (self._mock_name or 'mock',
self.call_count,
self._calls_repr()))
> raise AssertionError(msg)
E AssertionError: Expected 'create_pr_review_comment' to not have been called. Called 1 times.
E Calls: [call(12345, 'octocat/hello-world', 42, 'Stampbot configuration error in `stampbot.toml`:\n\nInvalid chatops_required_permission: invalid. Valid values: none, read, triage, write, maintain, admin\n\nPlease fix the file to re-enable automation.')].
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:946: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T20:04:02.901614Z[0m [[32m[1minfo [0m] [1mReceived pull_request event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'pull_request', 'action': 'labeled'}[0m
[2m2026-05-16T20:04:02.902930Z[0m [[33m[1mwarning [0m] [1mInvalid stampbot.toml in octocat/hello-world: Invalid chatops_required_permission: invalid. Valid values: none, read, triage, write, maintain, admin[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35mwarning[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'error': 'Invalid chatops_required_permission: invalid. Valid values: none, read, triage, write, maintain, admin'}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'pull_request', 'action': 'labeled'}, 'event': 'Received pull_request event', 'level': 'info', 'timestamp': '2026-05-16T20:04:02.901614Z'}
WARNING stampbot.webhook_handler:webhook_handler.py:176 {'extra': {'repo': 'octocat/hello-world', 'error': 'Invalid chatops_required_permission: invalid. Valid values: none, read, triage, write, maintain, admin'}, 'event': 'Invalid stampbot.toml in octocat/hello-world: Invalid chatops_required_permission: invalid. Valid values: none, read, triage, write, maintain, admin', 'level': 'warning', 'timestamp': '2026-05-16T20:04:02.902930Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_invalid_repo_config_no_review_on_non_opened - AssertionError: Expected 'create_pr_review_comment' to not have been called. Called 1 times.
Calls: [call(12345, 'octocat/hello-world', 42, 'Stampbot configuration error in `stampbot.toml`:\n\nInvalid chatops_required_permission: invalid. Valid values: none, read, triage, write, maintain, admin\n\nPlease fix the file to re-enable automation.')].
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 193 passed, 3 deselected in 1.62s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceComparisonOperator_Eq_LtE, occurrence: 5
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -206,7 +206,7 @@
"message": "Invalid repository configuration",
}
- if action == "opened":
+ if action <= "opened":
for label in repo_config.approval_labels:
label_exists = await run_in_threadpool(
github_client.repo_has_label,........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.63s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceComparisonOperator_Eq_LtE, occurrence: 6
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -314,7 +314,7 @@
}
# Check if we should remove approval when label is removed
- if repo_config.auto_approve_on_label and action == "unlabeled":
+ if repo_config.auto_approve_on_label and action <= "unlabeled":
removed_label = payload.get("label", {}).get("name")
if removed_label in repo_config.approval_labels:
logger.info(........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.60s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceComparisonOperator_Eq_LtE, occurrence: 7
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -572,7 +572,7 @@
org_repo_full_name = None
if (
- owner_type == "Organization"
+ owner_type <= "Organization"
and owner_login
and repo_full_name != f"{owner_login}/.github"
):........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.62s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceComparisonOperator_Eq_Gt, occurrence: 0
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -109,7 +109,7 @@
)
# Route to appropriate handler
- if event_type == "pull_request":
+ if event_type > "pull_request":
result = await self._handle_pull_request(payload)
elif event_type == "pull_request_review_comment":
result = await self._handle_pr_comment(payload)........................................................................ [ 34%]
........................................................................ [ 68%]
..................F
=================================== FAILURES ===================================
______________________________ test_unknown_event ______________________________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7ff9812e3250>
@pytest.mark.asyncio
async def test_unknown_event(webhook_handler):
"""Test handling unknown event type."""
payload = {}
result = await webhook_handler.handle_event("unknown", payload)
> assert result["status"] == "ignored"
E AssertionError: assert 'error' == 'ignored'
E
E - ignored
E + error
tests/test_webhook_handler.py:55: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:59:30.559728Z[0m [[32m[1minfo [0m] [1mReceived unknown event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'unknown', 'action': ''}[0m
[2m2026-05-16T19:59:30.559909Z[0m [[33m[1mwarning [0m] [1mMissing required fields in PR event[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35mwarning[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'unknown', 'action': ''}, 'event': 'Received unknown event', 'level': 'info', 'timestamp': '2026-05-16T19:59:30.559728Z'}
WARNING stampbot.webhook_handler:webhook_handler.py:161 {'event': 'Missing required fields in PR event', 'level': 'warning', 'timestamp': '2026-05-16T19:59:30.559909Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_unknown_event - AssertionError: assert 'error' == 'ignored'
- ignored
+ error
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 162 passed, 3 deselected in 1.06s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceComparisonOperator_Eq_Gt, occurrence: 1
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -111,7 +111,7 @@
# Route to appropriate handler
if event_type == "pull_request":
result = await self._handle_pull_request(payload)
- elif event_type == "pull_request_review_comment":
+ elif event_type > "pull_request_review_comment":
result = await self._handle_pr_comment(payload)
elif event_type == "issue_comment":
# Issue comments can be on PRs too........................................................................ [ 34%]
........................................................................ [ 68%]
.......................................................F
=================================== FAILURES ===================================
____________________ test_pull_request_review_comment_event ____________________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7fde722871d0>
mock_github_client = <MagicMock name='github_client' id='140593374794416'>
@pytest.mark.asyncio
async def test_pull_request_review_comment_event(webhook_handler, mock_github_client):
"""Test handling pull_request_review_comment event type."""
payload = {
"action": "created",
"pull_request": {
"number": 42,
},
"comment": {
"body": "@stampbot approve",
"user": {"login": "testuser"},
},
"repository": {
"full_name": "owner/repo",
},
"installation": {"id": 12345},
}
result = await webhook_handler.handle_event("pull_request_review_comment", payload)
> assert result["status"] == "success"
E AssertionError: assert 'ignored' == 'success'
E
E - success
E + ignored
tests/test_webhook_handler.py:625: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T20:08:14.704298Z[0m [[32m[1minfo [0m] [1mReceived pull_request_review_comment event[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'pull_request_review_comment', 'action': 'created'}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'pull_request_review_comment', 'action': 'created'}, 'event': 'Received pull_request_review_comment event', 'level': 'info', 'timestamp': '2026-05-16T20:08:14.704298Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pull_request_review_comment_event - AssertionError: assert 'ignored' == 'success'
- success
+ ignored
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 199 passed, 3 deselected in 1.65s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceComparisonOperator_Eq_Gt, occurrence: 2
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -113,7 +113,7 @@
result = await self._handle_pull_request(payload)
elif event_type == "pull_request_review_comment":
result = await self._handle_pr_comment(payload)
- elif event_type == "issue_comment":
+ elif event_type > "issue_comment":
# Issue comments can be on PRs too
if "pull_request" in payload.get("issue", {}):
result = await self._handle_pr_comment(payload)........................................................................ [ 34%]
.............................F
=================================== FAILURES ===================================
___________________________ test_webhook_valid_ping ____________________________
test_client = <starlette.testclient.TestClient object at 0x7fdc495a29c0>
def test_webhook_valid_ping(test_client: TestClient):
"""Test webhook accepts valid ping event."""
import hashlib
import hmac
from stampbot.webhook_handler import webhook_handler
body = json.dumps({"zen": "Design for failure."}).encode()
signature = (
"sha256="
+ hmac.new(
webhook_handler.webhook_secret,
body,
hashlib.sha256,
).hexdigest()
)
response = test_client.post(
"/webhook",
content=body,
headers={
"Content-Type": "application/json",
"X-GitHub-Event": "ping",
"X-Hub-Signature-256": signature,
},
)
assert response.status_code == 200
data = response.json()
> assert data["status"] == "ok"
E AssertionError: assert 'ignored' == 'ok'
E
E - ok
E + ignored
tests/test_main.py:177: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T20:01:15.405908Z[0m [[32m[1minfo [0m] [1mReceived ping event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mclient_ip[0m=[35mtestclient[0m [36mextra[0m=[35m{'event_type': 'ping', 'action': ''}[0m
[2m2026-05-16T20:01:15.406807Z[0m [[32m[1minfo [0m] [1mHTTP Request: POST http://testserver/webhook "HTTP/1.1 200 OK"[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'ping', 'action': ''}, 'event': 'Received ping event', 'client_ip': 'testclient', 'level': 'info', 'timestamp': '2026-05-16T20:01:15.405908Z'}
INFO httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 200 OK"
=========================== short test summary info ============================
FAILED tests/test_main.py::test_webhook_valid_ping - AssertionError: assert 'ignored' == 'ok'
- ok
+ ignored
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 101 passed, 3 deselected in 0.84s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceComparisonOperator_Eq_Gt, occurrence: 3
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -119,7 +119,7 @@
result = await self._handle_pr_comment(payload)
else:
result = {"status": "ignored", "message": "Not a PR comment"}
- elif event_type == "ping":
+ elif event_type > "ping":
result = {"status": "ok", "message": "pong"}
else:
result = {"status": "ignored", "message": f"Event type {event_type} not handled"}........................................................................ [ 34%]
.............................F
=================================== FAILURES ===================================
___________________________ test_webhook_valid_ping ____________________________
test_client = <starlette.testclient.TestClient object at 0x7fb1a4d929c0>
def test_webhook_valid_ping(test_client: TestClient):
"""Test webhook accepts valid ping event."""
import hashlib
import hmac
from stampbot.webhook_handler import webhook_handler
body = json.dumps({"zen": "Design for failure."}).encode()
signature = (
"sha256="
+ hmac.new(
webhook_handler.webhook_secret,
body,
hashlib.sha256,
).hexdigest()
)
response = test_client.post(
"/webhook",
content=body,
headers={
"Content-Type": "application/json",
"X-GitHub-Event": "ping",
"X-Hub-Signature-256": signature,
},
)
assert response.status_code == 200
data = response.json()
> assert data["status"] == "ok"
E AssertionError: assert 'ignored' == 'ok'
E
E - ok
E + ignored
tests/test_main.py:177: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:37:02.096149Z[0m [[32m[1minfo [0m] [1mReceived ping event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mclient_ip[0m=[35mtestclient[0m [36mextra[0m=[35m{'event_type': 'ping', 'action': ''}[0m
[2m2026-05-16T19:37:02.097051Z[0m [[32m[1minfo [0m] [1mHTTP Request: POST http://testserver/webhook "HTTP/1.1 200 OK"[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'ping', 'action': ''}, 'event': 'Received ping event', 'client_ip': 'testclient', 'level': 'info', 'timestamp': '2026-05-16T19:37:02.096149Z'}
INFO httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 200 OK"
=========================== short test summary info ============================
FAILED tests/test_main.py::test_webhook_valid_ping - AssertionError: assert 'ignored' == 'ok'
- ok
+ ignored
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 101 passed, 3 deselected in 0.85s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceComparisonOperator_Eq_Gt, occurrence: 4
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -187,7 +187,7 @@
},
)
- if action == "opened":
+ if action > "opened":
await run_in_threadpool(
github_client.create_pr_review_comment,
installation_id,........................................................................ [ 34%]
........................................................................ [ 68%]
................................................F
=================================== FAILURES ===================================
____________________ test_invalid_repo_config_posts_review _____________________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7fbaf0497b30>
mock_github_client = <MagicMock name='github_client' id='140440873397152'>
@pytest.mark.asyncio
async def test_invalid_repo_config_posts_review(webhook_handler, mock_github_client):
"""Test invalid repo config logs and posts a review comment."""
payload = load_fixture("pr_opened_with_autoapprove_label")
mock_github_client.get_repo_file.return_value = 'chatops_required_permission = "invalid"'
result = await webhook_handler.handle_event("pull_request", payload)
assert result["status"] == "error"
assert "invalid" in result["message"].lower()
> mock_github_client.create_pr_review_comment.assert_called_once()
tests/test_webhook_handler.py:518:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <MagicMock name='github_client.create_pr_review_comment' id='140440873398160'>
def assert_called_once(self):
"""assert that the mock was called only once.
"""
if not self.call_count == 1:
msg = ("Expected '%s' to have been called once. Called %s times.%s"
% (self._mock_name or 'mock',
self.call_count,
self._calls_repr()))
> raise AssertionError(msg)
E AssertionError: Expected 'create_pr_review_comment' to have been called once. Called 0 times.
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:964: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T20:07:40.409093Z[0m [[32m[1minfo [0m] [1mReceived pull_request event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'pull_request', 'action': 'opened'}[0m
[2m2026-05-16T20:07:40.410540Z[0m [[33m[1mwarning [0m] [1mInvalid stampbot.toml in octocat/hello-world: Invalid chatops_required_permission: invalid. Valid values: none, read, triage, write, maintain, admin[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35mwarning[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'error': 'Invalid chatops_required_permission: invalid. Valid values: none, read, triage, write, maintain, admin'}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'level': 'info', 'timestamp': '2026-05-16T20:07:40.409093Z'}
WARNING stampbot.webhook_handler:webhook_handler.py:176 {'extra': {'repo': 'octocat/hello-world', 'error': 'Invalid chatops_required_permission: invalid. Valid values: none, read, triage, write, maintain, admin'}, 'event': 'Invalid stampbot.toml in octocat/hello-world: Invalid chatops_required_permission: invalid. Valid values: none, read, triage, write, maintain, admin', 'level': 'warning', 'timestamp': '2026-05-16T20:07:40.410540Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_invalid_repo_config_posts_review - AssertionError: Expected 'create_pr_review_comment' to have been called once. Called 0 times.
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 192 passed, 3 deselected in 1.66s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceComparisonOperator_Eq_Gt, occurrence: 5
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -206,7 +206,7 @@
"message": "Invalid repository configuration",
}
- if action == "opened":
+ if action > "opened":
for label in repo_config.approval_labels:
label_exists = await run_in_threadpool(
github_client.repo_has_label,........................................................................ [ 34%]
........................................................................ [ 68%]
.....................F
=================================== FAILURES ===================================
__________________ test_pr_opened_missing_label_logs_warning ___________________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7f0ff70a0830>
mock_github_client = <MagicMock name='github_client' id='139706485845232'>
@pytest.mark.asyncio
async def test_pr_opened_missing_label_logs_warning(webhook_handler, mock_github_client):
"""Test missing approval label triggers label check."""
payload = load_fixture("pr_opened_with_autoapprove_label")
mock_github_client.repo_has_label.return_value = False
result = await webhook_handler.handle_event("pull_request", payload)
assert result["status"] == "success"
> assert mock_github_client.repo_has_label.call_count == 2
E AssertionError: assert 0 == 2
E + where 0 = <MagicMock name='github_client.repo_has_label' id='139706485842544'>.call_count
E + where <MagicMock name='github_client.repo_has_label' id='139706485842544'> = <MagicMock name='github_client' id='139706485845232'>.repo_has_label
tests/test_webhook_handler.py:101: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:34:25.124012Z[0m [[32m[1minfo [0m] [1mReceived pull_request event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'pull_request', 'action': 'opened'}[0m
[2m2026-05-16T19:34:25.124573Z[0m [[32m[1minfo [0m] [1mNo stampbot.toml found in octocat/hello-world, using defaults[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'org_repo': None}[0m
[2m2026-05-16T19:34:25.125083Z[0m [[32m[1minfo [0m] [1mPR #42 has approval label: autoapprove[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'level': 'info', 'timestamp': '2026-05-16T19:34:25.124012Z'}
INFO stampbot.webhook_handler:webhook_handler.py:593 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'level': 'info', 'timestamp': '2026-05-16T19:34:25.124573Z'}
INFO stampbot.webhook_handler:webhook_handler.py:278 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'level': 'info', 'timestamp': '2026-05-16T19:34:25.125083Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_opened_missing_label_logs_warning - AssertionError: assert 0 == 2
+ where 0 = <MagicMock name='github_client.repo_has_label' id='139706485842544'>.call_count
+ where <MagicMock name='github_client.repo_has_label' id='139706485842544'> = <MagicMock name='github_client' id='139706485845232'>.repo_has_label
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 165 passed, 3 deselected in 1.08s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceComparisonOperator_Eq_Gt, occurrence: 6
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -314,7 +314,7 @@
}
# Check if we should remove approval when label is removed
- if repo_config.auto_approve_on_label and action == "unlabeled":
+ if repo_config.auto_approve_on_label and action > "unlabeled":
removed_label = payload.get("label", {}).get("name")
if removed_label in repo_config.approval_labels:
logger.info(........................................................................ [ 34%]
........................................................................ [ 68%]
.........................F
=================================== FAILURES ===================================
________________________ test_pr_unlabeled_autoapprove _________________________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7f97781dd450>
mock_github_client = <MagicMock name='github_client' id='140288594077280'>
@pytest.mark.asyncio
async def test_pr_unlabeled_autoapprove(webhook_handler, mock_github_client):
"""Test removing autoapprove label dismisses approvals."""
payload = load_fixture("pr_unlabeled_autoapprove")
mock_github_client.find_bot_reviews.return_value = [111, 222] # Mock review IDs
result = await webhook_handler.handle_event("pull_request", payload)
> assert result["status"] == "success"
E AssertionError: assert 'ignored' == 'success'
E
E - success
E + ignored
tests/test_webhook_handler.py:152: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:35:54.593056Z[0m [[32m[1minfo [0m] [1mReceived pull_request event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'pull_request', 'action': 'unlabeled'}[0m
[2m2026-05-16T19:35:54.593613Z[0m [[32m[1minfo [0m] [1mNo stampbot.toml found in octocat/hello-world, using defaults[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'org_repo': None}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'pull_request', 'action': 'unlabeled'}, 'event': 'Received pull_request event', 'level': 'info', 'timestamp': '2026-05-16T19:35:54.593056Z'}
INFO stampbot.webhook_handler:webhook_handler.py:593 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'level': 'info', 'timestamp': '2026-05-16T19:35:54.593613Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_unlabeled_autoapprove - AssertionError: assert 'ignored' == 'success'
- success
+ ignored
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 169 passed, 3 deselected in 1.09s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceComparisonOperator_Eq_Gt, occurrence: 7
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -572,7 +572,7 @@
org_repo_full_name = None
if (
- owner_type == "Organization"
+ owner_type > "Organization"
and owner_login
and repo_full_name != f"{owner_login}/.github"
):........................................................................ [ 34%]
........................................................................ [ 68%]
............................................F
=================================== FAILURES ===================================
_____________________ test_repo_config_uses_default_branch _____________________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7f8a5d887530>
mock_github_client = <MagicMock name='github_client' id='140232250455920'>
@pytest.mark.asyncio
async def test_repo_config_uses_default_branch(webhook_handler, mock_github_client):
"""Test repo config is loaded from the default branch."""
payload = load_fixture("pr_opened_with_autoapprove_label")
payload["repository"]["default_branch"] = "develop"
payload["pull_request"]["base"]["ref"] = "release"
result = await webhook_handler.handle_event("pull_request", payload)
assert result["status"] == "success"
args = mock_github_client.get_repo_file.call_args[0]
> assert args[3] == "develop"
E AssertionError: assert None == 'develop'
tests/test_webhook_handler.py:434: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:57:36.875525Z[0m [[32m[1minfo [0m] [1mReceived pull_request event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'pull_request', 'action': 'opened'}[0m
[2m2026-05-16T19:57:36.876207Z[0m [[32m[1minfo [0m] [1mNo stampbot.toml found in octocat/hello-world or octocat/.github, using defaults[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'org_repo': 'octocat/.github'}[0m
[2m2026-05-16T19:57:36.876998Z[0m [[32m[1minfo [0m] [1mPR #42 has approval label: autoapprove[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'level': 'info', 'timestamp': '2026-05-16T19:57:36.875525Z'}
INFO stampbot.webhook_handler:webhook_handler.py:593 {'extra': {'repo': 'octocat/hello-world', 'org_repo': 'octocat/.github'}, 'event': 'No stampbot.toml found in octocat/hello-world or octocat/.github, using defaults', 'level': 'info', 'timestamp': '2026-05-16T19:57:36.876207Z'}
INFO stampbot.webhook_handler:webhook_handler.py:278 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'level': 'info', 'timestamp': '2026-05-16T19:57:36.876998Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_repo_config_uses_default_branch - AssertionError: assert None == 'develop'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 188 passed, 3 deselected in 1.55s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceComparisonOperator_Eq_GtE, occurrence: 0
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -109,7 +109,7 @@
)
# Route to appropriate handler
- if event_type == "pull_request":
+ if event_type >= "pull_request":
result = await self._handle_pull_request(payload)
elif event_type == "pull_request_review_comment":
result = await self._handle_pr_comment(payload)........................................................................ [ 34%]
........................................................................ [ 68%]
..................F
=================================== FAILURES ===================================
______________________________ test_unknown_event ______________________________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7f9cb1b07250>
@pytest.mark.asyncio
async def test_unknown_event(webhook_handler):
"""Test handling unknown event type."""
payload = {}
result = await webhook_handler.handle_event("unknown", payload)
> assert result["status"] == "ignored"
E AssertionError: assert 'error' == 'ignored'
E
E - ignored
E + error
tests/test_webhook_handler.py:55: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:39:06.521460Z[0m [[32m[1minfo [0m] [1mReceived unknown event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'unknown', 'action': ''}[0m
[2m2026-05-16T19:39:06.521646Z[0m [[33m[1mwarning [0m] [1mMissing required fields in PR event[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35mwarning[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'unknown', 'action': ''}, 'event': 'Received unknown event', 'level': 'info', 'timestamp': '2026-05-16T19:39:06.521460Z'}
WARNING stampbot.webhook_handler:webhook_handler.py:161 {'event': 'Missing required fields in PR event', 'level': 'warning', 'timestamp': '2026-05-16T19:39:06.521646Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_unknown_event - AssertionError: assert 'error' == 'ignored'
- ignored
+ error
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 162 passed, 3 deselected in 1.07s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceComparisonOperator_Eq_GtE, occurrence: 1
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -111,7 +111,7 @@
# Route to appropriate handler
if event_type == "pull_request":
result = await self._handle_pull_request(payload)
- elif event_type == "pull_request_review_comment":
+ elif event_type >= "pull_request_review_comment":
result = await self._handle_pr_comment(payload)
elif event_type == "issue_comment":
# Issue comments can be on PRs too........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.71s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceComparisonOperator_Eq_GtE, occurrence: 2
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -113,7 +113,7 @@
result = await self._handle_pull_request(payload)
elif event_type == "pull_request_review_comment":
result = await self._handle_pr_comment(payload)
- elif event_type == "issue_comment":
+ elif event_type >= "issue_comment":
# Issue comments can be on PRs too
if "pull_request" in payload.get("issue", {}):
result = await self._handle_pr_comment(payload)........................................................................ [ 34%]
.............................F
=================================== FAILURES ===================================
___________________________ test_webhook_valid_ping ____________________________
test_client = <starlette.testclient.TestClient object at 0x7f8205eda9c0>
def test_webhook_valid_ping(test_client: TestClient):
"""Test webhook accepts valid ping event."""
import hashlib
import hmac
from stampbot.webhook_handler import webhook_handler
body = json.dumps({"zen": "Design for failure."}).encode()
signature = (
"sha256="
+ hmac.new(
webhook_handler.webhook_secret,
body,
hashlib.sha256,
).hexdigest()
)
response = test_client.post(
"/webhook",
content=body,
headers={
"Content-Type": "application/json",
"X-GitHub-Event": "ping",
"X-Hub-Signature-256": signature,
},
)
assert response.status_code == 200
data = response.json()
> assert data["status"] == "ok"
E AssertionError: assert 'ignored' == 'ok'
E
E - ok
E + ignored
tests/test_main.py:177: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T20:02:01.476380Z[0m [[32m[1minfo [0m] [1mReceived ping event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mclient_ip[0m=[35mtestclient[0m [36mextra[0m=[35m{'event_type': 'ping', 'action': ''}[0m
[2m2026-05-16T20:02:01.477299Z[0m [[32m[1minfo [0m] [1mHTTP Request: POST http://testserver/webhook "HTTP/1.1 200 OK"[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'ping', 'action': ''}, 'event': 'Received ping event', 'client_ip': 'testclient', 'level': 'info', 'timestamp': '2026-05-16T20:02:01.476380Z'}
INFO httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 200 OK"
=========================== short test summary info ============================
FAILED tests/test_main.py::test_webhook_valid_ping - AssertionError: assert 'ignored' == 'ok'
- ok
+ ignored
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 101 passed, 3 deselected in 0.87s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceComparisonOperator_Eq_GtE, occurrence: 3
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -119,7 +119,7 @@
result = await self._handle_pr_comment(payload)
else:
result = {"status": "ignored", "message": "Not a PR comment"}
- elif event_type == "ping":
+ elif event_type >= "ping":
result = {"status": "ok", "message": "pong"}
else:
result = {"status": "ignored", "message": f"Event type {event_type} not handled"}........................................................................ [ 34%]
........................................................................ [ 68%]
..................F
=================================== FAILURES ===================================
______________________________ test_unknown_event ______________________________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7f31faa13250>
@pytest.mark.asyncio
async def test_unknown_event(webhook_handler):
"""Test handling unknown event type."""
payload = {}
result = await webhook_handler.handle_event("unknown", payload)
> assert result["status"] == "ignored"
E AssertionError: assert 'ok' == 'ignored'
E
E - ignored
E + ok
tests/test_webhook_handler.py:55: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T20:07:21.335661Z[0m [[32m[1minfo [0m] [1mReceived unknown event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'unknown', 'action': ''}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'unknown', 'action': ''}, 'event': 'Received unknown event', 'level': 'info', 'timestamp': '2026-05-16T20:07:21.335661Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_unknown_event - AssertionError: assert 'ok' == 'ignored'
- ignored
+ ok
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 162 passed, 3 deselected in 1.11s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceComparisonOperator_Eq_GtE, occurrence: 4
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -187,7 +187,7 @@
},
)
- if action == "opened":
+ if action >= "opened":
await run_in_threadpool(
github_client.create_pr_review_comment,
installation_id,........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.62s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceComparisonOperator_Eq_GtE, occurrence: 5
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -206,7 +206,7 @@
"message": "Invalid repository configuration",
}
- if action == "opened":
+ if action >= "opened":
for label in repo_config.approval_labels:
label_exists = await run_in_threadpool(
github_client.repo_has_label,........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.60s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceComparisonOperator_Eq_GtE, occurrence: 6
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -314,7 +314,7 @@
}
# Check if we should remove approval when label is removed
- if repo_config.auto_approve_on_label and action == "unlabeled":
+ if repo_config.auto_approve_on_label and action >= "unlabeled":
removed_label = payload.get("label", {}).get("name")
if removed_label in repo_config.approval_labels:
logger.info(........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceComparisonOperator_Eq_GtE, occurrence: 7
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -572,7 +572,7 @@
org_repo_full_name = None
if (
- owner_type == "Organization"
+ owner_type >= "Organization"
and owner_login
and repo_full_name != f"{owner_login}/.github"
):........................................................................ [ 34%]
........................................................................ [ 68%]
............................................F
=================================== FAILURES ===================================
_____________________ test_repo_config_uses_default_branch _____________________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7fbdbceabad0>
mock_github_client = <MagicMock name='github_client' id='140452893767536'>
@pytest.mark.asyncio
async def test_repo_config_uses_default_branch(webhook_handler, mock_github_client):
"""Test repo config is loaded from the default branch."""
payload = load_fixture("pr_opened_with_autoapprove_label")
payload["repository"]["default_branch"] = "develop"
payload["pull_request"]["base"]["ref"] = "release"
result = await webhook_handler.handle_event("pull_request", payload)
assert result["status"] == "success"
args = mock_github_client.get_repo_file.call_args[0]
> assert args[3] == "develop"
E AssertionError: assert None == 'develop'
tests/test_webhook_handler.py:434: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:59:10.694594Z[0m [[32m[1minfo [0m] [1mReceived pull_request event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'pull_request', 'action': 'opened'}[0m
[2m2026-05-16T19:59:10.695310Z[0m [[32m[1minfo [0m] [1mNo stampbot.toml found in octocat/hello-world or octocat/.github, using defaults[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'org_repo': 'octocat/.github'}[0m
[2m2026-05-16T19:59:10.696102Z[0m [[32m[1minfo [0m] [1mPR #42 has approval label: autoapprove[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'level': 'info', 'timestamp': '2026-05-16T19:59:10.694594Z'}
INFO stampbot.webhook_handler:webhook_handler.py:593 {'extra': {'repo': 'octocat/hello-world', 'org_repo': 'octocat/.github'}, 'event': 'No stampbot.toml found in octocat/hello-world or octocat/.github, using defaults', 'level': 'info', 'timestamp': '2026-05-16T19:59:10.695310Z'}
INFO stampbot.webhook_handler:webhook_handler.py:278 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'level': 'info', 'timestamp': '2026-05-16T19:59:10.696102Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_repo_config_uses_default_branch - AssertionError: assert None == 'develop'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 188 passed, 3 deselected in 1.56s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceComparisonOperator_Eq_Is, occurrence: 0
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -109,7 +109,7 @@
)
# Route to appropriate handler
- if event_type == "pull_request":
+ if event_type is "pull_request":
result = await self._handle_pull_request(payload)
elif event_type == "pull_request_review_comment":
result = await self._handle_pr_comment(payload)........................................................................ [ 34%]
........................................................................ [ 68%]
................................................................... [100%]
=============================== warnings summary ===============================
tests/test_main.py::test_lifespan_startup_shutdown_configured
/home/runner/work/stampbot/stampbot/stampbot/webhook_handler.py:112: SyntaxWarning: "is" with 'str' literal. Did you mean "=="?
if event_type is "pull_request":
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
211 passed, 3 deselected, 1 warning in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceComparisonOperator_Eq_Is, occurrence: 1
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -111,7 +111,7 @@
# Route to appropriate handler
if event_type == "pull_request":
result = await self._handle_pull_request(payload)
- elif event_type == "pull_request_review_comment":
+ elif event_type is "pull_request_review_comment":
result = await self._handle_pr_comment(payload)
elif event_type == "issue_comment":
# Issue comments can be on PRs too........................................................................ [ 34%]
........................................................................ [ 68%]
................................................................... [100%]
=============================== warnings summary ===============================
tests/test_main.py::test_lifespan_startup_shutdown_configured
/home/runner/work/stampbot/stampbot/stampbot/webhook_handler.py:114: SyntaxWarning: "is" with 'str' literal. Did you mean "=="?
elif event_type is "pull_request_review_comment":
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
211 passed, 3 deselected, 1 warning in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceComparisonOperator_Eq_Is, occurrence: 2
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -113,7 +113,7 @@
result = await self._handle_pull_request(payload)
elif event_type == "pull_request_review_comment":
result = await self._handle_pr_comment(payload)
- elif event_type == "issue_comment":
+ elif event_type is "issue_comment":
# Issue comments can be on PRs too
if "pull_request" in payload.get("issue", {}):
result = await self._handle_pr_comment(payload)........................................................................ [ 34%]
........................................................................ [ 68%]
................................................................... [100%]
=============================== warnings summary ===============================
tests/test_main.py::test_lifespan_startup_shutdown_configured
/home/runner/work/stampbot/stampbot/stampbot/webhook_handler.py:116: SyntaxWarning: "is" with 'str' literal. Did you mean "=="?
elif event_type is "issue_comment":
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
211 passed, 3 deselected, 1 warning in 1.70s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceComparisonOperator_Eq_Is, occurrence: 3
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -119,7 +119,7 @@
result = await self._handle_pr_comment(payload)
else:
result = {"status": "ignored", "message": "Not a PR comment"}
- elif event_type == "ping":
+ elif event_type is "ping":
result = {"status": "ok", "message": "pong"}
else:
result = {"status": "ignored", "message": f"Event type {event_type} not handled"}........................................................................ [ 34%]
.............................F
=================================== FAILURES ===================================
___________________________ test_webhook_valid_ping ____________________________
test_client = <starlette.testclient.TestClient object at 0x7fb8765ca9c0>
def test_webhook_valid_ping(test_client: TestClient):
"""Test webhook accepts valid ping event."""
import hashlib
import hmac
from stampbot.webhook_handler import webhook_handler
body = json.dumps({"zen": "Design for failure."}).encode()
signature = (
"sha256="
+ hmac.new(
webhook_handler.webhook_secret,
body,
hashlib.sha256,
).hexdigest()
)
response = test_client.post(
"/webhook",
content=body,
headers={
"Content-Type": "application/json",
"X-GitHub-Event": "ping",
"X-Hub-Signature-256": signature,
},
)
assert response.status_code == 200
data = response.json()
> assert data["status"] == "ok"
E AssertionError: assert 'ignored' == 'ok'
E
E - ok
E + ignored
tests/test_main.py:177: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:48:00.870329Z[0m [[32m[1minfo [0m] [1mReceived ping event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mclient_ip[0m=[35mtestclient[0m [36mextra[0m=[35m{'event_type': 'ping', 'action': ''}[0m
[2m2026-05-16T19:48:00.871399Z[0m [[32m[1minfo [0m] [1mHTTP Request: POST http://testserver/webhook "HTTP/1.1 200 OK"[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'ping', 'action': ''}, 'event': 'Received ping event', 'client_ip': 'testclient', 'level': 'info', 'timestamp': '2026-05-16T19:48:00.870329Z'}
INFO httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 200 OK"
=============================== warnings summary ===============================
tests/test_main.py::test_lifespan_startup_shutdown_configured
/home/runner/work/stampbot/stampbot/stampbot/webhook_handler.py:122: SyntaxWarning: "is" with 'str' literal. Did you mean "=="?
elif event_type is "ping":
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_webhook_valid_ping - AssertionError: assert 'ignored' == 'ok'
- ok
+ ignored
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 101 passed, 3 deselected, 1 warning in 0.85s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceComparisonOperator_Eq_Is, occurrence: 4
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -187,7 +187,7 @@
},
)
- if action == "opened":
+ if action is "opened":
await run_in_threadpool(
github_client.create_pr_review_comment,
installation_id,........................................................................ [ 34%]
........................................................................ [ 68%]
................................................F
=================================== FAILURES ===================================
____________________ test_invalid_repo_config_posts_review _____________________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7f44365a0d10>
mock_github_client = <MagicMock name='github_client' id='139930947251776'>
@pytest.mark.asyncio
async def test_invalid_repo_config_posts_review(webhook_handler, mock_github_client):
"""Test invalid repo config logs and posts a review comment."""
payload = load_fixture("pr_opened_with_autoapprove_label")
mock_github_client.get_repo_file.return_value = 'chatops_required_permission = "invalid"'
result = await webhook_handler.handle_event("pull_request", payload)
assert result["status"] == "error"
assert "invalid" in result["message"].lower()
> mock_github_client.create_pr_review_comment.assert_called_once()
tests/test_webhook_handler.py:518:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <MagicMock name='github_client.create_pr_review_comment' id='139930947252784'>
def assert_called_once(self):
"""assert that the mock was called only once.
"""
if not self.call_count == 1:
msg = ("Expected '%s' to have been called once. Called %s times.%s"
% (self._mock_name or 'mock',
self.call_count,
self._calls_repr()))
> raise AssertionError(msg)
E AssertionError: Expected 'create_pr_review_comment' to have been called once. Called 0 times.
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:964: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:46:42.731186Z[0m [[32m[1minfo [0m] [1mReceived pull_request event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'pull_request', 'action': 'opened'}[0m
[2m2026-05-16T19:46:42.732593Z[0m [[33m[1mwarning [0m] [1mInvalid stampbot.toml in octocat/hello-world: Invalid chatops_required_permission: invalid. Valid values: none, read, triage, write, maintain, admin[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35mwarning[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'error': 'Invalid chatops_required_permission: invalid. Valid values: none, read, triage, write, maintain, admin'}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'level': 'info', 'timestamp': '2026-05-16T19:46:42.731186Z'}
WARNING stampbot.webhook_handler:webhook_handler.py:176 {'extra': {'repo': 'octocat/hello-world', 'error': 'Invalid chatops_required_permission: invalid. Valid values: none, read, triage, write, maintain, admin'}, 'event': 'Invalid stampbot.toml in octocat/hello-world: Invalid chatops_required_permission: invalid. Valid values: none, read, triage, write, maintain, admin', 'level': 'warning', 'timestamp': '2026-05-16T19:46:42.732593Z'}
=============================== warnings summary ===============================
tests/test_main.py::test_lifespan_startup_shutdown_configured
/home/runner/work/stampbot/stampbot/stampbot/webhook_handler.py:190: SyntaxWarning: "is" with 'str' literal. Did you mean "=="?
if action is "opened":
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_invalid_repo_config_posts_review - AssertionError: Expected 'create_pr_review_comment' to have been called once. Called 0 times.
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 192 passed, 3 deselected, 1 warning in 1.66s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceComparisonOperator_Eq_Is, occurrence: 5
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -206,7 +206,7 @@
"message": "Invalid repository configuration",
}
- if action == "opened":
+ if action is "opened":
for label in repo_config.approval_labels:
label_exists = await run_in_threadpool(
github_client.repo_has_label,........................................................................ [ 34%]
........................................................................ [ 68%]
.....................F
=================================== FAILURES ===================================
__________________ test_pr_opened_missing_label_logs_warning ___________________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7f27c8120830>
mock_github_client = <MagicMock name='github_client' id='139808777514224'>
@pytest.mark.asyncio
async def test_pr_opened_missing_label_logs_warning(webhook_handler, mock_github_client):
"""Test missing approval label triggers label check."""
payload = load_fixture("pr_opened_with_autoapprove_label")
mock_github_client.repo_has_label.return_value = False
result = await webhook_handler.handle_event("pull_request", payload)
assert result["status"] == "success"
> assert mock_github_client.repo_has_label.call_count == 2
E AssertionError: assert 0 == 2
E + where 0 = <MagicMock name='github_client.repo_has_label' id='139808777511536'>.call_count
E + where <MagicMock name='github_client.repo_has_label' id='139808777511536'> = <MagicMock name='github_client' id='139808777514224'>.repo_has_label
tests/test_webhook_handler.py:101: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:52:47.482249Z[0m [[32m[1minfo [0m] [1mReceived pull_request event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'pull_request', 'action': 'opened'}[0m
[2m2026-05-16T19:52:47.482792Z[0m [[32m[1minfo [0m] [1mNo stampbot.toml found in octocat/hello-world, using defaults[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'org_repo': None}[0m
[2m2026-05-16T19:52:47.483334Z[0m [[32m[1minfo [0m] [1mPR #42 has approval label: autoapprove[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'level': 'info', 'timestamp': '2026-05-16T19:52:47.482249Z'}
INFO stampbot.webhook_handler:webhook_handler.py:593 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'level': 'info', 'timestamp': '2026-05-16T19:52:47.482792Z'}
INFO stampbot.webhook_handler:webhook_handler.py:278 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'level': 'info', 'timestamp': '2026-05-16T19:52:47.483334Z'}
=============================== warnings summary ===============================
tests/test_main.py::test_lifespan_startup_shutdown_configured
/home/runner/work/stampbot/stampbot/stampbot/webhook_handler.py:209: SyntaxWarning: "is" with 'str' literal. Did you mean "=="?
if action is "opened":
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_opened_missing_label_logs_warning - AssertionError: assert 0 == 2
+ where 0 = <MagicMock name='github_client.repo_has_label' id='139808777511536'>.call_count
+ where <MagicMock name='github_client.repo_has_label' id='139808777511536'> = <MagicMock name='github_client' id='139808777514224'>.repo_has_label
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 165 passed, 3 deselected, 1 warning in 1.08s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceComparisonOperator_Eq_Is, occurrence: 6
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -314,7 +314,7 @@
}
# Check if we should remove approval when label is removed
- if repo_config.auto_approve_on_label and action == "unlabeled":
+ if repo_config.auto_approve_on_label and action is "unlabeled":
removed_label = payload.get("label", {}).get("name")
if removed_label in repo_config.approval_labels:
logger.info(........................................................................ [ 34%]
........................................................................ [ 68%]
.........................F
=================================== FAILURES ===================================
________________________ test_pr_unlabeled_autoapprove _________________________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7f9b5e9d1250>
mock_github_client = <MagicMock name='github_client' id='140305346127456'>
@pytest.mark.asyncio
async def test_pr_unlabeled_autoapprove(webhook_handler, mock_github_client):
"""Test removing autoapprove label dismisses approvals."""
payload = load_fixture("pr_unlabeled_autoapprove")
mock_github_client.find_bot_reviews.return_value = [111, 222] # Mock review IDs
result = await webhook_handler.handle_event("pull_request", payload)
> assert result["status"] == "success"
E AssertionError: assert 'ignored' == 'success'
E
E - success
E + ignored
tests/test_webhook_handler.py:152: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:23:14.162031Z[0m [[32m[1minfo [0m] [1mReceived pull_request event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'pull_request', 'action': 'unlabeled'}[0m
[2m2026-05-16T19:23:14.162600Z[0m [[32m[1minfo [0m] [1mNo stampbot.toml found in octocat/hello-world, using defaults[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'org_repo': None}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'pull_request', 'action': 'unlabeled'}, 'event': 'Received pull_request event', 'level': 'info', 'timestamp': '2026-05-16T19:23:14.162031Z'}
INFO stampbot.webhook_handler:webhook_handler.py:593 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'level': 'info', 'timestamp': '2026-05-16T19:23:14.162600Z'}
=============================== warnings summary ===============================
tests/test_main.py::test_lifespan_startup_shutdown_configured
/home/runner/work/stampbot/stampbot/stampbot/webhook_handler.py:317: SyntaxWarning: "is" with 'str' literal. Did you mean "=="?
if repo_config.auto_approve_on_label and action is "unlabeled":
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_unlabeled_autoapprove - AssertionError: assert 'ignored' == 'success'
- success
+ ignored
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 169 passed, 3 deselected, 1 warning in 1.07s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceComparisonOperator_Eq_Is, occurrence: 7
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -572,7 +572,7 @@
org_repo_full_name = None
if (
- owner_type == "Organization"
+ owner_type is "Organization"
and owner_login
and repo_full_name != f"{owner_login}/.github"
):........................................................................ [ 34%]
........................................................................ [ 68%]
................................................................... [100%]
=============================== warnings summary ===============================
tests/test_main.py::test_lifespan_startup_shutdown_configured
/home/runner/work/stampbot/stampbot/stampbot/webhook_handler.py:575: SyntaxWarning: "is" with 'str' literal. Did you mean "=="?
owner_type is "Organization"
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
211 passed, 3 deselected, 1 warning in 1.60s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceComparisonOperator_Eq_IsNot, occurrence: 0
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -109,7 +109,7 @@
)
# Route to appropriate handler
- if event_type == "pull_request":
+ if event_type is not "pull_request":
result = await self._handle_pull_request(payload)
elif event_type == "pull_request_review_comment":
result = await self._handle_pr_comment(payload)........................................................................ [ 34%]
.............................F
=================================== FAILURES ===================================
___________________________ test_webhook_valid_ping ____________________________
test_client = <starlette.testclient.TestClient object at 0x7f69f46ba9c0>
def test_webhook_valid_ping(test_client: TestClient):
"""Test webhook accepts valid ping event."""
import hashlib
import hmac
from stampbot.webhook_handler import webhook_handler
body = json.dumps({"zen": "Design for failure."}).encode()
signature = (
"sha256="
+ hmac.new(
webhook_handler.webhook_secret,
body,
hashlib.sha256,
).hexdigest()
)
response = test_client.post(
"/webhook",
content=body,
headers={
"Content-Type": "application/json",
"X-GitHub-Event": "ping",
"X-Hub-Signature-256": signature,
},
)
assert response.status_code == 200
data = response.json()
> assert data["status"] == "ok"
E AssertionError: assert 'error' == 'ok'
E
E - ok
E + error
tests/test_main.py:177: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:48:32.343857Z[0m [[32m[1minfo [0m] [1mReceived ping event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mclient_ip[0m=[35mtestclient[0m [36mextra[0m=[35m{'event_type': 'ping', 'action': ''}[0m
[2m2026-05-16T19:48:32.344096Z[0m [[33m[1mwarning [0m] [1mMissing required fields in PR event[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35mwarning[0m [36mclient_ip[0m=[35mtestclient[0m
[2m2026-05-16T19:48:32.344910Z[0m [[32m[1minfo [0m] [1mHTTP Request: POST http://testserver/webhook "HTTP/1.1 200 OK"[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'ping', 'action': ''}, 'event': 'Received ping event', 'client_ip': 'testclient', 'level': 'info', 'timestamp': '2026-05-16T19:48:32.343857Z'}
WARNING stampbot.webhook_handler:webhook_handler.py:161 {'event': 'Missing required fields in PR event', 'client_ip': 'testclient', 'level': 'warning', 'timestamp': '2026-05-16T19:48:32.344096Z'}
INFO httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 200 OK"
=============================== warnings summary ===============================
tests/test_main.py::test_lifespan_startup_shutdown_configured
/home/runner/work/stampbot/stampbot/stampbot/webhook_handler.py:112: SyntaxWarning: "is not" with 'str' literal. Did you mean "!="?
if event_type is not "pull_request":
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_webhook_valid_ping - AssertionError: assert 'error' == 'ok'
- ok
+ error
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 101 passed, 3 deselected, 1 warning in 0.85s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceComparisonOperator_Eq_IsNot, occurrence: 1
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -111,7 +111,7 @@
# Route to appropriate handler
if event_type == "pull_request":
result = await self._handle_pull_request(payload)
- elif event_type == "pull_request_review_comment":
+ elif event_type is not "pull_request_review_comment":
result = await self._handle_pr_comment(payload)
elif event_type == "issue_comment":
# Issue comments can be on PRs too........................................................................ [ 34%]
.............................F
=================================== FAILURES ===================================
___________________________ test_webhook_valid_ping ____________________________
test_client = <starlette.testclient.TestClient object at 0x7fd0703ca9c0>
def test_webhook_valid_ping(test_client: TestClient):
"""Test webhook accepts valid ping event."""
import hashlib
import hmac
from stampbot.webhook_handler import webhook_handler
body = json.dumps({"zen": "Design for failure."}).encode()
signature = (
"sha256="
+ hmac.new(
webhook_handler.webhook_secret,
body,
hashlib.sha256,
).hexdigest()
)
response = test_client.post(
"/webhook",
content=body,
headers={
"Content-Type": "application/json",
"X-GitHub-Event": "ping",
"X-Hub-Signature-256": signature,
},
)
assert response.status_code == 200
data = response.json()
> assert data["status"] == "ok"
E AssertionError: assert 'ignored' == 'ok'
E
E - ok
E + ignored
tests/test_main.py:177: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:24:30.655990Z[0m [[32m[1minfo [0m] [1mReceived ping event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mclient_ip[0m=[35mtestclient[0m [36mextra[0m=[35m{'event_type': 'ping', 'action': ''}[0m
[2m2026-05-16T19:24:30.656893Z[0m [[32m[1minfo [0m] [1mHTTP Request: POST http://testserver/webhook "HTTP/1.1 200 OK"[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'ping', 'action': ''}, 'event': 'Received ping event', 'client_ip': 'testclient', 'level': 'info', 'timestamp': '2026-05-16T19:24:30.655990Z'}
INFO httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 200 OK"
=============================== warnings summary ===============================
tests/test_main.py::test_lifespan_startup_shutdown_configured
/home/runner/work/stampbot/stampbot/stampbot/webhook_handler.py:114: SyntaxWarning: "is not" with 'str' literal. Did you mean "!="?
elif event_type is not "pull_request_review_comment":
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_webhook_valid_ping - AssertionError: assert 'ignored' == 'ok'
- ok
+ ignored
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 101 passed, 3 deselected, 1 warning in 0.85s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceComparisonOperator_Eq_IsNot, occurrence: 2
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -113,7 +113,7 @@
result = await self._handle_pull_request(payload)
elif event_type == "pull_request_review_comment":
result = await self._handle_pr_comment(payload)
- elif event_type == "issue_comment":
+ elif event_type is not "issue_comment":
# Issue comments can be on PRs too
if "pull_request" in payload.get("issue", {}):
result = await self._handle_pr_comment(payload)........................................................................ [ 34%]
.............................F
=================================== FAILURES ===================================
___________________________ test_webhook_valid_ping ____________________________
test_client = <starlette.testclient.TestClient object at 0x7f1a21abe9c0>
def test_webhook_valid_ping(test_client: TestClient):
"""Test webhook accepts valid ping event."""
import hashlib
import hmac
from stampbot.webhook_handler import webhook_handler
body = json.dumps({"zen": "Design for failure."}).encode()
signature = (
"sha256="
+ hmac.new(
webhook_handler.webhook_secret,
body,
hashlib.sha256,
).hexdigest()
)
response = test_client.post(
"/webhook",
content=body,
headers={
"Content-Type": "application/json",
"X-GitHub-Event": "ping",
"X-Hub-Signature-256": signature,
},
)
assert response.status_code == 200
data = response.json()
> assert data["status"] == "ok"
E AssertionError: assert 'ignored' == 'ok'
E
E - ok
E + ignored
tests/test_main.py:177: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:36:46.187854Z[0m [[32m[1minfo [0m] [1mReceived ping event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mclient_ip[0m=[35mtestclient[0m [36mextra[0m=[35m{'event_type': 'ping', 'action': ''}[0m
[2m2026-05-16T19:36:46.188755Z[0m [[32m[1minfo [0m] [1mHTTP Request: POST http://testserver/webhook "HTTP/1.1 200 OK"[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'ping', 'action': ''}, 'event': 'Received ping event', 'client_ip': 'testclient', 'level': 'info', 'timestamp': '2026-05-16T19:36:46.187854Z'}
INFO httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 200 OK"
=============================== warnings summary ===============================
tests/test_main.py::test_lifespan_startup_shutdown_configured
/home/runner/work/stampbot/stampbot/stampbot/webhook_handler.py:116: SyntaxWarning: "is not" with 'str' literal. Did you mean "!="?
elif event_type is not "issue_comment":
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_webhook_valid_ping - AssertionError: assert 'ignored' == 'ok'
- ok
+ ignored
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 101 passed, 3 deselected, 1 warning in 0.85s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceComparisonOperator_Eq_IsNot, occurrence: 3
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -119,7 +119,7 @@
result = await self._handle_pr_comment(payload)
else:
result = {"status": "ignored", "message": "Not a PR comment"}
- elif event_type == "ping":
+ elif event_type is not "ping":
result = {"status": "ok", "message": "pong"}
else:
result = {"status": "ignored", "message": f"Event type {event_type} not handled"}........................................................................ [ 34%]
........................................................................ [ 68%]
.................F
=================================== FAILURES ===================================
_______________________________ test_ping_event ________________________________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7f3b931fb250>
@pytest.mark.asyncio
async def test_ping_event(webhook_handler):
"""Test handling ping event."""
payload = {"zen": "Design for failure."}
result = await webhook_handler.handle_event("ping", payload)
> assert result["status"] == "ok"
E AssertionError: assert 'ignored' == 'ok'
E
E - ok
E + ignored
tests/test_webhook_handler.py:46: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:54:47.969168Z[0m [[32m[1minfo [0m] [1mReceived ping event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'ping', 'action': ''}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'ping', 'action': ''}, 'event': 'Received ping event', 'level': 'info', 'timestamp': '2026-05-16T19:54:47.969168Z'}
=============================== warnings summary ===============================
tests/test_main.py::test_lifespan_startup_shutdown_configured
/home/runner/work/stampbot/stampbot/stampbot/webhook_handler.py:122: SyntaxWarning: "is not" with 'str' literal. Did you mean "!="?
elif event_type is not "ping":
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_ping_event - AssertionError: assert 'ignored' == 'ok'
- ok
+ ignored
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 161 passed, 3 deselected, 1 warning in 1.07s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceComparisonOperator_Eq_IsNot, occurrence: 4
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -187,7 +187,7 @@
},
)
- if action == "opened":
+ if action is not "opened":
await run_in_threadpool(
github_client.create_pr_review_comment,
installation_id,........................................................................ [ 34%]
........................................................................ [ 68%]
.................................................F
=================================== FAILURES ===================================
_______________ test_invalid_repo_config_no_review_on_non_opened _______________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7f7df87c5310>
mock_github_client = <MagicMock name='github_client' id='140179017382416'>
@pytest.mark.asyncio
async def test_invalid_repo_config_no_review_on_non_opened(webhook_handler, mock_github_client):
"""Test invalid config skips review comment for non-opened events."""
payload = load_fixture("pr_opened_with_autoapprove_label")
payload["action"] = "labeled"
mock_github_client.get_repo_file.return_value = 'chatops_required_permission = "invalid"'
result = await webhook_handler.handle_event("pull_request", payload)
assert result["status"] == "error"
> mock_github_client.create_pr_review_comment.assert_not_called()
tests/test_webhook_handler.py:532:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <MagicMock name='github_client.create_pr_review_comment' id='140179017383424'>
def assert_not_called(self):
"""assert that the mock was never called.
"""
if self.call_count != 0:
msg = ("Expected '%s' to not have been called. Called %s times.%s"
% (self._mock_name or 'mock',
self.call_count,
self._calls_repr()))
> raise AssertionError(msg)
E AssertionError: Expected 'create_pr_review_comment' to not have been called. Called 1 times.
E Calls: [call(12345, 'octocat/hello-world', 42, 'Stampbot configuration error in `stampbot.toml`:\n\nInvalid chatops_required_permission: invalid. Valid values: none, read, triage, write, maintain, admin\n\nPlease fix the file to re-enable automation.')].
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:946: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:32:19.704045Z[0m [[32m[1minfo [0m] [1mReceived pull_request event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'pull_request', 'action': 'labeled'}[0m
[2m2026-05-16T19:32:19.705419Z[0m [[33m[1mwarning [0m] [1mInvalid stampbot.toml in octocat/hello-world: Invalid chatops_required_permission: invalid. Valid values: none, read, triage, write, maintain, admin[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35mwarning[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'error': 'Invalid chatops_required_permission: invalid. Valid values: none, read, triage, write, maintain, admin'}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'pull_request', 'action': 'labeled'}, 'event': 'Received pull_request event', 'level': 'info', 'timestamp': '2026-05-16T19:32:19.704045Z'}
WARNING stampbot.webhook_handler:webhook_handler.py:176 {'extra': {'repo': 'octocat/hello-world', 'error': 'Invalid chatops_required_permission: invalid. Valid values: none, read, triage, write, maintain, admin'}, 'event': 'Invalid stampbot.toml in octocat/hello-world: Invalid chatops_required_permission: invalid. Valid values: none, read, triage, write, maintain, admin', 'level': 'warning', 'timestamp': '2026-05-16T19:32:19.705419Z'}
=============================== warnings summary ===============================
tests/test_main.py::test_lifespan_startup_shutdown_configured
/home/runner/work/stampbot/stampbot/stampbot/webhook_handler.py:190: SyntaxWarning: "is not" with 'str' literal. Did you mean "!="?
if action is not "opened":
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_invalid_repo_config_no_review_on_non_opened - AssertionError: Expected 'create_pr_review_comment' to not have been called. Called 1 times.
Calls: [call(12345, 'octocat/hello-world', 42, 'Stampbot configuration error in `stampbot.toml`:\n\nInvalid chatops_required_permission: invalid. Valid values: none, read, triage, write, maintain, admin\n\nPlease fix the file to re-enable automation.')].
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 193 passed, 3 deselected, 1 warning in 1.63s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceComparisonOperator_Eq_IsNot, occurrence: 5
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -206,7 +206,7 @@
"message": "Invalid repository configuration",
}
- if action == "opened":
+ if action is not "opened":
for label in repo_config.approval_labels:
label_exists = await run_in_threadpool(
github_client.repo_has_label,........................................................................ [ 34%]
........................................................................ [ 68%]
................................................................... [100%]
=============================== warnings summary ===============================
tests/test_main.py::test_lifespan_startup_shutdown_configured
/home/runner/work/stampbot/stampbot/stampbot/webhook_handler.py:209: SyntaxWarning: "is not" with 'str' literal. Did you mean "!="?
if action is not "opened":
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
211 passed, 3 deselected, 1 warning in 1.62s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceComparisonOperator_Eq_IsNot, occurrence: 6
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -314,7 +314,7 @@
}
# Check if we should remove approval when label is removed
- if repo_config.auto_approve_on_label and action == "unlabeled":
+ if repo_config.auto_approve_on_label and action is not "unlabeled":
removed_label = payload.get("label", {}).get("name")
if removed_label in repo_config.approval_labels:
logger.info(........................................................................ [ 34%]
........................................................................ [ 68%]
................................................................... [100%]
=============================== warnings summary ===============================
tests/test_main.py::test_lifespan_startup_shutdown_configured
/home/runner/work/stampbot/stampbot/stampbot/webhook_handler.py:317: SyntaxWarning: "is not" with 'str' literal. Did you mean "!="?
if repo_config.auto_approve_on_label and action is not "unlabeled":
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
211 passed, 3 deselected, 1 warning in 1.61s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceComparisonOperator_Eq_IsNot, occurrence: 7
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -572,7 +572,7 @@
org_repo_full_name = None
if (
- owner_type == "Organization"
+ owner_type is not "Organization"
and owner_login
and repo_full_name != f"{owner_login}/.github"
):........................................................................ [ 34%]
........................................................................ [ 68%]
............................................F
=================================== FAILURES ===================================
_____________________ test_repo_config_uses_default_branch _____________________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7fc801de9010>
mock_github_client = <MagicMock name='github_client' id='140497000184336'>
@pytest.mark.asyncio
async def test_repo_config_uses_default_branch(webhook_handler, mock_github_client):
"""Test repo config is loaded from the default branch."""
payload = load_fixture("pr_opened_with_autoapprove_label")
payload["repository"]["default_branch"] = "develop"
payload["pull_request"]["base"]["ref"] = "release"
result = await webhook_handler.handle_event("pull_request", payload)
assert result["status"] == "success"
args = mock_github_client.get_repo_file.call_args[0]
> assert args[3] == "develop"
E AssertionError: assert None == 'develop'
tests/test_webhook_handler.py:434: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:25:58.063663Z[0m [[32m[1minfo [0m] [1mReceived pull_request event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'pull_request', 'action': 'opened'}[0m
[2m2026-05-16T19:25:58.064356Z[0m [[32m[1minfo [0m] [1mNo stampbot.toml found in octocat/hello-world or octocat/.github, using defaults[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'org_repo': 'octocat/.github'}[0m
[2m2026-05-16T19:25:58.065141Z[0m [[32m[1minfo [0m] [1mPR #42 has approval label: autoapprove[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'level': 'info', 'timestamp': '2026-05-16T19:25:58.063663Z'}
INFO stampbot.webhook_handler:webhook_handler.py:593 {'extra': {'repo': 'octocat/hello-world', 'org_repo': 'octocat/.github'}, 'event': 'No stampbot.toml found in octocat/hello-world or octocat/.github, using defaults', 'level': 'info', 'timestamp': '2026-05-16T19:25:58.064356Z'}
INFO stampbot.webhook_handler:webhook_handler.py:278 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'level': 'info', 'timestamp': '2026-05-16T19:25:58.065141Z'}
=============================== warnings summary ===============================
tests/test_main.py::test_lifespan_startup_shutdown_configured
/home/runner/work/stampbot/stampbot/stampbot/webhook_handler.py:575: SyntaxWarning: "is not" with 'str' literal. Did you mean "!="?
owner_type is not "Organization"
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_repo_config_uses_default_branch - AssertionError: assert None == 'develop'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 188 passed, 3 deselected, 1 warning in 1.55s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceComparisonOperator_NotEq_Eq, occurrence: 0
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -574,7 +574,7 @@
if (
owner_type == "Organization"
and owner_login
- and repo_full_name != f"{owner_login}/.github"
+ and repo_full_name == f"{owner_login}/.github"
):
org_repo_full_name = f"{owner_login}/.github"
org_content = await run_in_threadpool(........................................................................ [ 34%]
........................................................................ [ 68%]
.............................................F
=================================== FAILURES ===================================
_____________________ test_org_github_repo_config_fallback _____________________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7fce1bcaf770>
mock_github_client = <MagicMock name='github_client' id='140523207181376'>
@pytest.mark.asyncio
async def test_org_github_repo_config_fallback(webhook_handler, mock_github_client):
"""Test org .github stampbot.toml is used when repo config is missing."""
payload = load_fixture("pr_opened_with_autoapprove_label")
payload["repository"]["full_name"] = "acme/widgets"
payload["repository"]["default_branch"] = "main"
payload["repository"]["owner"] = {"login": "acme", "type": "Organization"}
payload["pull_request"]["labels"] = [{"name": "org-approve"}]
def get_repo_file_side_effect(_installation_id, repo_full_name, _file_path, _ref):
if repo_full_name == "acme/widgets":
return None
if repo_full_name == "acme/.github":
return 'approval_labels = ["org-approve"]'
return None
mock_github_client.get_repo_file.side_effect = get_repo_file_side_effect
result = await webhook_handler.handle_event("pull_request", payload)
> assert result["status"] == "success"
E AssertionError: assert 'ignored' == 'success'
E
E - success
E + ignored
tests/test_webhook_handler.py:457: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:58:57.164929Z[0m [[32m[1minfo [0m] [1mReceived pull_request event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'pull_request', 'action': 'opened'}[0m
[2m2026-05-16T19:58:57.165493Z[0m [[32m[1minfo [0m] [1mNo stampbot.toml found in acme/widgets, using defaults[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'acme/widgets', 'org_repo': None}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'level': 'info', 'timestamp': '2026-05-16T19:58:57.164929Z'}
INFO stampbot.webhook_handler:webhook_handler.py:593 {'extra': {'repo': 'acme/widgets', 'org_repo': None}, 'event': 'No stampbot.toml found in acme/widgets, using defaults', 'level': 'info', 'timestamp': '2026-05-16T19:58:57.165493Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_org_github_repo_config_fallback - AssertionError: assert 'ignored' == 'success'
- success
+ ignored
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 189 passed, 3 deselected in 1.59s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceComparisonOperator_NotEq_Lt, occurrence: 0
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -574,7 +574,7 @@
if (
owner_type == "Organization"
and owner_login
- and repo_full_name != f"{owner_login}/.github"
+ and repo_full_name < f"{owner_login}/.github"
):
org_repo_full_name = f"{owner_login}/.github"
org_content = await run_in_threadpool(........................................................................ [ 34%]
........................................................................ [ 68%]
.............................................F
=================================== FAILURES ===================================
_____________________ test_org_github_repo_config_fallback _____________________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7f5bf769b7d0>
mock_github_client = <MagicMock name='github_client' id='140032970310720'>
@pytest.mark.asyncio
async def test_org_github_repo_config_fallback(webhook_handler, mock_github_client):
"""Test org .github stampbot.toml is used when repo config is missing."""
payload = load_fixture("pr_opened_with_autoapprove_label")
payload["repository"]["full_name"] = "acme/widgets"
payload["repository"]["default_branch"] = "main"
payload["repository"]["owner"] = {"login": "acme", "type": "Organization"}
payload["pull_request"]["labels"] = [{"name": "org-approve"}]
def get_repo_file_side_effect(_installation_id, repo_full_name, _file_path, _ref):
if repo_full_name == "acme/widgets":
return None
if repo_full_name == "acme/.github":
return 'approval_labels = ["org-approve"]'
return None
mock_github_client.get_repo_file.side_effect = get_repo_file_side_effect
result = await webhook_handler.handle_event("pull_request", payload)
> assert result["status"] == "success"
E AssertionError: assert 'ignored' == 'success'
E
E - success
E + ignored
tests/test_webhook_handler.py:457: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:28:19.829163Z[0m [[32m[1minfo [0m] [1mReceived pull_request event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'pull_request', 'action': 'opened'}[0m
[2m2026-05-16T19:28:19.829723Z[0m [[32m[1minfo [0m] [1mNo stampbot.toml found in acme/widgets, using defaults[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'acme/widgets', 'org_repo': None}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'level': 'info', 'timestamp': '2026-05-16T19:28:19.829163Z'}
INFO stampbot.webhook_handler:webhook_handler.py:593 {'extra': {'repo': 'acme/widgets', 'org_repo': None}, 'event': 'No stampbot.toml found in acme/widgets, using defaults', 'level': 'info', 'timestamp': '2026-05-16T19:28:19.829723Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_org_github_repo_config_fallback - AssertionError: assert 'ignored' == 'success'
- success
+ ignored
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 189 passed, 3 deselected in 1.56s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceComparisonOperator_NotEq_LtE, occurrence: 0
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -574,7 +574,7 @@
if (
owner_type == "Organization"
and owner_login
- and repo_full_name != f"{owner_login}/.github"
+ and repo_full_name <= f"{owner_login}/.github"
):
org_repo_full_name = f"{owner_login}/.github"
org_content = await run_in_threadpool(........................................................................ [ 34%]
........................................................................ [ 68%]
.............................................F
=================================== FAILURES ===================================
_____________________ test_org_github_repo_config_fallback _____________________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7f0eb25bb7d0>
mock_github_client = <MagicMock name='github_client' id='139701099463744'>
@pytest.mark.asyncio
async def test_org_github_repo_config_fallback(webhook_handler, mock_github_client):
"""Test org .github stampbot.toml is used when repo config is missing."""
payload = load_fixture("pr_opened_with_autoapprove_label")
payload["repository"]["full_name"] = "acme/widgets"
payload["repository"]["default_branch"] = "main"
payload["repository"]["owner"] = {"login": "acme", "type": "Organization"}
payload["pull_request"]["labels"] = [{"name": "org-approve"}]
def get_repo_file_side_effect(_installation_id, repo_full_name, _file_path, _ref):
if repo_full_name == "acme/widgets":
return None
if repo_full_name == "acme/.github":
return 'approval_labels = ["org-approve"]'
return None
mock_github_client.get_repo_file.side_effect = get_repo_file_side_effect
result = await webhook_handler.handle_event("pull_request", payload)
> assert result["status"] == "success"
E AssertionError: assert 'ignored' == 'success'
E
E - success
E + ignored
tests/test_webhook_handler.py:457: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T20:03:06.253242Z[0m [[32m[1minfo [0m] [1mReceived pull_request event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'pull_request', 'action': 'opened'}[0m
[2m2026-05-16T20:03:06.253791Z[0m [[32m[1minfo [0m] [1mNo stampbot.toml found in acme/widgets, using defaults[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'acme/widgets', 'org_repo': None}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'level': 'info', 'timestamp': '2026-05-16T20:03:06.253242Z'}
INFO stampbot.webhook_handler:webhook_handler.py:593 {'extra': {'repo': 'acme/widgets', 'org_repo': None}, 'event': 'No stampbot.toml found in acme/widgets, using defaults', 'level': 'info', 'timestamp': '2026-05-16T20:03:06.253791Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_org_github_repo_config_fallback - AssertionError: assert 'ignored' == 'success'
- success
+ ignored
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 189 passed, 3 deselected in 1.59s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceComparisonOperator_NotEq_Gt, occurrence: 0
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -574,7 +574,7 @@
if (
owner_type == "Organization"
and owner_login
- and repo_full_name != f"{owner_login}/.github"
+ and repo_full_name > f"{owner_login}/.github"
):
org_repo_full_name = f"{owner_login}/.github"
org_content = await run_in_threadpool(........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceComparisonOperator_NotEq_GtE, occurrence: 0
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -574,7 +574,7 @@
if (
owner_type == "Organization"
and owner_login
- and repo_full_name != f"{owner_login}/.github"
+ and repo_full_name >= f"{owner_login}/.github"
):
org_repo_full_name = f"{owner_login}/.github"
org_content = await run_in_threadpool(........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceComparisonOperator_NotEq_Is, occurrence: 0
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -574,7 +574,7 @@
if (
owner_type == "Organization"
and owner_login
- and repo_full_name != f"{owner_login}/.github"
+ and repo_full_name is f"{owner_login}/.github"
):
org_repo_full_name = f"{owner_login}/.github"
org_content = await run_in_threadpool(........................................................................ [ 34%]
........................................................................ [ 68%]
.............................................F
=================================== FAILURES ===================================
_____________________ test_org_github_repo_config_fallback _____________________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7fa52acb3770>
mock_github_client = <MagicMock name='github_client' id='140347365147712'>
@pytest.mark.asyncio
async def test_org_github_repo_config_fallback(webhook_handler, mock_github_client):
"""Test org .github stampbot.toml is used when repo config is missing."""
payload = load_fixture("pr_opened_with_autoapprove_label")
payload["repository"]["full_name"] = "acme/widgets"
payload["repository"]["default_branch"] = "main"
payload["repository"]["owner"] = {"login": "acme", "type": "Organization"}
payload["pull_request"]["labels"] = [{"name": "org-approve"}]
def get_repo_file_side_effect(_installation_id, repo_full_name, _file_path, _ref):
if repo_full_name == "acme/widgets":
return None
if repo_full_name == "acme/.github":
return 'approval_labels = ["org-approve"]'
return None
mock_github_client.get_repo_file.side_effect = get_repo_file_side_effect
result = await webhook_handler.handle_event("pull_request", payload)
> assert result["status"] == "success"
E AssertionError: assert 'ignored' == 'success'
E
E - success
E + ignored
tests/test_webhook_handler.py:457: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:34:41.688503Z[0m [[32m[1minfo [0m] [1mReceived pull_request event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'pull_request', 'action': 'opened'}[0m
[2m2026-05-16T19:34:41.689036Z[0m [[32m[1minfo [0m] [1mNo stampbot.toml found in acme/widgets, using defaults[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'acme/widgets', 'org_repo': None}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'level': 'info', 'timestamp': '2026-05-16T19:34:41.688503Z'}
INFO stampbot.webhook_handler:webhook_handler.py:593 {'extra': {'repo': 'acme/widgets', 'org_repo': None}, 'event': 'No stampbot.toml found in acme/widgets, using defaults', 'level': 'info', 'timestamp': '2026-05-16T19:34:41.689036Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_org_github_repo_config_fallback - AssertionError: assert 'ignored' == 'success'
- success
+ ignored
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 189 passed, 3 deselected in 1.54s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceComparisonOperator_NotEq_IsNot, occurrence: 0
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -574,7 +574,7 @@
if (
owner_type == "Organization"
and owner_login
- and repo_full_name != f"{owner_login}/.github"
+ and repo_full_name is not f"{owner_login}/.github"
):
org_repo_full_name = f"{owner_login}/.github"
org_content = await run_in_threadpool(........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.63s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceComparisonOperator_Gt_Eq, occurrence: 0
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -370,7 +370,7 @@
comment_body = comment.get("body", "")
# Security: limit comment length to prevent DoS
- if len(comment_body) > MAX_COMMENT_LENGTH:
+ if len(comment_body) == MAX_COMMENT_LENGTH:
return {"status": "ignored", "message": "Comment too long"}
comment_body = comment_body.lower().strip()........................................................................ [ 34%]
........................................................................ [ 68%]
.....................................F
=================================== FAILURES ===================================
_____________________ test_issue_comment_too_long_rejected _____________________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7f3cf4893020>
mock_github_client = <MagicMock name='github_client' id='139899620466976'>
@pytest.mark.asyncio
async def test_issue_comment_too_long_rejected(webhook_handler, mock_github_client):
"""Test that comments exceeding max length are rejected to prevent DoS."""
payload = load_fixture("issue_comment_approve")
# Create an excessively long comment (> 64KB)
payload["comment"]["body"] = "@stampbot approve " + "x" * 100000
result = await webhook_handler.handle_event("issue_comment", payload)
> assert result["status"] == "ignored"
E AssertionError: assert 'success' == 'ignored'
E
E - ignored
E + success
tests/test_webhook_handler.py:315: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:43:34.609893Z[0m [[32m[1minfo [0m] [1mReceived issue_comment event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'issue_comment', 'action': 'created'}[0m
[2m2026-05-16T19:43:34.610517Z[0m [[32m[1minfo [0m] [1mNo stampbot.toml found in octocat/hello-world, using defaults[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'org_repo': None}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'level': 'info', 'timestamp': '2026-05-16T19:43:34.609893Z'}
INFO stampbot.webhook_handler:webhook_handler.py:593 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'level': 'info', 'timestamp': '2026-05-16T19:43:34.610517Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_issue_comment_too_long_rejected - AssertionError: assert 'success' == 'ignored'
- ignored
+ success
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 181 passed, 3 deselected in 1.18s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceComparisonOperator_Gt_NotEq, occurrence: 0
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -370,7 +370,7 @@
comment_body = comment.get("body", "")
# Security: limit comment length to prevent DoS
- if len(comment_body) > MAX_COMMENT_LENGTH:
+ if len(comment_body) != MAX_COMMENT_LENGTH:
return {"status": "ignored", "message": "Comment too long"}
comment_body = comment_body.lower().strip()........................................................................ [ 34%]
........................................................................ [ 68%]
.................................F
=================================== FAILURES ===================================
__________________________ test_issue_comment_approve __________________________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7f921a6671d0>
mock_github_client = <MagicMock name='github_client' id='140265485365136'>
@pytest.mark.asyncio
async def test_issue_comment_approve(webhook_handler, mock_github_client):
"""Test @stampbot approve command."""
payload = load_fixture("issue_comment_approve")
result = await webhook_handler.handle_event("issue_comment", payload)
> assert result["status"] == "success"
E AssertionError: assert 'ignored' == 'success'
E
E - success
E + ignored
tests/test_webhook_handler.py:259: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T20:07:18.939765Z[0m [[32m[1minfo [0m] [1mReceived issue_comment event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'issue_comment', 'action': 'created'}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'level': 'info', 'timestamp': '2026-05-16T20:07:18.939765Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_issue_comment_approve - AssertionError: assert 'ignored' == 'success'
- success
+ ignored
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 177 passed, 3 deselected in 1.16s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceComparisonOperator_Gt_Lt, occurrence: 0
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -370,7 +370,7 @@
comment_body = comment.get("body", "")
# Security: limit comment length to prevent DoS
- if len(comment_body) > MAX_COMMENT_LENGTH:
+ if len(comment_body) < MAX_COMMENT_LENGTH:
return {"status": "ignored", "message": "Comment too long"}
comment_body = comment_body.lower().strip()........................................................................ [ 34%]
........................................................................ [ 68%]
.................................F
=================================== FAILURES ===================================
__________________________ test_issue_comment_approve __________________________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7f29b245f330>
mock_github_client = <MagicMock name='github_client' id='139817061805968'>
@pytest.mark.asyncio
async def test_issue_comment_approve(webhook_handler, mock_github_client):
"""Test @stampbot approve command."""
payload = load_fixture("issue_comment_approve")
result = await webhook_handler.handle_event("issue_comment", payload)
> assert result["status"] == "success"
E AssertionError: assert 'ignored' == 'success'
E
E - success
E + ignored
tests/test_webhook_handler.py:259: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:41:54.588566Z[0m [[32m[1minfo [0m] [1mReceived issue_comment event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'issue_comment', 'action': 'created'}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'level': 'info', 'timestamp': '2026-05-16T19:41:54.588566Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_issue_comment_approve - AssertionError: assert 'ignored' == 'success'
- success
+ ignored
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 177 passed, 3 deselected in 1.11s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceComparisonOperator_Gt_LtE, occurrence: 0
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -370,7 +370,7 @@
comment_body = comment.get("body", "")
# Security: limit comment length to prevent DoS
- if len(comment_body) > MAX_COMMENT_LENGTH:
+ if len(comment_body) <= MAX_COMMENT_LENGTH:
return {"status": "ignored", "message": "Comment too long"}
comment_body = comment_body.lower().strip()........................................................................ [ 34%]
........................................................................ [ 68%]
.................................F
=================================== FAILURES ===================================
__________________________ test_issue_comment_approve __________________________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7f586e875ff0>
mock_github_client = <MagicMock name='github_client' id='140017788710800'>
@pytest.mark.asyncio
async def test_issue_comment_approve(webhook_handler, mock_github_client):
"""Test @stampbot approve command."""
payload = load_fixture("issue_comment_approve")
result = await webhook_handler.handle_event("issue_comment", payload)
> assert result["status"] == "success"
E AssertionError: assert 'ignored' == 'success'
E
E - success
E + ignored
tests/test_webhook_handler.py:259: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:36:01.748019Z[0m [[32m[1minfo [0m] [1mReceived issue_comment event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'issue_comment', 'action': 'created'}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'level': 'info', 'timestamp': '2026-05-16T19:36:01.748019Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_issue_comment_approve - AssertionError: assert 'ignored' == 'success'
- success
+ ignored
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 177 passed, 3 deselected in 1.12s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceComparisonOperator_Gt_GtE, occurrence: 0
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -370,7 +370,7 @@
comment_body = comment.get("body", "")
# Security: limit comment length to prevent DoS
- if len(comment_body) > MAX_COMMENT_LENGTH:
+ if len(comment_body) >= MAX_COMMENT_LENGTH:
return {"status": "ignored", "message": "Comment too long"}
comment_body = comment_body.lower().strip()........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.60s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceComparisonOperator_Gt_Is, occurrence: 0
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -370,7 +370,7 @@
comment_body = comment.get("body", "")
# Security: limit comment length to prevent DoS
- if len(comment_body) > MAX_COMMENT_LENGTH:
+ if len(comment_body) is MAX_COMMENT_LENGTH:
return {"status": "ignored", "message": "Comment too long"}
comment_body = comment_body.lower().strip()........................................................................ [ 34%]
........................................................................ [ 68%]
.....................................F
=================================== FAILURES ===================================
_____________________ test_issue_comment_too_long_rejected _____________________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7fdfa6fe7020>
mock_github_client = <MagicMock name='github_client' id='140598464897312'>
@pytest.mark.asyncio
async def test_issue_comment_too_long_rejected(webhook_handler, mock_github_client):
"""Test that comments exceeding max length are rejected to prevent DoS."""
payload = load_fixture("issue_comment_approve")
# Create an excessively long comment (> 64KB)
payload["comment"]["body"] = "@stampbot approve " + "x" * 100000
result = await webhook_handler.handle_event("issue_comment", payload)
> assert result["status"] == "ignored"
E AssertionError: assert 'success' == 'ignored'
E
E - ignored
E + success
tests/test_webhook_handler.py:315: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:59:28.409790Z[0m [[32m[1minfo [0m] [1mReceived issue_comment event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'issue_comment', 'action': 'created'}[0m
[2m2026-05-16T19:59:28.410377Z[0m [[32m[1minfo [0m] [1mNo stampbot.toml found in octocat/hello-world, using defaults[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'org_repo': None}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'level': 'info', 'timestamp': '2026-05-16T19:59:28.409790Z'}
INFO stampbot.webhook_handler:webhook_handler.py:593 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'level': 'info', 'timestamp': '2026-05-16T19:59:28.410377Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_issue_comment_too_long_rejected - AssertionError: assert 'success' == 'ignored'
- ignored
+ success
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 181 passed, 3 deselected in 1.12s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceComparisonOperator_Gt_IsNot, occurrence: 0
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -370,7 +370,7 @@
comment_body = comment.get("body", "")
# Security: limit comment length to prevent DoS
- if len(comment_body) > MAX_COMMENT_LENGTH:
+ if len(comment_body) is not MAX_COMMENT_LENGTH:
return {"status": "ignored", "message": "Comment too long"}
comment_body = comment_body.lower().strip()........................................................................ [ 34%]
........................................................................ [ 68%]
.................................F
=================================== FAILURES ===================================
__________________________ test_issue_comment_approve __________________________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7fd106e4f1d0>
mock_github_client = <MagicMock name='github_client' id='140535741050768'>
@pytest.mark.asyncio
async def test_issue_comment_approve(webhook_handler, mock_github_client):
"""Test @stampbot approve command."""
payload = load_fixture("issue_comment_approve")
result = await webhook_handler.handle_event("issue_comment", payload)
> assert result["status"] == "success"
E AssertionError: assert 'ignored' == 'success'
E
E - success
E + ignored
tests/test_webhook_handler.py:259: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T20:05:05.904002Z[0m [[32m[1minfo [0m] [1mReceived issue_comment event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'issue_comment', 'action': 'created'}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'level': 'info', 'timestamp': '2026-05-16T20:05:05.904002Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_issue_comment_approve - AssertionError: assert 'ignored' == 'success'
- success
+ ignored
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 177 passed, 3 deselected in 1.14s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceComparisonOperator_Is_Eq, occurrence: 0
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -214,7 +214,7 @@
repo_full_name,
label,
)
- if label_exists is False:
+ if label_exists == False:
logger.warning(
"Approval label %s not found in %s",
label,........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.59s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceComparisonOperator_Is_NotEq, occurrence: 0
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -214,7 +214,7 @@
repo_full_name,
label,
)
- if label_exists is False:
+ if label_exists != False:
logger.warning(
"Approval label %s not found in %s",
label,........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceComparisonOperator_Is_Lt, occurrence: 0
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -214,7 +214,7 @@
repo_full_name,
label,
)
- if label_exists is False:
+ if label_exists < False:
logger.warning(
"Approval label %s not found in %s",
label,........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.62s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceComparisonOperator_Is_LtE, occurrence: 0
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -214,7 +214,7 @@
repo_full_name,
label,
)
- if label_exists is False:
+ if label_exists <= False:
logger.warning(
"Approval label %s not found in %s",
label,........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.68s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceComparisonOperator_Is_Gt, occurrence: 0
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -214,7 +214,7 @@
repo_full_name,
label,
)
- if label_exists is False:
+ if label_exists > False:
logger.warning(
"Approval label %s not found in %s",
label,........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.71s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceComparisonOperator_Is_GtE, occurrence: 0
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -214,7 +214,7 @@
repo_full_name,
label,
)
- if label_exists is False:
+ if label_exists >= False:
logger.warning(
"Approval label %s not found in %s",
label,........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceComparisonOperator_Is_IsNot, occurrence: 0
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -49,7 +49,7 @@
Raises:
RuntimeError: If webhook secret is not configured.
"""
- if self._webhook_secret is None:
+ if self._webhook_secret is not None:
if not settings.webhook_secret:
raise RuntimeError(
"Webhook secret not configured. Visit /setup to create your GitHub App."........................................................................ [ 34%]
........................F
=================================== FAILURES ===================================
_______________________ test_request_with_content_length _______________________
test_client = <starlette.testclient.TestClient object at 0x7f04906a7770>
def test_request_with_content_length(test_client: TestClient):
"""Test that requests with Content-Length header are tracked."""
# POST with a body will have Content-Length set automatically
> response = test_client.post(
"/webhook",
content=b'{"test": "data"}',
headers={
"Content-Type": "application/json",
"X-GitHub-Event": "ping",
"X-Hub-Signature-256": "sha256=invalid",
},
)
tests/test_main.py:69:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:546: in post
return super().post(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1144: in post
return self.request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:445: in request
return super().request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:825: in request
return self.send(request, auth=auth, follow_redirects=follow_redirects)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:914: in send
response = self._send_handling_auth(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:942: in _send_handling_auth
response = self._send_handling_redirects(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:979: in _send_handling_redirects
response = self._send_single_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1014: in _send_single_request
response = transport.handle_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:348: in handle_request
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:345: in handle_request
portal.call(self.app, scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:334: in call
return cast(T_Retval, self.start_task_soon(func, *args).result())
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:443: in result
return self.__get_result()
^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:395: in __get_result
raise self._exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:259: in _call_func
retval = await retval_or_awaitable
^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1135: in __call__
await super().__call__(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:107: in __call__
await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:186: in __call__
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:164: in __call__
await self.app(scope, receive, _send)
stampbot/main.py:205: in __call__
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:191: in __call__
with recv_stream, send_stream, collapse_excgroups():
^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/contextlib.py:162: in __exit__
self.gen.throw(value)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_utils.py:85: in collapse_excgroups
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:193: in __call__
response = await self.dispatch_func(request, call_next)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:120: in metrics_middleware
response = await call_next(request)
^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:168: in call_next
raise app_exc from app_exc.__cause__ or app_exc.__context__
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:144: in coro
await self.app(scope, receive_or_disconnect, send_no_error)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/exceptions.py:63: in __call__
await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/middleware/asyncexitstack.py:18: in __call__
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:716: in __call__
await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:736: in app
await route.handle(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:290: in handle
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:115: in app
await wrap_app_handling_exceptions(app, request)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:101: in app
response = await f(request)
^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:355: in app
raw_response = await run_endpoint_function(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:243: in run_endpoint_function
return await dependant.call(**values)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:296: in webhook
if not webhook_handler.verify_signature(body, x_hub_signature_256):
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/webhook_handler.py:74: in verify_signature
"sha256=" + hmac.new(self.webhook_secret, payload, hashlib.sha256).hexdigest()
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/hmac.py:218: in new
return HMAC(key, msg, digestmod)
^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <hmac.HMAC object at 0x7f04904b9a80>, key = None
msg = b'{"test": "data"}', digestmod = <built-in function openssl_sha256>
def __init__(self, key, msg=None, digestmod=''):
"""Create a new HMAC object.
key: bytes or buffer, key for the keyed hash object.
msg: bytes or buffer, Initial input for the hash or None.
digestmod: A hash name suitable for hashlib.new(). *OR*
A hashlib constructor returning a new hash object. *OR*
A module supporting PEP 247.
Required as of 3.8, despite its position after the optional
msg argument. Passing it as a keyword argument is
recommended, though not required for legacy API reasons.
"""
if not isinstance(key, (bytes, bytearray)):
> raise TypeError(f"key: expected bytes or bytearray, "
f"but got {type(key).__name__!r}")
E TypeError: key: expected bytes or bytearray, but got 'NoneType'
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/hmac.py:71: TypeError
=========================== short test summary info ============================
FAILED tests/test_main.py::test_request_with_content_length - TypeError: key: expected bytes or bytearray, but got 'NoneType'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 96 passed, 3 deselected in 1.17s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceComparisonOperator_Is_IsNot, occurrence: 1
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -214,7 +214,7 @@
repo_full_name,
label,
)
- if label_exists is False:
+ if label_exists is not False:
logger.warning(
"Approval label %s not found in %s",
label,........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceUnaryOperator_USub_UAdd, occurrence: 0
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -383,7 +383,7 @@
if "pull_request" in payload.get("issue", {}):
pr_url = payload["issue"]["pull_request"]["url"]
# Extract PR number from URL
- pr_number = int(pr_url.split("/")[-1])
+ pr_number = int(pr_url.split("/")[+1])
elif "pull_request" in payload:
pr_number = payload["pull_request"]["number"]
else:........................................................................ [ 34%]
........................................................................ [ 68%]
.................................F
=================================== FAILURES ===================================
__________________________ test_issue_comment_approve __________________________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7f4a2055f1d0>
mock_github_client = <MagicMock name='github_client' id='139956347301776'>
@pytest.mark.asyncio
async def test_issue_comment_approve(webhook_handler, mock_github_client):
"""Test @stampbot approve command."""
payload = load_fixture("issue_comment_approve")
> result = await webhook_handler.handle_event("issue_comment", payload)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_webhook_handler.py:257:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
stampbot/webhook_handler.py:119: in handle_event
result = await self._handle_pr_comment(payload)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.webhook_handler.WebhookHandler object at 0x7f4a2055f1d0>
payload = {'action': 'created', 'comment': {'body': '@stampbot approve\n\nLGTM, approving this PR.', 'created_at': '2025-01-10T1...U='}, 'issue': {'assignee': None, 'assignees': [], 'body': 'This PR adds a new feature.', 'closed_at': None, ...}, ...}
async def _handle_pr_comment(self, payload: dict[str, Any]) -> dict[str, Any]:
"""Handle PR comment events for chatops.
Args:
payload: Event payload
Returns:
Response dictionary
"""
comment = payload.get("comment", {})
comment_body = comment.get("body", "")
# Security: limit comment length to prevent DoS
if len(comment_body) > MAX_COMMENT_LENGTH:
return {"status": "ignored", "message": "Comment too long"}
comment_body = comment_body.lower().strip()
# Check if bot is mentioned
if "@stampbot" not in comment_body:
return {"status": "ignored", "message": "Bot not mentioned"}
# Extract PR info
if "pull_request" in payload.get("issue", {}):
pr_url = payload["issue"]["pull_request"]["url"]
# Extract PR number from URL
> pr_number = int(pr_url.split("/")[+1])
^^^^^^^^^^^^^^^^^^^^^^^^^^
E ValueError: invalid literal for int() with base 10: ''
stampbot/webhook_handler.py:386: ValueError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T20:04:56.524337Z[0m [[32m[1minfo [0m] [1mReceived issue_comment event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'issue_comment', 'action': 'created'}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'level': 'info', 'timestamp': '2026-05-16T20:04:56.524337Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_issue_comment_approve - ValueError: invalid literal for int() with base 10: ''
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 177 passed, 3 deselected in 1.20s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceUnaryOperator_USub_Invert, occurrence: 0
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -383,7 +383,7 @@
if "pull_request" in payload.get("issue", {}):
pr_url = payload["issue"]["pull_request"]["url"]
# Extract PR number from URL
- pr_number = int(pr_url.split("/")[-1])
+ pr_number = int(pr_url.split("/")[~1])
elif "pull_request" in payload:
pr_number = payload["pull_request"]["number"]
else:........................................................................ [ 34%]
........................................................................ [ 68%]
.................................F
=================================== FAILURES ===================================
__________________________ test_issue_comment_approve __________________________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7fce6435f330>
mock_github_client = <MagicMock name='github_client' id='140524421754768'>
@pytest.mark.asyncio
async def test_issue_comment_approve(webhook_handler, mock_github_client):
"""Test @stampbot approve command."""
payload = load_fixture("issue_comment_approve")
> result = await webhook_handler.handle_event("issue_comment", payload)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_webhook_handler.py:257:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
stampbot/webhook_handler.py:119: in handle_event
result = await self._handle_pr_comment(payload)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.webhook_handler.WebhookHandler object at 0x7fce6435f330>
payload = {'action': 'created', 'comment': {'body': '@stampbot approve\n\nLGTM, approving this PR.', 'created_at': '2025-01-10T1...U='}, 'issue': {'assignee': None, 'assignees': [], 'body': 'This PR adds a new feature.', 'closed_at': None, ...}, ...}
async def _handle_pr_comment(self, payload: dict[str, Any]) -> dict[str, Any]:
"""Handle PR comment events for chatops.
Args:
payload: Event payload
Returns:
Response dictionary
"""
comment = payload.get("comment", {})
comment_body = comment.get("body", "")
# Security: limit comment length to prevent DoS
if len(comment_body) > MAX_COMMENT_LENGTH:
return {"status": "ignored", "message": "Comment too long"}
comment_body = comment_body.lower().strip()
# Check if bot is mentioned
if "@stampbot" not in comment_body:
return {"status": "ignored", "message": "Bot not mentioned"}
# Extract PR info
if "pull_request" in payload.get("issue", {}):
pr_url = payload["issue"]["pull_request"]["url"]
# Extract PR number from URL
> pr_number = int(pr_url.split("/")[~1])
^^^^^^^^^^^^^^^^^^^^^^^^^^
E ValueError: invalid literal for int() with base 10: 'pulls'
stampbot/webhook_handler.py:386: ValueError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:58:13.569033Z[0m [[32m[1minfo [0m] [1mReceived issue_comment event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'issue_comment', 'action': 'created'}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'level': 'info', 'timestamp': '2026-05-16T19:58:13.569033Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_issue_comment_approve - ValueError: invalid literal for int() with base 10: 'pulls'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 177 passed, 3 deselected in 1.16s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceUnaryOperator_USub_Not, occurrence: 0
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -383,7 +383,7 @@
if "pull_request" in payload.get("issue", {}):
pr_url = payload["issue"]["pull_request"]["url"]
# Extract PR number from URL
- pr_number = int(pr_url.split("/")[-1])
+ pr_number = int(pr_url.split("/")[not 1])
elif "pull_request" in payload:
pr_number = payload["pull_request"]["number"]
else:........................................................................ [ 34%]
........................................................................ [ 68%]
.................................F
=================================== FAILURES ===================================
__________________________ test_issue_comment_approve __________________________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7fcc5e757330>
mock_github_client = <MagicMock name='github_client' id='140515735334800'>
@pytest.mark.asyncio
async def test_issue_comment_approve(webhook_handler, mock_github_client):
"""Test @stampbot approve command."""
payload = load_fixture("issue_comment_approve")
> result = await webhook_handler.handle_event("issue_comment", payload)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_webhook_handler.py:257:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
stampbot/webhook_handler.py:119: in handle_event
result = await self._handle_pr_comment(payload)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.webhook_handler.WebhookHandler object at 0x7fcc5e757330>
payload = {'action': 'created', 'comment': {'body': '@stampbot approve\n\nLGTM, approving this PR.', 'created_at': '2025-01-10T1...U='}, 'issue': {'assignee': None, 'assignees': [], 'body': 'This PR adds a new feature.', 'closed_at': None, ...}, ...}
async def _handle_pr_comment(self, payload: dict[str, Any]) -> dict[str, Any]:
"""Handle PR comment events for chatops.
Args:
payload: Event payload
Returns:
Response dictionary
"""
comment = payload.get("comment", {})
comment_body = comment.get("body", "")
# Security: limit comment length to prevent DoS
if len(comment_body) > MAX_COMMENT_LENGTH:
return {"status": "ignored", "message": "Comment too long"}
comment_body = comment_body.lower().strip()
# Check if bot is mentioned
if "@stampbot" not in comment_body:
return {"status": "ignored", "message": "Bot not mentioned"}
# Extract PR info
if "pull_request" in payload.get("issue", {}):
pr_url = payload["issue"]["pull_request"]["url"]
# Extract PR number from URL
> pr_number = int(pr_url.split("/")[not 1])
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E ValueError: invalid literal for int() with base 10: 'https:'
stampbot/webhook_handler.py:386: ValueError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:53:20.185268Z[0m [[32m[1minfo [0m] [1mReceived issue_comment event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'issue_comment', 'action': 'created'}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'level': 'info', 'timestamp': '2026-05-16T19:53:20.185268Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_issue_comment_approve - ValueError: invalid literal for int() with base 10: 'https:'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 177 passed, 3 deselected in 1.18s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceUnaryOperator_Delete_USub, occurrence: 0
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -383,7 +383,7 @@
if "pull_request" in payload.get("issue", {}):
pr_url = payload["issue"]["pull_request"]["url"]
# Extract PR number from URL
- pr_number = int(pr_url.split("/")[-1])
+ pr_number = int(pr_url.split("/")[1])
elif "pull_request" in payload:
pr_number = payload["pull_request"]["number"]
else:........................................................................ [ 34%]
........................................................................ [ 68%]
.................................F
=================================== FAILURES ===================================
__________________________ test_issue_comment_approve __________________________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7f8dbbf4b330>
mock_github_client = <MagicMock name='github_client' id='140246720982928'>
@pytest.mark.asyncio
async def test_issue_comment_approve(webhook_handler, mock_github_client):
"""Test @stampbot approve command."""
payload = load_fixture("issue_comment_approve")
> result = await webhook_handler.handle_event("issue_comment", payload)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_webhook_handler.py:257:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
stampbot/webhook_handler.py:119: in handle_event
result = await self._handle_pr_comment(payload)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.webhook_handler.WebhookHandler object at 0x7f8dbbf4b330>
payload = {'action': 'created', 'comment': {'body': '@stampbot approve\n\nLGTM, approving this PR.', 'created_at': '2025-01-10T1...U='}, 'issue': {'assignee': None, 'assignees': [], 'body': 'This PR adds a new feature.', 'closed_at': None, ...}, ...}
async def _handle_pr_comment(self, payload: dict[str, Any]) -> dict[str, Any]:
"""Handle PR comment events for chatops.
Args:
payload: Event payload
Returns:
Response dictionary
"""
comment = payload.get("comment", {})
comment_body = comment.get("body", "")
# Security: limit comment length to prevent DoS
if len(comment_body) > MAX_COMMENT_LENGTH:
return {"status": "ignored", "message": "Comment too long"}
comment_body = comment_body.lower().strip()
# Check if bot is mentioned
if "@stampbot" not in comment_body:
return {"status": "ignored", "message": "Bot not mentioned"}
# Extract PR info
if "pull_request" in payload.get("issue", {}):
pr_url = payload["issue"]["pull_request"]["url"]
# Extract PR number from URL
> pr_number = int(pr_url.split("/")[1])
^^^^^^^^^^^^^^^^^^^^^^^^^
E ValueError: invalid literal for int() with base 10: ''
stampbot/webhook_handler.py:386: ValueError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:56:29.481791Z[0m [[32m[1minfo [0m] [1mReceived issue_comment event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'issue_comment', 'action': 'created'}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'level': 'info', 'timestamp': '2026-05-16T19:56:29.481791Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_issue_comment_approve - ValueError: invalid literal for int() with base 10: ''
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 177 passed, 3 deselected in 1.16s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceUnaryOperator_Delete_Not, occurrence: 0
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -50,7 +50,7 @@
RuntimeError: If webhook secret is not configured.
"""
if self._webhook_secret is None:
- if not settings.webhook_secret:
+ if settings.webhook_secret:
raise RuntimeError(
"Webhook secret not configured. Visit /setup to create your GitHub App."
)........................................................................ [ 34%]
........................F
=================================== FAILURES ===================================
_______________________ test_request_with_content_length _______________________
test_client = <starlette.testclient.TestClient object at 0x7f73d429f770>
def test_request_with_content_length(test_client: TestClient):
"""Test that requests with Content-Length header are tracked."""
# POST with a body will have Content-Length set automatically
> response = test_client.post(
"/webhook",
content=b'{"test": "data"}',
headers={
"Content-Type": "application/json",
"X-GitHub-Event": "ping",
"X-Hub-Signature-256": "sha256=invalid",
},
)
tests/test_main.py:69:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:546: in post
return super().post(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1144: in post
return self.request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:445: in request
return super().request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:825: in request
return self.send(request, auth=auth, follow_redirects=follow_redirects)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:914: in send
response = self._send_handling_auth(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:942: in _send_handling_auth
response = self._send_handling_redirects(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:979: in _send_handling_redirects
response = self._send_single_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1014: in _send_single_request
response = transport.handle_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:348: in handle_request
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:345: in handle_request
portal.call(self.app, scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:334: in call
return cast(T_Retval, self.start_task_soon(func, *args).result())
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:443: in result
return self.__get_result()
^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:395: in __get_result
raise self._exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:259: in _call_func
retval = await retval_or_awaitable
^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1135: in __call__
await super().__call__(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:107: in __call__
await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:186: in __call__
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:164: in __call__
await self.app(scope, receive, _send)
stampbot/main.py:205: in __call__
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:191: in __call__
with recv_stream, send_stream, collapse_excgroups():
^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/contextlib.py:162: in __exit__
self.gen.throw(value)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_utils.py:85: in collapse_excgroups
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:193: in __call__
response = await self.dispatch_func(request, call_next)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:120: in metrics_middleware
response = await call_next(request)
^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:168: in call_next
raise app_exc from app_exc.__cause__ or app_exc.__context__
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:144: in coro
await self.app(scope, receive_or_disconnect, send_no_error)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/exceptions.py:63: in __call__
await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/middleware/asyncexitstack.py:18: in __call__
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:716: in __call__
await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:736: in app
await route.handle(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:290: in handle
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:115: in app
await wrap_app_handling_exceptions(app, request)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:101: in app
response = await f(request)
^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:355: in app
raw_response = await run_endpoint_function(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:243: in run_endpoint_function
return await dependant.call(**values)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:296: in webhook
if not webhook_handler.verify_signature(body, x_hub_signature_256):
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/webhook_handler.py:74: in verify_signature
"sha256=" + hmac.new(self.webhook_secret, payload, hashlib.sha256).hexdigest()
^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.webhook_handler.WebhookHandler object at 0x7f73d42a2e40>
@property
def webhook_secret(self) -> bytes:
"""Get webhook secret, initializing if needed.
Returns:
Webhook secret as bytes.
Raises:
RuntimeError: If webhook secret is not configured.
"""
if self._webhook_secret is None:
if settings.webhook_secret:
> raise RuntimeError(
"Webhook secret not configured. Visit /setup to create your GitHub App."
)
E RuntimeError: Webhook secret not configured. Visit /setup to create your GitHub App.
stampbot/webhook_handler.py:54: RuntimeError
=========================== short test summary info ============================
FAILED tests/test_main.py::test_request_with_content_length - RuntimeError: Webhook secret not configured. Visit /setup to create your GitHub App.
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 96 passed, 3 deselected in 1.18s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceUnaryOperator_Delete_Not, occurrence: 1
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -67,7 +67,7 @@
Returns:
True if signature is valid
"""
- if not signature:
+ if signature:
return False
expected_signature = (........................................................................ [ 34%]
.........................F
=================================== FAILURES ===================================
________________________ test_webhook_missing_signature ________________________
test_client = <starlette.testclient.TestClient object at 0x7f53bfeb45a0>
def test_webhook_missing_signature(test_client: TestClient):
"""Test webhook rejects requests without signature."""
> response = test_client.post(
"/webhook",
json={"zen": "test"},
headers={"X-GitHub-Event": "ping"},
)
tests/test_main.py:84:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:546: in post
return super().post(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1144: in post
return self.request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:445: in request
return super().request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:825: in request
return self.send(request, auth=auth, follow_redirects=follow_redirects)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:914: in send
response = self._send_handling_auth(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:942: in _send_handling_auth
response = self._send_handling_redirects(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:979: in _send_handling_redirects
response = self._send_single_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1014: in _send_single_request
response = transport.handle_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:348: in handle_request
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:345: in handle_request
portal.call(self.app, scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:334: in call
return cast(T_Retval, self.start_task_soon(func, *args).result())
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:443: in result
return self.__get_result()
^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:395: in __get_result
raise self._exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:259: in _call_func
retval = await retval_or_awaitable
^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1135: in __call__
await super().__call__(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:107: in __call__
await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:186: in __call__
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:164: in __call__
await self.app(scope, receive, _send)
stampbot/main.py:205: in __call__
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:191: in __call__
with recv_stream, send_stream, collapse_excgroups():
^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/contextlib.py:162: in __exit__
self.gen.throw(value)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_utils.py:85: in collapse_excgroups
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:193: in __call__
response = await self.dispatch_func(request, call_next)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:120: in metrics_middleware
response = await call_next(request)
^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:168: in call_next
raise app_exc from app_exc.__cause__ or app_exc.__context__
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:144: in coro
await self.app(scope, receive_or_disconnect, send_no_error)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/exceptions.py:63: in __call__
await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/middleware/asyncexitstack.py:18: in __call__
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:716: in __call__
await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:736: in app
await route.handle(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:290: in handle
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:115: in app
await wrap_app_handling_exceptions(app, request)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:101: in app
response = await f(request)
^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:355: in app
raw_response = await run_endpoint_function(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:243: in run_endpoint_function
return await dependant.call(**values)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:296: in webhook
if not webhook_handler.verify_signature(body, x_hub_signature_256):
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.webhook_handler.WebhookHandler object at 0x7f53c41aee40>
payload = b'{"zen":"test"}', signature = None
def verify_signature(self, payload: bytes, signature: str) -> bool:
"""Verify GitHub webhook signature.
Args:
payload: Request payload
signature: X-Hub-Signature-256 header value
Returns:
True if signature is valid
"""
if signature:
return False
expected_signature = (
"sha256=" + hmac.new(self.webhook_secret, payload, hashlib.sha256).hexdigest()
)
> return hmac.compare_digest(expected_signature, signature)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E TypeError: unsupported operand types(s) or combination of types: 'str' and 'NoneType'
stampbot/webhook_handler.py:77: TypeError
=========================== short test summary info ============================
FAILED tests/test_main.py::test_webhook_missing_signature - TypeError: unsupported operand types(s) or combination of types: 'str' and 'NoneType'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 97 passed, 3 deselected in 1.19s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceUnaryOperator_Delete_Not, occurrence: 2
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -157,7 +157,7 @@
"github.action": action or "unknown",
},
) as span:
- if not all([pr_number, repo_full_name, installation_id]):
+ if all([pr_number, repo_full_name, installation_id]):
logger.warning("Missing required fields in PR event")
add_span_attributes(span, {"webhook.result": "missing_fields"})
set_span_ok(span)........................................................................ [ 34%]
........................................................................ [ 68%]
...................F
=================================== FAILURES ===================================
____________________ test_pr_opened_with_autoapprove_label _____________________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7fc0c9fe3e10>
mock_github_client = <MagicMock name='github_client' id='140465995629296'>
@pytest.mark.asyncio
async def test_pr_opened_with_autoapprove_label(webhook_handler, mock_github_client):
"""Test PR opened with autoapprove label triggers approval."""
payload = load_fixture("pr_opened_with_autoapprove_label")
result = await webhook_handler.handle_event("pull_request", payload)
> assert result["status"] == "success"
E AssertionError: assert 'error' == 'success'
E
E - success
E + error
tests/test_webhook_handler.py:70: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:47:58.829388Z[0m [[32m[1minfo [0m] [1mReceived pull_request event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'pull_request', 'action': 'opened'}[0m
[2m2026-05-16T19:47:58.829580Z[0m [[33m[1mwarning [0m] [1mMissing required fields in PR event[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35mwarning[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'level': 'info', 'timestamp': '2026-05-16T19:47:58.829388Z'}
WARNING stampbot.webhook_handler:webhook_handler.py:161 {'event': 'Missing required fields in PR event', 'level': 'warning', 'timestamp': '2026-05-16T19:47:58.829580Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_opened_with_autoapprove_label - AssertionError: assert 'error' == 'success'
- success
+ error
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 163 passed, 3 deselected in 1.10s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceUnaryOperator_Delete_Not, occurrence: 3
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -250,7 +250,7 @@
is_eligible, reason = repo_config.is_pr_eligible(
labels, pr_title, pr_author, author_team_slugs
)
- if not is_eligible:
+ if is_eligible:
logger.info(
"PR #%d not eligible for auto-approval: %s",
pr_number,........................................................................ [ 34%]
........................................................................ [ 68%]
...................F
=================================== FAILURES ===================================
____________________ test_pr_opened_with_autoapprove_label _____________________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7f6178a13e10>
mock_github_client = <MagicMock name='github_client' id='140056610767600'>
@pytest.mark.asyncio
async def test_pr_opened_with_autoapprove_label(webhook_handler, mock_github_client):
"""Test PR opened with autoapprove label triggers approval."""
payload = load_fixture("pr_opened_with_autoapprove_label")
result = await webhook_handler.handle_event("pull_request", payload)
> assert result["status"] == "success"
E AssertionError: assert 'ignored' == 'success'
E
E - success
E + ignored
tests/test_webhook_handler.py:70: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:40:07.753429Z[0m [[32m[1minfo [0m] [1mReceived pull_request event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'pull_request', 'action': 'opened'}[0m
[2m2026-05-16T19:40:07.754062Z[0m [[32m[1minfo [0m] [1mNo stampbot.toml found in octocat/hello-world, using defaults[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'org_repo': None}[0m
[2m2026-05-16T19:40:07.754921Z[0m [[32m[1minfo [0m] [1mPR #42 not eligible for auto-approval: None[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'pr_number': 42, 'pr_author': 'contributor', 'reason': None}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'level': 'info', 'timestamp': '2026-05-16T19:40:07.753429Z'}
INFO stampbot.webhook_handler:webhook_handler.py:593 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'level': 'info', 'timestamp': '2026-05-16T19:40:07.754062Z'}
INFO stampbot.webhook_handler:webhook_handler.py:254 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'pr_author': 'contributor', 'reason': None}, 'event': 'PR #42 not eligible for auto-approval: None', 'level': 'info', 'timestamp': '2026-05-16T19:40:07.754921Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_opened_with_autoapprove_label - AssertionError: assert 'ignored' == 'success'
- success
+ ignored
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 163 passed, 3 deselected in 1.07s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceUnaryOperator_Delete_Not, occurrence: 4
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -406,7 +406,7 @@
"chatops.commenter": commenter,
},
) as span:
- if not all([pr_number, repo_full_name, installation_id]):
+ if all([pr_number, repo_full_name, installation_id]):
logger.warning("Missing required fields in comment event")
add_span_attributes(span, {"chatops.result": "missing_fields"})
set_span_ok(span)........................................................................ [ 34%]
........................................................................ [ 68%]
.................................F
=================================== FAILURES ===================================
__________________________ test_issue_comment_approve __________________________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7f274954b330>
mock_github_client = <MagicMock name='github_client' id='139806711230352'>
@pytest.mark.asyncio
async def test_issue_comment_approve(webhook_handler, mock_github_client):
"""Test @stampbot approve command."""
payload = load_fixture("issue_comment_approve")
result = await webhook_handler.handle_event("issue_comment", payload)
> assert result["status"] == "success"
E AssertionError: assert 'error' == 'success'
E
E - success
E + error
tests/test_webhook_handler.py:259: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T20:05:34.002723Z[0m [[32m[1minfo [0m] [1mReceived issue_comment event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'issue_comment', 'action': 'created'}[0m
[2m2026-05-16T20:05:34.002916Z[0m [[33m[1mwarning [0m] [1mMissing required fields in comment event[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35mwarning[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'level': 'info', 'timestamp': '2026-05-16T20:05:34.002723Z'}
WARNING stampbot.webhook_handler:webhook_handler.py:410 {'event': 'Missing required fields in comment event', 'level': 'warning', 'timestamp': '2026-05-16T20:05:34.002916Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_issue_comment_approve - AssertionError: assert 'error' == 'success'
- success
+ error
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 177 passed, 3 deselected in 1.15s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceUnaryOperator_Delete_Not, occurrence: 5
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -435,7 +435,7 @@
"message": "Invalid repository configuration",
}
- if not repo_config.chatops_enabled:
+ if repo_config.chatops_enabled:
add_span_attributes(span, {"chatops.result": "disabled"})
set_span_ok(span)
return {"status": "ignored", "message": "Chatops not enabled"}........................................................................ [ 34%]
........................................................................ [ 68%]
.................................F
=================================== FAILURES ===================================
__________________________ test_issue_comment_approve __________________________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7f1d61b5f1d0>
mock_github_client = <MagicMock name='github_client' id='139764170583952'>
@pytest.mark.asyncio
async def test_issue_comment_approve(webhook_handler, mock_github_client):
"""Test @stampbot approve command."""
payload = load_fixture("issue_comment_approve")
result = await webhook_handler.handle_event("issue_comment", payload)
> assert result["status"] == "success"
E AssertionError: assert 'ignored' == 'success'
E
E - success
E + ignored
tests/test_webhook_handler.py:259: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:58:38.143668Z[0m [[32m[1minfo [0m] [1mReceived issue_comment event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'issue_comment', 'action': 'created'}[0m
[2m2026-05-16T19:58:38.144244Z[0m [[32m[1minfo [0m] [1mNo stampbot.toml found in octocat/hello-world, using defaults[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'org_repo': None}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'level': 'info', 'timestamp': '2026-05-16T19:58:38.143668Z'}
INFO stampbot.webhook_handler:webhook_handler.py:593 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'level': 'info', 'timestamp': '2026-05-16T19:58:38.144244Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_issue_comment_approve - AssertionError: assert 'ignored' == 'success'
- success
+ ignored
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 177 passed, 3 deselected in 1.13s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceUnaryOperator_Delete_Not, occurrence: 6
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -442,7 +442,7 @@
# Parse command
command_match = re.search(r"@stampbot\s+(\w+)", comment_body)
- if not command_match:
+ if command_match:
chatops_commands_total.labels(command="none", status="ignored").inc()
add_span_attributes(span, {"chatops.result": "no_command"})
set_span_ok(span)........................................................................ [ 34%]
........................................................................ [ 68%]
.................................F
=================================== FAILURES ===================================
__________________________ test_issue_comment_approve __________________________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7fa267357330>
mock_github_client = <MagicMock name='github_client' id='140335493476240'>
@pytest.mark.asyncio
async def test_issue_comment_approve(webhook_handler, mock_github_client):
"""Test @stampbot approve command."""
payload = load_fixture("issue_comment_approve")
result = await webhook_handler.handle_event("issue_comment", payload)
> assert result["status"] == "success"
E AssertionError: assert 'ignored' == 'success'
E
E - success
E + ignored
tests/test_webhook_handler.py:259: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:33:53.418851Z[0m [[32m[1minfo [0m] [1mReceived issue_comment event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'issue_comment', 'action': 'created'}[0m
[2m2026-05-16T19:33:53.419401Z[0m [[32m[1minfo [0m] [1mNo stampbot.toml found in octocat/hello-world, using defaults[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'org_repo': None}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'level': 'info', 'timestamp': '2026-05-16T19:33:53.418851Z'}
INFO stampbot.webhook_handler:webhook_handler.py:593 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'level': 'info', 'timestamp': '2026-05-16T19:33:53.419401Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_issue_comment_approve - AssertionError: assert 'ignored' == 'success'
- success
+ ignored
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 177 passed, 3 deselected in 1.11s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceUnaryOperator_Delete_Not, occurrence: 7
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -459,7 +459,7 @@
commenter,
repo_config.chatops_required_permission,
)
- if not has_permission:
+ if has_permission:
chatops_commands_total.labels(command=command, status="forbidden").inc()
add_span_attributes(
span,........................................................................ [ 34%]
........................................................................ [ 68%]
.................................F
=================================== FAILURES ===================================
__________________________ test_issue_comment_approve __________________________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7f53de045ff0>
mock_github_client = <MagicMock name='github_client' id='139998184337296'>
@pytest.mark.asyncio
async def test_issue_comment_approve(webhook_handler, mock_github_client):
"""Test @stampbot approve command."""
payload = load_fixture("issue_comment_approve")
result = await webhook_handler.handle_event("issue_comment", payload)
> assert result["status"] == "success"
E AssertionError: assert 'ignored' == 'success'
E
E - success
E + ignored
tests/test_webhook_handler.py:259: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:36:21.976184Z[0m [[32m[1minfo [0m] [1mReceived issue_comment event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'issue_comment', 'action': 'created'}[0m
[2m2026-05-16T19:36:21.976751Z[0m [[32m[1minfo [0m] [1mNo stampbot.toml found in octocat/hello-world, using defaults[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'org_repo': None}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'level': 'info', 'timestamp': '2026-05-16T19:36:21.976184Z'}
INFO stampbot.webhook_handler:webhook_handler.py:593 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'level': 'info', 'timestamp': '2026-05-16T19:36:21.976751Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_issue_comment_approve - AssertionError: assert 'ignored' == 'success'
- success
+ ignored
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 177 passed, 3 deselected in 1.13s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceUnaryOperator_Delete_Not, occurrence: 8
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -729,7 +729,7 @@
status=status,
).inc()
- if not success:
+ if success:
errors_total.labels(error_type="approval_failed").inc()
add_span_attributes(span, {"approval.result": status})........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceUnaryOperator_Delete_Not, occurrence: 9
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -778,7 +778,7 @@
add_span_attributes(span, {"dismissal.reviews_found": len(review_ids)})
- if not review_ids:
+ if review_ids:
logger.info(
"No bot approvals found on PR #%d",
pr_number,........................................................................ [ 34%]
........................................................................ [ 68%]
.........................F
=================================== FAILURES ===================================
________________________ test_pr_unlabeled_autoapprove _________________________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7fb3fcff1450>
mock_github_client = <MagicMock name='github_client' id='140411082466912'>
@pytest.mark.asyncio
async def test_pr_unlabeled_autoapprove(webhook_handler, mock_github_client):
"""Test removing autoapprove label dismisses approvals."""
payload = load_fixture("pr_unlabeled_autoapprove")
mock_github_client.find_bot_reviews.return_value = [111, 222] # Mock review IDs
result = await webhook_handler.handle_event("pull_request", payload)
assert result["status"] == "success"
assert "dismissed" in result["message"].lower()
mock_github_client.find_bot_reviews.assert_called_once()
> assert mock_github_client.dismiss_approval.call_count == 2
E AssertionError: assert 0 == 2
E + where 0 = <MagicMock name='github_client.dismiss_approval' id='140411082465904'>.call_count
E + where <MagicMock name='github_client.dismiss_approval' id='140411082465904'> = <MagicMock name='github_client' id='140411082466912'>.dismiss_approval
tests/test_webhook_handler.py:155: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:52:05.864076Z[0m [[32m[1minfo [0m] [1mReceived pull_request event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'pull_request', 'action': 'unlabeled'}[0m
[2m2026-05-16T19:52:05.864628Z[0m [[32m[1minfo [0m] [1mNo stampbot.toml found in octocat/hello-world, using defaults[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'org_repo': None}[0m
[2m2026-05-16T19:52:05.865142Z[0m [[32m[1minfo [0m] [1mApproval label autoapprove removed from PR #42[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}[0m
[2m2026-05-16T19:52:05.865567Z[0m [[32m[1minfo [0m] [1mNo bot approvals found on PR #42[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'pr_number': 42}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'pull_request', 'action': 'unlabeled'}, 'event': 'Received pull_request event', 'level': 'info', 'timestamp': '2026-05-16T19:52:05.864076Z'}
INFO stampbot.webhook_handler:webhook_handler.py:593 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'level': 'info', 'timestamp': '2026-05-16T19:52:05.864628Z'}
INFO stampbot.webhook_handler:webhook_handler.py:320 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'Approval label autoapprove removed from PR #42', 'level': 'info', 'timestamp': '2026-05-16T19:52:05.865142Z'}
INFO stampbot.webhook_handler:webhook_handler.py:782 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42}, 'event': 'No bot approvals found on PR #42', 'level': 'info', 'timestamp': '2026-05-16T19:52:05.865567Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_unlabeled_autoapprove - AssertionError: assert 0 == 2
+ where 0 = <MagicMock name='github_client.dismiss_approval' id='140411082465904'>.call_count
+ where <MagicMock name='github_client.dismiss_approval' id='140411082465904'> = <MagicMock name='github_client' id='140411082466912'>.dismiss_approval
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 169 passed, 3 deselected in 1.09s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/AddNot, occurrence: 0
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -49,7 +49,7 @@
Raises:
RuntimeError: If webhook secret is not configured.
"""
- if self._webhook_secret is None:
+ if not self._webhook_secret is None:
if not settings.webhook_secret:
raise RuntimeError(
"Webhook secret not configured. Visit /setup to create your GitHub App."........................................................................ [ 34%]
........................F
=================================== FAILURES ===================================
_______________________ test_request_with_content_length _______________________
test_client = <starlette.testclient.TestClient object at 0x7ff8fdeb3770>
def test_request_with_content_length(test_client: TestClient):
"""Test that requests with Content-Length header are tracked."""
# POST with a body will have Content-Length set automatically
> response = test_client.post(
"/webhook",
content=b'{"test": "data"}',
headers={
"Content-Type": "application/json",
"X-GitHub-Event": "ping",
"X-Hub-Signature-256": "sha256=invalid",
},
)
tests/test_main.py:69:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:546: in post
return super().post(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1144: in post
return self.request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:445: in request
return super().request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:825: in request
return self.send(request, auth=auth, follow_redirects=follow_redirects)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:914: in send
response = self._send_handling_auth(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:942: in _send_handling_auth
response = self._send_handling_redirects(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:979: in _send_handling_redirects
response = self._send_single_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1014: in _send_single_request
response = transport.handle_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:348: in handle_request
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:345: in handle_request
portal.call(self.app, scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:334: in call
return cast(T_Retval, self.start_task_soon(func, *args).result())
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:443: in result
return self.__get_result()
^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:395: in __get_result
raise self._exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:259: in _call_func
retval = await retval_or_awaitable
^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1135: in __call__
await super().__call__(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:107: in __call__
await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:186: in __call__
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:164: in __call__
await self.app(scope, receive, _send)
stampbot/main.py:205: in __call__
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:191: in __call__
with recv_stream, send_stream, collapse_excgroups():
^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/contextlib.py:162: in __exit__
self.gen.throw(value)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_utils.py:85: in collapse_excgroups
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:193: in __call__
response = await self.dispatch_func(request, call_next)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:120: in metrics_middleware
response = await call_next(request)
^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:168: in call_next
raise app_exc from app_exc.__cause__ or app_exc.__context__
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:144: in coro
await self.app(scope, receive_or_disconnect, send_no_error)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/exceptions.py:63: in __call__
await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/middleware/asyncexitstack.py:18: in __call__
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:716: in __call__
await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:736: in app
await route.handle(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:290: in handle
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:115: in app
await wrap_app_handling_exceptions(app, request)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:101: in app
response = await f(request)
^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:355: in app
raw_response = await run_endpoint_function(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:243: in run_endpoint_function
return await dependant.call(**values)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:296: in webhook
if not webhook_handler.verify_signature(body, x_hub_signature_256):
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/webhook_handler.py:74: in verify_signature
"sha256=" + hmac.new(self.webhook_secret, payload, hashlib.sha256).hexdigest()
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/hmac.py:218: in new
return HMAC(key, msg, digestmod)
^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <hmac.HMAC object at 0x7ff8fdcc5990>, key = None
msg = b'{"test": "data"}', digestmod = <built-in function openssl_sha256>
def __init__(self, key, msg=None, digestmod=''):
"""Create a new HMAC object.
key: bytes or buffer, key for the keyed hash object.
msg: bytes or buffer, Initial input for the hash or None.
digestmod: A hash name suitable for hashlib.new(). *OR*
A hashlib constructor returning a new hash object. *OR*
A module supporting PEP 247.
Required as of 3.8, despite its position after the optional
msg argument. Passing it as a keyword argument is
recommended, though not required for legacy API reasons.
"""
if not isinstance(key, (bytes, bytearray)):
> raise TypeError(f"key: expected bytes or bytearray, "
f"but got {type(key).__name__!r}")
E TypeError: key: expected bytes or bytearray, but got 'NoneType'
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/hmac.py:71: TypeError
=========================== short test summary info ============================
FAILED tests/test_main.py::test_request_with_content_length - TypeError: key: expected bytes or bytearray, but got 'NoneType'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 96 passed, 3 deselected in 1.17s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/AddNot, occurrence: 1
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -50,7 +50,7 @@
RuntimeError: If webhook secret is not configured.
"""
if self._webhook_secret is None:
- if not settings.webhook_secret:
+ if not not settings.webhook_secret:
raise RuntimeError(
"Webhook secret not configured. Visit /setup to create your GitHub App."
)........................................................................ [ 34%]
........................F
=================================== FAILURES ===================================
_______________________ test_request_with_content_length _______________________
test_client = <starlette.testclient.TestClient object at 0x7f3dfec9f770>
def test_request_with_content_length(test_client: TestClient):
"""Test that requests with Content-Length header are tracked."""
# POST with a body will have Content-Length set automatically
> response = test_client.post(
"/webhook",
content=b'{"test": "data"}',
headers={
"Content-Type": "application/json",
"X-GitHub-Event": "ping",
"X-Hub-Signature-256": "sha256=invalid",
},
)
tests/test_main.py:69:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:546: in post
return super().post(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1144: in post
return self.request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:445: in request
return super().request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:825: in request
return self.send(request, auth=auth, follow_redirects=follow_redirects)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:914: in send
response = self._send_handling_auth(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:942: in _send_handling_auth
response = self._send_handling_redirects(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:979: in _send_handling_redirects
response = self._send_single_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1014: in _send_single_request
response = transport.handle_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:348: in handle_request
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:345: in handle_request
portal.call(self.app, scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:334: in call
return cast(T_Retval, self.start_task_soon(func, *args).result())
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:443: in result
return self.__get_result()
^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:395: in __get_result
raise self._exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:259: in _call_func
retval = await retval_or_awaitable
^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1135: in __call__
await super().__call__(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:107: in __call__
await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:186: in __call__
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:164: in __call__
await self.app(scope, receive, _send)
stampbot/main.py:205: in __call__
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:191: in __call__
with recv_stream, send_stream, collapse_excgroups():
^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/contextlib.py:162: in __exit__
self.gen.throw(value)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_utils.py:85: in collapse_excgroups
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:193: in __call__
response = await self.dispatch_func(request, call_next)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:120: in metrics_middleware
response = await call_next(request)
^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:168: in call_next
raise app_exc from app_exc.__cause__ or app_exc.__context__
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:144: in coro
await self.app(scope, receive_or_disconnect, send_no_error)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/exceptions.py:63: in __call__
await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/middleware/asyncexitstack.py:18: in __call__
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:716: in __call__
await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:736: in app
await route.handle(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:290: in handle
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:115: in app
await wrap_app_handling_exceptions(app, request)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:101: in app
response = await f(request)
^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:355: in app
raw_response = await run_endpoint_function(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:243: in run_endpoint_function
return await dependant.call(**values)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:296: in webhook
if not webhook_handler.verify_signature(body, x_hub_signature_256):
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/webhook_handler.py:74: in verify_signature
"sha256=" + hmac.new(self.webhook_secret, payload, hashlib.sha256).hexdigest()
^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.webhook_handler.WebhookHandler object at 0x7f3dfeca2e40>
@property
def webhook_secret(self) -> bytes:
"""Get webhook secret, initializing if needed.
Returns:
Webhook secret as bytes.
Raises:
RuntimeError: If webhook secret is not configured.
"""
if self._webhook_secret is None:
if not not settings.webhook_secret:
> raise RuntimeError(
"Webhook secret not configured. Visit /setup to create your GitHub App."
)
E RuntimeError: Webhook secret not configured. Visit /setup to create your GitHub App.
stampbot/webhook_handler.py:54: RuntimeError
=========================== short test summary info ============================
FAILED tests/test_main.py::test_request_with_content_length - RuntimeError: Webhook secret not configured. Visit /setup to create your GitHub App.
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 96 passed, 3 deselected in 1.19s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/AddNot, occurrence: 2
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -67,7 +67,7 @@
Returns:
True if signature is valid
"""
- if not signature:
+ if not not signature:
return False
expected_signature = (........................................................................ [ 34%]
.........................F
=================================== FAILURES ===================================
________________________ test_webhook_missing_signature ________________________
test_client = <starlette.testclient.TestClient object at 0x7f64ddda05a0>
def test_webhook_missing_signature(test_client: TestClient):
"""Test webhook rejects requests without signature."""
> response = test_client.post(
"/webhook",
json={"zen": "test"},
headers={"X-GitHub-Event": "ping"},
)
tests/test_main.py:84:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:546: in post
return super().post(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1144: in post
return self.request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:445: in request
return super().request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:825: in request
return self.send(request, auth=auth, follow_redirects=follow_redirects)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:914: in send
response = self._send_handling_auth(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:942: in _send_handling_auth
response = self._send_handling_redirects(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:979: in _send_handling_redirects
response = self._send_single_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1014: in _send_single_request
response = transport.handle_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:348: in handle_request
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:345: in handle_request
portal.call(self.app, scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:334: in call
return cast(T_Retval, self.start_task_soon(func, *args).result())
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:443: in result
return self.__get_result()
^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:395: in __get_result
raise self._exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:259: in _call_func
retval = await retval_or_awaitable
^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1135: in __call__
await super().__call__(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:107: in __call__
await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:186: in __call__
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:164: in __call__
await self.app(scope, receive, _send)
stampbot/main.py:205: in __call__
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:191: in __call__
with recv_stream, send_stream, collapse_excgroups():
^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/contextlib.py:162: in __exit__
self.gen.throw(value)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_utils.py:85: in collapse_excgroups
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:193: in __call__
response = await self.dispatch_func(request, call_next)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:120: in metrics_middleware
response = await call_next(request)
^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:168: in call_next
raise app_exc from app_exc.__cause__ or app_exc.__context__
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:144: in coro
await self.app(scope, receive_or_disconnect, send_no_error)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/exceptions.py:63: in __call__
await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/middleware/asyncexitstack.py:18: in __call__
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:716: in __call__
await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:736: in app
await route.handle(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:290: in handle
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:115: in app
await wrap_app_handling_exceptions(app, request)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:101: in app
response = await f(request)
^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:355: in app
raw_response = await run_endpoint_function(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:243: in run_endpoint_function
return await dependant.call(**values)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:296: in webhook
if not webhook_handler.verify_signature(body, x_hub_signature_256):
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.webhook_handler.WebhookHandler object at 0x7f64ddf96e40>
payload = b'{"zen":"test"}', signature = None
def verify_signature(self, payload: bytes, signature: str) -> bool:
"""Verify GitHub webhook signature.
Args:
payload: Request payload
signature: X-Hub-Signature-256 header value
Returns:
True if signature is valid
"""
if not not signature:
return False
expected_signature = (
"sha256=" + hmac.new(self.webhook_secret, payload, hashlib.sha256).hexdigest()
)
> return hmac.compare_digest(expected_signature, signature)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E TypeError: unsupported operand types(s) or combination of types: 'str' and 'NoneType'
stampbot/webhook_handler.py:77: TypeError
=========================== short test summary info ============================
FAILED tests/test_main.py::test_webhook_missing_signature - TypeError: unsupported operand types(s) or combination of types: 'str' and 'NoneType'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 97 passed, 3 deselected in 1.22s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/AddNot, occurrence: 3
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -109,7 +109,7 @@
)
# Route to appropriate handler
- if event_type == "pull_request":
+ if not event_type == "pull_request":
result = await self._handle_pull_request(payload)
elif event_type == "pull_request_review_comment":
result = await self._handle_pr_comment(payload)........................................................................ [ 34%]
.............................F
=================================== FAILURES ===================================
___________________________ test_webhook_valid_ping ____________________________
test_client = <starlette.testclient.TestClient object at 0x7fd6565ba9c0>
def test_webhook_valid_ping(test_client: TestClient):
"""Test webhook accepts valid ping event."""
import hashlib
import hmac
from stampbot.webhook_handler import webhook_handler
body = json.dumps({"zen": "Design for failure."}).encode()
signature = (
"sha256="
+ hmac.new(
webhook_handler.webhook_secret,
body,
hashlib.sha256,
).hexdigest()
)
response = test_client.post(
"/webhook",
content=body,
headers={
"Content-Type": "application/json",
"X-GitHub-Event": "ping",
"X-Hub-Signature-256": signature,
},
)
assert response.status_code == 200
data = response.json()
> assert data["status"] == "ok"
E AssertionError: assert 'error' == 'ok'
E
E - ok
E + error
tests/test_main.py:177: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:46:46.897036Z[0m [[32m[1minfo [0m] [1mReceived ping event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mclient_ip[0m=[35mtestclient[0m [36mextra[0m=[35m{'event_type': 'ping', 'action': ''}[0m
[2m2026-05-16T19:46:46.897287Z[0m [[33m[1mwarning [0m] [1mMissing required fields in PR event[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35mwarning[0m [36mclient_ip[0m=[35mtestclient[0m
[2m2026-05-16T19:46:46.898096Z[0m [[32m[1minfo [0m] [1mHTTP Request: POST http://testserver/webhook "HTTP/1.1 200 OK"[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'ping', 'action': ''}, 'event': 'Received ping event', 'client_ip': 'testclient', 'level': 'info', 'timestamp': '2026-05-16T19:46:46.897036Z'}
WARNING stampbot.webhook_handler:webhook_handler.py:161 {'event': 'Missing required fields in PR event', 'client_ip': 'testclient', 'level': 'warning', 'timestamp': '2026-05-16T19:46:46.897287Z'}
INFO httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 200 OK"
=========================== short test summary info ============================
FAILED tests/test_main.py::test_webhook_valid_ping - AssertionError: assert 'error' == 'ok'
- ok
+ error
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 101 passed, 3 deselected in 0.86s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/AddNot, occurrence: 4
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -115,7 +115,7 @@
result = await self._handle_pr_comment(payload)
elif event_type == "issue_comment":
# Issue comments can be on PRs too
- if "pull_request" in payload.get("issue", {}):
+ if not "pull_request" in payload.get("issue", {}):
result = await self._handle_pr_comment(payload)
else:
result = {"status": "ignored", "message": "Not a PR comment"}........................................................................ [ 34%]
........................................................................ [ 68%]
.................................F
=================================== FAILURES ===================================
__________________________ test_issue_comment_approve __________________________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7f578ab5dff0>
mock_github_client = <MagicMock name='github_client' id='140013966569360'>
@pytest.mark.asyncio
async def test_issue_comment_approve(webhook_handler, mock_github_client):
"""Test @stampbot approve command."""
payload = load_fixture("issue_comment_approve")
result = await webhook_handler.handle_event("issue_comment", payload)
> assert result["status"] == "success"
E AssertionError: assert 'ignored' == 'success'
E
E - success
E + ignored
tests/test_webhook_handler.py:259: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:49:44.988946Z[0m [[32m[1minfo [0m] [1mReceived issue_comment event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'issue_comment', 'action': 'created'}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'level': 'info', 'timestamp': '2026-05-16T19:49:44.988946Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_issue_comment_approve - AssertionError: assert 'ignored' == 'success'
- success
+ ignored
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 177 passed, 3 deselected in 1.11s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/AddNot, occurrence: 5
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -157,7 +157,7 @@
"github.action": action or "unknown",
},
) as span:
- if not all([pr_number, repo_full_name, installation_id]):
+ if not not all([pr_number, repo_full_name, installation_id]):
logger.warning("Missing required fields in PR event")
add_span_attributes(span, {"webhook.result": "missing_fields"})
set_span_ok(span)........................................................................ [ 34%]
........................................................................ [ 68%]
...................F
=================================== FAILURES ===================================
____________________ test_pr_opened_with_autoapprove_label _____________________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7f29d6eefe10>
mock_github_client = <MagicMock name='github_client' id='139817672688368'>
@pytest.mark.asyncio
async def test_pr_opened_with_autoapprove_label(webhook_handler, mock_github_client):
"""Test PR opened with autoapprove label triggers approval."""
payload = load_fixture("pr_opened_with_autoapprove_label")
result = await webhook_handler.handle_event("pull_request", payload)
> assert result["status"] == "success"
E AssertionError: assert 'error' == 'success'
E
E - success
E + error
tests/test_webhook_handler.py:70: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:59:24.389781Z[0m [[32m[1minfo [0m] [1mReceived pull_request event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'pull_request', 'action': 'opened'}[0m
[2m2026-05-16T19:59:24.389962Z[0m [[33m[1mwarning [0m] [1mMissing required fields in PR event[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35mwarning[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'level': 'info', 'timestamp': '2026-05-16T19:59:24.389781Z'}
WARNING stampbot.webhook_handler:webhook_handler.py:161 {'event': 'Missing required fields in PR event', 'level': 'warning', 'timestamp': '2026-05-16T19:59:24.389962Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_opened_with_autoapprove_label - AssertionError: assert 'error' == 'success'
- success
+ error
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 163 passed, 3 deselected in 1.06s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/AddNot, occurrence: 6
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -172,7 +172,7 @@
owner_type,
)
- if repo_config.config_error:
+ if not repo_config.config_error:
logger.warning(
"Invalid stampbot.toml in %s: %s",
repo_full_name,........................................................................ [ 34%]
........................................................................ [ 68%]
...................F
=================================== FAILURES ===================================
____________________ test_pr_opened_with_autoapprove_label _____________________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7fcde63f3e10>
mock_github_client = <MagicMock name='github_client' id='140522304242416'>
@pytest.mark.asyncio
async def test_pr_opened_with_autoapprove_label(webhook_handler, mock_github_client):
"""Test PR opened with autoapprove label triggers approval."""
payload = load_fixture("pr_opened_with_autoapprove_label")
result = await webhook_handler.handle_event("pull_request", payload)
> assert result["status"] == "success"
E AssertionError: assert 'error' == 'success'
E
E - success
E + error
tests/test_webhook_handler.py:70: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T20:06:02.796259Z[0m [[32m[1minfo [0m] [1mReceived pull_request event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'pull_request', 'action': 'opened'}[0m
[2m2026-05-16T20:06:02.796968Z[0m [[32m[1minfo [0m] [1mNo stampbot.toml found in octocat/hello-world, using defaults[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'org_repo': None}[0m
[2m2026-05-16T20:06:02.797548Z[0m [[33m[1mwarning [0m] [1mInvalid stampbot.toml in octocat/hello-world: None[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35mwarning[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'error': None}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'level': 'info', 'timestamp': '2026-05-16T20:06:02.796259Z'}
INFO stampbot.webhook_handler:webhook_handler.py:593 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'level': 'info', 'timestamp': '2026-05-16T20:06:02.796968Z'}
WARNING stampbot.webhook_handler:webhook_handler.py:176 {'extra': {'repo': 'octocat/hello-world', 'error': None}, 'event': 'Invalid stampbot.toml in octocat/hello-world: None', 'level': 'warning', 'timestamp': '2026-05-16T20:06:02.797548Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_opened_with_autoapprove_label - AssertionError: assert 'error' == 'success'
- success
+ error
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 163 passed, 3 deselected in 1.12s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/AddNot, occurrence: 7
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -187,7 +187,7 @@
},
)
- if action == "opened":
+ if not action == "opened":
await run_in_threadpool(
github_client.create_pr_review_comment,
installation_id,........................................................................ [ 34%]
........................................................................ [ 68%]
................................................F
=================================== FAILURES ===================================
____________________ test_invalid_repo_config_posts_review _____________________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7fdc0f67f590>
mock_github_client = <MagicMock name='github_client' id='140583129099168'>
@pytest.mark.asyncio
async def test_invalid_repo_config_posts_review(webhook_handler, mock_github_client):
"""Test invalid repo config logs and posts a review comment."""
payload = load_fixture("pr_opened_with_autoapprove_label")
mock_github_client.get_repo_file.return_value = 'chatops_required_permission = "invalid"'
result = await webhook_handler.handle_event("pull_request", payload)
assert result["status"] == "error"
assert "invalid" in result["message"].lower()
> mock_github_client.create_pr_review_comment.assert_called_once()
tests/test_webhook_handler.py:518:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <MagicMock name='github_client.create_pr_review_comment' id='140583129100176'>
def assert_called_once(self):
"""assert that the mock was called only once.
"""
if not self.call_count == 1:
msg = ("Expected '%s' to have been called once. Called %s times.%s"
% (self._mock_name or 'mock',
self.call_count,
self._calls_repr()))
> raise AssertionError(msg)
E AssertionError: Expected 'create_pr_review_comment' to have been called once. Called 0 times.
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:964: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:56:55.654556Z[0m [[32m[1minfo [0m] [1mReceived pull_request event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'pull_request', 'action': 'opened'}[0m
[2m2026-05-16T19:56:55.655889Z[0m [[33m[1mwarning [0m] [1mInvalid stampbot.toml in octocat/hello-world: Invalid chatops_required_permission: invalid. Valid values: none, read, triage, write, maintain, admin[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35mwarning[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'error': 'Invalid chatops_required_permission: invalid. Valid values: none, read, triage, write, maintain, admin'}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'level': 'info', 'timestamp': '2026-05-16T19:56:55.654556Z'}
WARNING stampbot.webhook_handler:webhook_handler.py:176 {'extra': {'repo': 'octocat/hello-world', 'error': 'Invalid chatops_required_permission: invalid. Valid values: none, read, triage, write, maintain, admin'}, 'event': 'Invalid stampbot.toml in octocat/hello-world: Invalid chatops_required_permission: invalid. Valid values: none, read, triage, write, maintain, admin', 'level': 'warning', 'timestamp': '2026-05-16T19:56:55.655889Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_invalid_repo_config_posts_review - AssertionError: Expected 'create_pr_review_comment' to have been called once. Called 0 times.
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 192 passed, 3 deselected in 1.62s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/AddNot, occurrence: 8
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -206,7 +206,7 @@
"message": "Invalid repository configuration",
}
- if action == "opened":
+ if not action == "opened":
for label in repo_config.approval_labels:
label_exists = await run_in_threadpool(
github_client.repo_has_label,........................................................................ [ 34%]
........................................................................ [ 68%]
.....................F
=================================== FAILURES ===================================
__________________ test_pr_opened_missing_label_logs_warning ___________________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7f89dfe9c830>
mock_github_client = <MagicMock name='github_client' id='140230083833072'>
@pytest.mark.asyncio
async def test_pr_opened_missing_label_logs_warning(webhook_handler, mock_github_client):
"""Test missing approval label triggers label check."""
payload = load_fixture("pr_opened_with_autoapprove_label")
mock_github_client.repo_has_label.return_value = False
result = await webhook_handler.handle_event("pull_request", payload)
assert result["status"] == "success"
> assert mock_github_client.repo_has_label.call_count == 2
E AssertionError: assert 0 == 2
E + where 0 = <MagicMock name='github_client.repo_has_label' id='140230083830384'>.call_count
E + where <MagicMock name='github_client.repo_has_label' id='140230083830384'> = <MagicMock name='github_client' id='140230083833072'>.repo_has_label
tests/test_webhook_handler.py:101: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T20:05:23.103212Z[0m [[32m[1minfo [0m] [1mReceived pull_request event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'pull_request', 'action': 'opened'}[0m
[2m2026-05-16T20:05:23.103801Z[0m [[32m[1minfo [0m] [1mNo stampbot.toml found in octocat/hello-world, using defaults[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'org_repo': None}[0m
[2m2026-05-16T20:05:23.104381Z[0m [[32m[1minfo [0m] [1mPR #42 has approval label: autoapprove[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'level': 'info', 'timestamp': '2026-05-16T20:05:23.103212Z'}
INFO stampbot.webhook_handler:webhook_handler.py:593 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'level': 'info', 'timestamp': '2026-05-16T20:05:23.103801Z'}
INFO stampbot.webhook_handler:webhook_handler.py:278 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'level': 'info', 'timestamp': '2026-05-16T20:05:23.104381Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_opened_missing_label_logs_warning - AssertionError: assert 0 == 2
+ where 0 = <MagicMock name='github_client.repo_has_label' id='140230083830384'>.call_count
+ where <MagicMock name='github_client.repo_has_label' id='140230083830384'> = <MagicMock name='github_client' id='140230083833072'>.repo_has_label
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 165 passed, 3 deselected in 1.11s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/AddNot, occurrence: 9
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -214,7 +214,7 @@
repo_full_name,
label,
)
- if label_exists is False:
+ if not label_exists is False:
logger.warning(
"Approval label %s not found in %s",
label,........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.62s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/AddNot, occurrence: 10
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -223,7 +223,7 @@
)
# Check if we should approve based on labels
- if repo_config.auto_approve_on_label and action in [
+ if not repo_config.auto_approve_on_label and action in [
"opened",
"reopened",
"labeled",........................................................................ [ 34%]
........................................................................ [ 68%]
...................F
=================================== FAILURES ===================================
____________________ test_pr_opened_with_autoapprove_label _____________________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7efee6813e10>
mock_github_client = <MagicMock name='github_client' id='139633252401904'>
@pytest.mark.asyncio
async def test_pr_opened_with_autoapprove_label(webhook_handler, mock_github_client):
"""Test PR opened with autoapprove label triggers approval."""
payload = load_fixture("pr_opened_with_autoapprove_label")
result = await webhook_handler.handle_event("pull_request", payload)
> assert result["status"] == "success"
E AssertionError: assert 'ignored' == 'success'
E
E - success
E + ignored
tests/test_webhook_handler.py:70: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:40:54.724583Z[0m [[32m[1minfo [0m] [1mReceived pull_request event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'pull_request', 'action': 'opened'}[0m
[2m2026-05-16T19:40:54.725256Z[0m [[32m[1minfo [0m] [1mNo stampbot.toml found in octocat/hello-world, using defaults[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'org_repo': None}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'level': 'info', 'timestamp': '2026-05-16T19:40:54.724583Z'}
INFO stampbot.webhook_handler:webhook_handler.py:593 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'level': 'info', 'timestamp': '2026-05-16T19:40:54.725256Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_opened_with_autoapprove_label - AssertionError: assert 'ignored' == 'success'
- success
+ ignored
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 163 passed, 3 deselected in 1.08s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/AddNot, occurrence: 11
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -234,7 +234,7 @@
pr_author = pr.get("user", {}).get("login", "")
for label in labels:
- if label in repo_config.approval_labels:
+ if not label in repo_config.approval_labels:
# Check if team membership verification is needed
author_team_slugs: list[str] | None = None
if repo_config.needs_team_check(pr_author) and owner_login:........................................................................ [ 34%]
........................................................................ [ 68%]
...................F
=================================== FAILURES ===================================
____________________ test_pr_opened_with_autoapprove_label _____________________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7fe62bbdfe10>
mock_github_client = <MagicMock name='github_client' id='140626549392112'>
@pytest.mark.asyncio
async def test_pr_opened_with_autoapprove_label(webhook_handler, mock_github_client):
"""Test PR opened with autoapprove label triggers approval."""
payload = load_fixture("pr_opened_with_autoapprove_label")
result = await webhook_handler.handle_event("pull_request", payload)
> assert result["status"] == "success"
E AssertionError: assert 'ignored' == 'success'
E
E - success
E + ignored
tests/test_webhook_handler.py:70: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:38:15.075366Z[0m [[32m[1minfo [0m] [1mReceived pull_request event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'pull_request', 'action': 'opened'}[0m
[2m2026-05-16T19:38:15.076034Z[0m [[32m[1minfo [0m] [1mNo stampbot.toml found in octocat/hello-world, using defaults[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'org_repo': None}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'level': 'info', 'timestamp': '2026-05-16T19:38:15.075366Z'}
INFO stampbot.webhook_handler:webhook_handler.py:593 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'level': 'info', 'timestamp': '2026-05-16T19:38:15.076034Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_opened_with_autoapprove_label - AssertionError: assert 'ignored' == 'success'
- success
+ ignored
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 163 passed, 3 deselected in 1.07s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/AddNot, occurrence: 12
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -237,7 +237,7 @@
if label in repo_config.approval_labels:
# Check if team membership verification is needed
author_team_slugs: list[str] | None = None
- if repo_config.needs_team_check(pr_author) and owner_login:
+ if not repo_config.needs_team_check(pr_author) and owner_login:
author_team_slugs = await run_in_threadpool(
github_client.get_user_team_slugs,
installation_id,........................................................................ [ 34%]
........................................................................ [ 68%]
................................................................F
=================================== FAILURES ===================================
________________ test_pr_with_allowed_teams_triggers_team_check ________________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7ff99a6831d0>
mock_github_client = <MagicMock name='github_client' id='140710008897952'>
@pytest.mark.asyncio
async def test_pr_with_allowed_teams_triggers_team_check(webhook_handler, mock_github_client):
"""Test PR with allowed_teams configured triggers team membership check."""
payload = load_fixture("pr_opened_with_autoapprove_label")
# Config with allowed_teams - user not in allowed_users so needs team check
mock_github_client.get_repo_file.return_value = """
allowed_teams = ["acme/release-team"]
"""
# User is in the allowed team
mock_github_client.get_user_team_slugs.return_value = ["release-team"]
result = await webhook_handler.handle_event("pull_request", payload)
> assert result["status"] == "success"
E AssertionError: assert 'ignored' == 'success'
E
E - success
E + ignored
tests/test_webhook_handler.py:811: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:42:32.944515Z[0m [[32m[1minfo [0m] [1mReceived pull_request event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'pull_request', 'action': 'opened'}[0m
[2m2026-05-16T19:42:32.945779Z[0m [[32m[1minfo [0m] [1mPR #42 not eligible for auto-approval: PR author not a member of any allowed team[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'pr_number': 42, 'pr_author': 'contributor', 'reason': 'PR author not a member of any allowed team'}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'level': 'info', 'timestamp': '2026-05-16T19:42:32.944515Z'}
INFO stampbot.webhook_handler:webhook_handler.py:254 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'pr_author': 'contributor', 'reason': 'PR author not a member of any allowed team'}, 'event': 'PR #42 not eligible for auto-approval: PR author not a member of any allowed team', 'level': 'info', 'timestamp': '2026-05-16T19:42:32.945779Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_with_allowed_teams_triggers_team_check - AssertionError: assert 'ignored' == 'success'
- success
+ ignored
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 208 passed, 3 deselected in 1.64s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/AddNot, occurrence: 13
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -250,7 +250,7 @@
is_eligible, reason = repo_config.is_pr_eligible(
labels, pr_title, pr_author, author_team_slugs
)
- if not is_eligible:
+ if not not is_eligible:
logger.info(
"PR #%d not eligible for auto-approval: %s",
pr_number,........................................................................ [ 34%]
........................................................................ [ 68%]
...................F
=================================== FAILURES ===================================
____________________ test_pr_opened_with_autoapprove_label _____________________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7f0cdd513e10>
mock_github_client = <MagicMock name='github_client' id='139693227803376'>
@pytest.mark.asyncio
async def test_pr_opened_with_autoapprove_label(webhook_handler, mock_github_client):
"""Test PR opened with autoapprove label triggers approval."""
payload = load_fixture("pr_opened_with_autoapprove_label")
result = await webhook_handler.handle_event("pull_request", payload)
> assert result["status"] == "success"
E AssertionError: assert 'ignored' == 'success'
E
E - success
E + ignored
tests/test_webhook_handler.py:70: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:35:49.730045Z[0m [[32m[1minfo [0m] [1mReceived pull_request event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'pull_request', 'action': 'opened'}[0m
[2m2026-05-16T19:35:49.730702Z[0m [[32m[1minfo [0m] [1mNo stampbot.toml found in octocat/hello-world, using defaults[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'org_repo': None}[0m
[2m2026-05-16T19:35:49.731581Z[0m [[32m[1minfo [0m] [1mPR #42 not eligible for auto-approval: None[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'pr_number': 42, 'pr_author': 'contributor', 'reason': None}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'level': 'info', 'timestamp': '2026-05-16T19:35:49.730045Z'}
INFO stampbot.webhook_handler:webhook_handler.py:593 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'level': 'info', 'timestamp': '2026-05-16T19:35:49.730702Z'}
INFO stampbot.webhook_handler:webhook_handler.py:254 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'pr_author': 'contributor', 'reason': None}, 'event': 'PR #42 not eligible for auto-approval: None', 'level': 'info', 'timestamp': '2026-05-16T19:35:49.731581Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_opened_with_autoapprove_label - AssertionError: assert 'ignored' == 'success'
- success
+ ignored
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 163 passed, 3 deselected in 1.07s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/AddNot, occurrence: 14
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -298,7 +298,7 @@
add_span_attributes(
span,
{
- "webhook.result": "approved" if success else "approval_failed",
+ "webhook.result": "approved" if not success else "approval_failed",
"webhook.trigger_label": label,
},
)........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.64s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/AddNot, occurrence: 15
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -305,7 +305,7 @@
set_span_ok(span)
return {
- "status": "success" if success else "error",
+ "status": "success" if not success else "error",
"message": (
f"PR approved via label: {label}"
if success........................................................................ [ 34%]
........................................................................ [ 68%]
...................F
=================================== FAILURES ===================================
____________________ test_pr_opened_with_autoapprove_label _____________________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7f5e8151fe10>
mock_github_client = <MagicMock name='github_client' id='140043871683312'>
@pytest.mark.asyncio
async def test_pr_opened_with_autoapprove_label(webhook_handler, mock_github_client):
"""Test PR opened with autoapprove label triggers approval."""
payload = load_fixture("pr_opened_with_autoapprove_label")
result = await webhook_handler.handle_event("pull_request", payload)
> assert result["status"] == "success"
E AssertionError: assert 'error' == 'success'
E
E - success
E + error
tests/test_webhook_handler.py:70: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:58:04.078368Z[0m [[32m[1minfo [0m] [1mReceived pull_request event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'pull_request', 'action': 'opened'}[0m
[2m2026-05-16T19:58:04.079017Z[0m [[32m[1minfo [0m] [1mNo stampbot.toml found in octocat/hello-world, using defaults[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'org_repo': None}[0m
[2m2026-05-16T19:58:04.079887Z[0m [[32m[1minfo [0m] [1mPR #42 has approval label: autoapprove[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'level': 'info', 'timestamp': '2026-05-16T19:58:04.078368Z'}
INFO stampbot.webhook_handler:webhook_handler.py:593 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'level': 'info', 'timestamp': '2026-05-16T19:58:04.079017Z'}
INFO stampbot.webhook_handler:webhook_handler.py:278 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'level': 'info', 'timestamp': '2026-05-16T19:58:04.079887Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_opened_with_autoapprove_label - AssertionError: assert 'error' == 'success'
- success
+ error
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 163 passed, 3 deselected in 1.06s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/AddNot, occurrence: 16
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -308,7 +308,7 @@
"status": "success" if success else "error",
"message": (
f"PR approved via label: {label}"
- if success
+ if not success
else "Failed to approve PR"
),
}........................................................................ [ 34%]
........................................................................ [ 68%]
...................F
=================================== FAILURES ===================================
____________________ test_pr_opened_with_autoapprove_label _____________________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7ff3da53be10>
mock_github_client = <MagicMock name='github_client' id='140685315080944'>
@pytest.mark.asyncio
async def test_pr_opened_with_autoapprove_label(webhook_handler, mock_github_client):
"""Test PR opened with autoapprove label triggers approval."""
payload = load_fixture("pr_opened_with_autoapprove_label")
result = await webhook_handler.handle_event("pull_request", payload)
assert result["status"] == "success"
> assert "approved" in result["message"].lower()
E AssertionError: assert 'approved' in 'failed to approve pr'
E + where 'failed to approve pr' = <built-in method lower of str object at 0x7ff3da16dd70>()
E + where <built-in method lower of str object at 0x7ff3da16dd70> = 'Failed to approve PR'.lower
tests/test_webhook_handler.py:71: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:25:35.143102Z[0m [[32m[1minfo [0m] [1mReceived pull_request event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'pull_request', 'action': 'opened'}[0m
[2m2026-05-16T19:25:35.143796Z[0m [[32m[1minfo [0m] [1mNo stampbot.toml found in octocat/hello-world, using defaults[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'org_repo': None}[0m
[2m2026-05-16T19:25:35.144698Z[0m [[32m[1minfo [0m] [1mPR #42 has approval label: autoapprove[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'level': 'info', 'timestamp': '2026-05-16T19:25:35.143102Z'}
INFO stampbot.webhook_handler:webhook_handler.py:593 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'level': 'info', 'timestamp': '2026-05-16T19:25:35.143796Z'}
INFO stampbot.webhook_handler:webhook_handler.py:278 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'level': 'info', 'timestamp': '2026-05-16T19:25:35.144698Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_opened_with_autoapprove_label - AssertionError: assert 'approved' in 'failed to approve pr'
+ where 'failed to approve pr' = <built-in method lower of str object at 0x7ff3da16dd70>()
+ where <built-in method lower of str object at 0x7ff3da16dd70> = 'Failed to approve PR'.lower
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 163 passed, 3 deselected in 1.08s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/AddNot, occurrence: 17
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -314,7 +314,7 @@
}
# Check if we should remove approval when label is removed
- if repo_config.auto_approve_on_label and action == "unlabeled":
+ if not repo_config.auto_approve_on_label and action == "unlabeled":
removed_label = payload.get("label", {}).get("name")
if removed_label in repo_config.approval_labels:
logger.info(........................................................................ [ 34%]
........................................................................ [ 68%]
.........................F
=================================== FAILURES ===================================
________________________ test_pr_unlabeled_autoapprove _________________________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7f247f3f1550>
mock_github_client = <MagicMock name='github_client' id='139794792457824'>
@pytest.mark.asyncio
async def test_pr_unlabeled_autoapprove(webhook_handler, mock_github_client):
"""Test removing autoapprove label dismisses approvals."""
payload = load_fixture("pr_unlabeled_autoapprove")
mock_github_client.find_bot_reviews.return_value = [111, 222] # Mock review IDs
result = await webhook_handler.handle_event("pull_request", payload)
> assert result["status"] == "success"
E AssertionError: assert 'ignored' == 'success'
E
E - success
E + ignored
tests/test_webhook_handler.py:152: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:47:19.657499Z[0m [[32m[1minfo [0m] [1mReceived pull_request event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'pull_request', 'action': 'unlabeled'}[0m
[2m2026-05-16T19:47:19.658084Z[0m [[32m[1minfo [0m] [1mNo stampbot.toml found in octocat/hello-world, using defaults[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'org_repo': None}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'pull_request', 'action': 'unlabeled'}, 'event': 'Received pull_request event', 'level': 'info', 'timestamp': '2026-05-16T19:47:19.657499Z'}
INFO stampbot.webhook_handler:webhook_handler.py:593 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'level': 'info', 'timestamp': '2026-05-16T19:47:19.658084Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_unlabeled_autoapprove - AssertionError: assert 'ignored' == 'success'
- success
+ ignored
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 169 passed, 3 deselected in 1.13s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/AddNot, occurrence: 18
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -316,7 +316,7 @@
# Check if we should remove approval when label is removed
if repo_config.auto_approve_on_label and action == "unlabeled":
removed_label = payload.get("label", {}).get("name")
- if removed_label in repo_config.approval_labels:
+ if not removed_label in repo_config.approval_labels:
logger.info(
"Approval label %s removed from PR #%d",
removed_label,........................................................................ [ 34%]
........................................................................ [ 68%]
.........................F
=================================== FAILURES ===================================
________________________ test_pr_unlabeled_autoapprove _________________________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7f3b74be1550>
mock_github_client = <MagicMock name='github_client' id='139893400479328'>
@pytest.mark.asyncio
async def test_pr_unlabeled_autoapprove(webhook_handler, mock_github_client):
"""Test removing autoapprove label dismisses approvals."""
payload = load_fixture("pr_unlabeled_autoapprove")
mock_github_client.find_bot_reviews.return_value = [111, 222] # Mock review IDs
result = await webhook_handler.handle_event("pull_request", payload)
> assert result["status"] == "success"
E AssertionError: assert 'ignored' == 'success'
E
E - success
E + ignored
tests/test_webhook_handler.py:152: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:55:31.671289Z[0m [[32m[1minfo [0m] [1mReceived pull_request event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'pull_request', 'action': 'unlabeled'}[0m
[2m2026-05-16T19:55:31.671828Z[0m [[32m[1minfo [0m] [1mNo stampbot.toml found in octocat/hello-world, using defaults[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'org_repo': None}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'pull_request', 'action': 'unlabeled'}, 'event': 'Received pull_request event', 'level': 'info', 'timestamp': '2026-05-16T19:55:31.671289Z'}
INFO stampbot.webhook_handler:webhook_handler.py:593 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'level': 'info', 'timestamp': '2026-05-16T19:55:31.671828Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_unlabeled_autoapprove - AssertionError: assert 'ignored' == 'success'
- success
+ ignored
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 169 passed, 3 deselected in 1.09s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/AddNot, occurrence: 19
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -340,7 +340,7 @@
add_span_attributes(
span,
{
- "webhook.result": "dismissed" if success else "dismiss_failed",
+ "webhook.result": "dismissed" if not success else "dismiss_failed",
"webhook.removed_label": removed_label,
},
)........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.66s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/AddNot, occurrence: 20
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -347,7 +347,7 @@
set_span_ok(span)
return {
- "status": "success" if success else "error",
+ "status": "success" if not success else "error",
"message": (
"Approvals dismissed" if success else "Failed to dismiss approvals"
),........................................................................ [ 34%]
........................................................................ [ 68%]
.........................F
=================================== FAILURES ===================================
________________________ test_pr_unlabeled_autoapprove _________________________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7f9890cf1450>
mock_github_client = <MagicMock name='github_client' id='140293306164832'>
@pytest.mark.asyncio
async def test_pr_unlabeled_autoapprove(webhook_handler, mock_github_client):
"""Test removing autoapprove label dismisses approvals."""
payload = load_fixture("pr_unlabeled_autoapprove")
mock_github_client.find_bot_reviews.return_value = [111, 222] # Mock review IDs
result = await webhook_handler.handle_event("pull_request", payload)
> assert result["status"] == "success"
E AssertionError: assert 'error' == 'success'
E
E - success
E + error
tests/test_webhook_handler.py:152: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:42:35.139503Z[0m [[32m[1minfo [0m] [1mReceived pull_request event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'pull_request', 'action': 'unlabeled'}[0m
[2m2026-05-16T19:42:35.140035Z[0m [[32m[1minfo [0m] [1mNo stampbot.toml found in octocat/hello-world, using defaults[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'org_repo': None}[0m
[2m2026-05-16T19:42:35.140570Z[0m [[32m[1minfo [0m] [1mApproval label autoapprove removed from PR #42[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'pull_request', 'action': 'unlabeled'}, 'event': 'Received pull_request event', 'level': 'info', 'timestamp': '2026-05-16T19:42:35.139503Z'}
INFO stampbot.webhook_handler:webhook_handler.py:593 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'level': 'info', 'timestamp': '2026-05-16T19:42:35.140035Z'}
INFO stampbot.webhook_handler:webhook_handler.py:320 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'Approval label autoapprove removed from PR #42', 'level': 'info', 'timestamp': '2026-05-16T19:42:35.140570Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_unlabeled_autoapprove - AssertionError: assert 'error' == 'success'
- success
+ error
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 169 passed, 3 deselected in 1.10s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/AddNot, occurrence: 21
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -349,7 +349,7 @@
return {
"status": "success" if success else "error",
"message": (
- "Approvals dismissed" if success else "Failed to dismiss approvals"
+ "Approvals dismissed" if not success else "Failed to dismiss approvals"
),
}
........................................................................ [ 34%]
........................................................................ [ 68%]
.........................F
=================================== FAILURES ===================================
________________________ test_pr_unlabeled_autoapprove _________________________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7f581dbc9450>
mock_github_client = <MagicMock name='github_client' id='140016501974624'>
@pytest.mark.asyncio
async def test_pr_unlabeled_autoapprove(webhook_handler, mock_github_client):
"""Test removing autoapprove label dismisses approvals."""
payload = load_fixture("pr_unlabeled_autoapprove")
mock_github_client.find_bot_reviews.return_value = [111, 222] # Mock review IDs
result = await webhook_handler.handle_event("pull_request", payload)
assert result["status"] == "success"
> assert "dismissed" in result["message"].lower()
E AssertionError: assert 'dismissed' in 'failed to dismiss approvals'
E + where 'failed to dismiss approvals' = <built-in method lower of str object at 0x7f581d9a5160>()
E + where <built-in method lower of str object at 0x7f581d9a5160> = 'Failed to dismiss approvals'.lower
tests/test_webhook_handler.py:153: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:23:24.586170Z[0m [[32m[1minfo [0m] [1mReceived pull_request event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'pull_request', 'action': 'unlabeled'}[0m
[2m2026-05-16T19:23:24.586723Z[0m [[32m[1minfo [0m] [1mNo stampbot.toml found in octocat/hello-world, using defaults[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'org_repo': None}[0m
[2m2026-05-16T19:23:24.587248Z[0m [[32m[1minfo [0m] [1mApproval label autoapprove removed from PR #42[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'pull_request', 'action': 'unlabeled'}, 'event': 'Received pull_request event', 'level': 'info', 'timestamp': '2026-05-16T19:23:24.586170Z'}
INFO stampbot.webhook_handler:webhook_handler.py:593 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'level': 'info', 'timestamp': '2026-05-16T19:23:24.586723Z'}
INFO stampbot.webhook_handler:webhook_handler.py:320 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'Approval label autoapprove removed from PR #42', 'level': 'info', 'timestamp': '2026-05-16T19:23:24.587248Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_unlabeled_autoapprove - AssertionError: assert 'dismissed' in 'failed to dismiss approvals'
+ where 'failed to dismiss approvals' = <built-in method lower of str object at 0x7f581d9a5160>()
+ where <built-in method lower of str object at 0x7f581d9a5160> = 'Failed to dismiss approvals'.lower
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 169 passed, 3 deselected in 1.09s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/AddNot, occurrence: 22
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -370,7 +370,7 @@
comment_body = comment.get("body", "")
# Security: limit comment length to prevent DoS
- if len(comment_body) > MAX_COMMENT_LENGTH:
+ if not len(comment_body) > MAX_COMMENT_LENGTH:
return {"status": "ignored", "message": "Comment too long"}
comment_body = comment_body.lower().strip()........................................................................ [ 34%]
........................................................................ [ 68%]
.................................F
=================================== FAILURES ===================================
__________________________ test_issue_comment_approve __________________________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7f8bfbf5f1d0>
mock_github_client = <MagicMock name='github_client' id='140239204904848'>
@pytest.mark.asyncio
async def test_issue_comment_approve(webhook_handler, mock_github_client):
"""Test @stampbot approve command."""
payload = load_fixture("issue_comment_approve")
result = await webhook_handler.handle_event("issue_comment", payload)
> assert result["status"] == "success"
E AssertionError: assert 'ignored' == 'success'
E
E - success
E + ignored
tests/test_webhook_handler.py:259: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:30:47.220730Z[0m [[32m[1minfo [0m] [1mReceived issue_comment event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'issue_comment', 'action': 'created'}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'level': 'info', 'timestamp': '2026-05-16T19:30:47.220730Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_issue_comment_approve - AssertionError: assert 'ignored' == 'success'
- success
+ ignored
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 177 passed, 3 deselected in 1.11s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/AddNot, occurrence: 23
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -376,7 +376,7 @@
comment_body = comment_body.lower().strip()
# Check if bot is mentioned
- if "@stampbot" not in comment_body:
+ if not "@stampbot" not in comment_body:
return {"status": "ignored", "message": "Bot not mentioned"}
# Extract PR info........................................................................ [ 34%]
........................................................................ [ 68%]
.................................F
=================================== FAILURES ===================================
__________________________ test_issue_comment_approve __________________________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7f28ec75b1d0>
mock_github_client = <MagicMock name='github_client' id='139813743046544'>
@pytest.mark.asyncio
async def test_issue_comment_approve(webhook_handler, mock_github_client):
"""Test @stampbot approve command."""
payload = load_fixture("issue_comment_approve")
result = await webhook_handler.handle_event("issue_comment", payload)
> assert result["status"] == "success"
E AssertionError: assert 'ignored' == 'success'
E
E - success
E + ignored
tests/test_webhook_handler.py:259: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:49:26.932963Z[0m [[32m[1minfo [0m] [1mReceived issue_comment event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'issue_comment', 'action': 'created'}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'level': 'info', 'timestamp': '2026-05-16T19:49:26.932963Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_issue_comment_approve - AssertionError: assert 'ignored' == 'success'
- success
+ ignored
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 177 passed, 3 deselected in 1.11s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/AddNot, occurrence: 24
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -380,7 +380,7 @@
return {"status": "ignored", "message": "Bot not mentioned"}
# Extract PR info
- if "pull_request" in payload.get("issue", {}):
+ if not "pull_request" in payload.get("issue", {}):
pr_url = payload["issue"]["pull_request"]["url"]
# Extract PR number from URL
pr_number = int(pr_url.split("/")[-1])........................................................................ [ 34%]
........................................................................ [ 68%]
.................................F
=================================== FAILURES ===================================
__________________________ test_issue_comment_approve __________________________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7fbfb105dff0>
mock_github_client = <MagicMock name='github_client' id='140461285928848'>
@pytest.mark.asyncio
async def test_issue_comment_approve(webhook_handler, mock_github_client):
"""Test @stampbot approve command."""
payload = load_fixture("issue_comment_approve")
result = await webhook_handler.handle_event("issue_comment", payload)
> assert result["status"] == "success"
E AssertionError: assert 'error' == 'success'
E
E - success
E + error
tests/test_webhook_handler.py:259: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T20:04:05.210541Z[0m [[32m[1minfo [0m] [1mReceived issue_comment event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'issue_comment', 'action': 'created'}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'level': 'info', 'timestamp': '2026-05-16T20:04:05.210541Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_issue_comment_approve - AssertionError: assert 'error' == 'success'
- success
+ error
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 177 passed, 3 deselected in 1.13s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/AddNot, occurrence: 25
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -406,7 +406,7 @@
"chatops.commenter": commenter,
},
) as span:
- if not all([pr_number, repo_full_name, installation_id]):
+ if not not all([pr_number, repo_full_name, installation_id]):
logger.warning("Missing required fields in comment event")
add_span_attributes(span, {"chatops.result": "missing_fields"})
set_span_ok(span)........................................................................ [ 34%]
........................................................................ [ 68%]
.................................F
=================================== FAILURES ===================================
__________________________ test_issue_comment_approve __________________________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7fd8a0957330>
mock_github_client = <MagicMock name='github_client' id='140568384302992'>
@pytest.mark.asyncio
async def test_issue_comment_approve(webhook_handler, mock_github_client):
"""Test @stampbot approve command."""
payload = load_fixture("issue_comment_approve")
result = await webhook_handler.handle_event("issue_comment", payload)
> assert result["status"] == "success"
E AssertionError: assert 'error' == 'success'
E
E - success
E + error
tests/test_webhook_handler.py:259: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:36:41.553884Z[0m [[32m[1minfo [0m] [1mReceived issue_comment event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'issue_comment', 'action': 'created'}[0m
[2m2026-05-16T19:36:41.554072Z[0m [[33m[1mwarning [0m] [1mMissing required fields in comment event[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35mwarning[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'level': 'info', 'timestamp': '2026-05-16T19:36:41.553884Z'}
WARNING stampbot.webhook_handler:webhook_handler.py:410 {'event': 'Missing required fields in comment event', 'level': 'warning', 'timestamp': '2026-05-16T19:36:41.554072Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_issue_comment_approve - AssertionError: assert 'error' == 'success'
- success
+ error
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 177 passed, 3 deselected in 1.12s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/AddNot, occurrence: 26
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -421,7 +421,7 @@
owner_type,
)
- if repo_config.config_error:
+ if not repo_config.config_error:
add_span_attributes(
span,
{........................................................................ [ 34%]
........................................................................ [ 68%]
.................................F
=================================== FAILURES ===================================
__________________________ test_issue_comment_approve __________________________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7fa57615b1d0>
mock_github_client = <MagicMock name='github_client' id='140348627955600'>
@pytest.mark.asyncio
async def test_issue_comment_approve(webhook_handler, mock_github_client):
"""Test @stampbot approve command."""
payload = load_fixture("issue_comment_approve")
result = await webhook_handler.handle_event("issue_comment", payload)
> assert result["status"] == "success"
E AssertionError: assert 'error' == 'success'
E
E - success
E + error
tests/test_webhook_handler.py:259: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:31:17.065485Z[0m [[32m[1minfo [0m] [1mReceived issue_comment event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'issue_comment', 'action': 'created'}[0m
[2m2026-05-16T19:31:17.066015Z[0m [[32m[1minfo [0m] [1mNo stampbot.toml found in octocat/hello-world, using defaults[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'org_repo': None}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'level': 'info', 'timestamp': '2026-05-16T19:31:17.065485Z'}
INFO stampbot.webhook_handler:webhook_handler.py:593 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'level': 'info', 'timestamp': '2026-05-16T19:31:17.066015Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_issue_comment_approve - AssertionError: assert 'error' == 'success'
- success
+ error
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 177 passed, 3 deselected in 1.11s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/AddNot, occurrence: 27
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -435,7 +435,7 @@
"message": "Invalid repository configuration",
}
- if not repo_config.chatops_enabled:
+ if not not repo_config.chatops_enabled:
add_span_attributes(span, {"chatops.result": "disabled"})
set_span_ok(span)
return {"status": "ignored", "message": "Chatops not enabled"}........................................................................ [ 34%]
........................................................................ [ 68%]
.................................F
=================================== FAILURES ===================================
__________________________ test_issue_comment_approve __________________________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7f39ff151ff0>
mock_github_client = <MagicMock name='github_client' id='139887069933456'>
@pytest.mark.asyncio
async def test_issue_comment_approve(webhook_handler, mock_github_client):
"""Test @stampbot approve command."""
payload = load_fixture("issue_comment_approve")
result = await webhook_handler.handle_event("issue_comment", payload)
> assert result["status"] == "success"
E AssertionError: assert 'ignored' == 'success'
E
E - success
E + ignored
tests/test_webhook_handler.py:259: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:46:37.239825Z[0m [[32m[1minfo [0m] [1mReceived issue_comment event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'issue_comment', 'action': 'created'}[0m
[2m2026-05-16T19:46:37.240379Z[0m [[32m[1minfo [0m] [1mNo stampbot.toml found in octocat/hello-world, using defaults[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'org_repo': None}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'level': 'info', 'timestamp': '2026-05-16T19:46:37.239825Z'}
INFO stampbot.webhook_handler:webhook_handler.py:593 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'level': 'info', 'timestamp': '2026-05-16T19:46:37.240379Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_issue_comment_approve - AssertionError: assert 'ignored' == 'success'
- success
+ ignored
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 177 passed, 3 deselected in 1.14s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/AddNot, occurrence: 28
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -442,7 +442,7 @@
# Parse command
command_match = re.search(r"@stampbot\s+(\w+)", comment_body)
- if not command_match:
+ if not not command_match:
chatops_commands_total.labels(command="none", status="ignored").inc()
add_span_attributes(span, {"chatops.result": "no_command"})
set_span_ok(span)........................................................................ [ 34%]
........................................................................ [ 68%]
.................................F
=================================== FAILURES ===================================
__________________________ test_issue_comment_approve __________________________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7f71646571d0>
mock_github_client = <MagicMock name='github_client' id='140124992892816'>
@pytest.mark.asyncio
async def test_issue_comment_approve(webhook_handler, mock_github_client):
"""Test @stampbot approve command."""
payload = load_fixture("issue_comment_approve")
result = await webhook_handler.handle_event("issue_comment", payload)
> assert result["status"] == "success"
E AssertionError: assert 'ignored' == 'success'
E
E - success
E + ignored
tests/test_webhook_handler.py:259: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:48:49.972224Z[0m [[32m[1minfo [0m] [1mReceived issue_comment event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'issue_comment', 'action': 'created'}[0m
[2m2026-05-16T19:48:49.972760Z[0m [[32m[1minfo [0m] [1mNo stampbot.toml found in octocat/hello-world, using defaults[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'org_repo': None}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'level': 'info', 'timestamp': '2026-05-16T19:48:49.972224Z'}
INFO stampbot.webhook_handler:webhook_handler.py:593 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'level': 'info', 'timestamp': '2026-05-16T19:48:49.972760Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_issue_comment_approve - AssertionError: assert 'ignored' == 'success'
- success
+ ignored
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 177 passed, 3 deselected in 1.12s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/AddNot, occurrence: 29
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -451,7 +451,7 @@
command = command_match.group(1).lower()
add_span_attributes(span, {"chatops.command": command})
- if command in repo_config.approve_commands + repo_config.unapprove_commands:
+ if not command in repo_config.approve_commands + repo_config.unapprove_commands:
has_permission = await run_in_threadpool(
github_client.user_has_permission,
installation_id,........................................................................ [ 34%]
........................................................................ [ 68%]
.................................F
=================================== FAILURES ===================================
__________________________ test_issue_comment_approve __________________________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7f72ea5571d0>
mock_github_client = <MagicMock name='github_client' id='140131534958480'>
@pytest.mark.asyncio
async def test_issue_comment_approve(webhook_handler, mock_github_client):
"""Test @stampbot approve command."""
payload = load_fixture("issue_comment_approve")
result = await webhook_handler.handle_event("issue_comment", payload)
assert result["status"] == "success"
assert "approved" in result["message"].lower()
mock_github_client.approve_pr.assert_called_once()
> mock_github_client.user_has_permission.assert_called_once()
tests/test_webhook_handler.py:262:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <MagicMock name='github_client.user_has_permission' id='140131534960832'>
def assert_called_once(self):
"""assert that the mock was called only once.
"""
if not self.call_count == 1:
msg = ("Expected '%s' to have been called once. Called %s times.%s"
% (self._mock_name or 'mock',
self.call_count,
self._calls_repr()))
> raise AssertionError(msg)
E AssertionError: Expected 'user_has_permission' to have been called once. Called 0 times.
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:964: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:48:21.720854Z[0m [[32m[1minfo [0m] [1mReceived issue_comment event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'issue_comment', 'action': 'created'}[0m
[2m2026-05-16T19:48:21.721447Z[0m [[32m[1minfo [0m] [1mNo stampbot.toml found in octocat/hello-world, using defaults[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'org_repo': None}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'level': 'info', 'timestamp': '2026-05-16T19:48:21.720854Z'}
INFO stampbot.webhook_handler:webhook_handler.py:593 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'level': 'info', 'timestamp': '2026-05-16T19:48:21.721447Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_issue_comment_approve - AssertionError: Expected 'user_has_permission' to have been called once. Called 0 times.
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 177 passed, 3 deselected in 1.16s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/AddNot, occurrence: 30
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -459,7 +459,7 @@
commenter,
repo_config.chatops_required_permission,
)
- if not has_permission:
+ if not not has_permission:
chatops_commands_total.labels(command=command, status="forbidden").inc()
add_span_attributes(
span,........................................................................ [ 34%]
........................................................................ [ 68%]
.................................F
=================================== FAILURES ===================================
__________________________ test_issue_comment_approve __________________________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7fb553459ff0>
mock_github_client = <MagicMock name='github_client' id='140416763375504'>
@pytest.mark.asyncio
async def test_issue_comment_approve(webhook_handler, mock_github_client):
"""Test @stampbot approve command."""
payload = load_fixture("issue_comment_approve")
result = await webhook_handler.handle_event("issue_comment", payload)
> assert result["status"] == "success"
E AssertionError: assert 'ignored' == 'success'
E
E - success
E + ignored
tests/test_webhook_handler.py:259: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:31:08.105706Z[0m [[32m[1minfo [0m] [1mReceived issue_comment event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'issue_comment', 'action': 'created'}[0m
[2m2026-05-16T19:31:08.106254Z[0m [[32m[1minfo [0m] [1mNo stampbot.toml found in octocat/hello-world, using defaults[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'org_repo': None}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'level': 'info', 'timestamp': '2026-05-16T19:31:08.105706Z'}
INFO stampbot.webhook_handler:webhook_handler.py:593 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'level': 'info', 'timestamp': '2026-05-16T19:31:08.106254Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_issue_comment_approve - AssertionError: assert 'ignored' == 'success'
- success
+ ignored
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 177 passed, 3 deselected in 1.12s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/AddNot, occurrence: 31
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -477,7 +477,7 @@
}
# Handle approve commands
- if command in repo_config.approve_commands:
+ if not command in repo_config.approve_commands:
success = await self._approve_pr(
installation_id,
repo_full_name,........................................................................ [ 34%]
........................................................................ [ 68%]
.................................F
=================================== FAILURES ===================================
__________________________ test_issue_comment_approve __________________________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7f54a2c5dff0>
mock_github_client = <MagicMock name='github_client' id='140001485352848'>
@pytest.mark.asyncio
async def test_issue_comment_approve(webhook_handler, mock_github_client):
"""Test @stampbot approve command."""
payload = load_fixture("issue_comment_approve")
result = await webhook_handler.handle_event("issue_comment", payload)
> assert result["status"] == "success"
E AssertionError: assert 'ignored' == 'success'
E
E - success
E + ignored
tests/test_webhook_handler.py:259: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:35:02.424680Z[0m [[32m[1minfo [0m] [1mReceived issue_comment event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'issue_comment', 'action': 'created'}[0m
[2m2026-05-16T19:35:02.425244Z[0m [[32m[1minfo [0m] [1mNo stampbot.toml found in octocat/hello-world, using defaults[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'org_repo': None}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'level': 'info', 'timestamp': '2026-05-16T19:35:02.424680Z'}
INFO stampbot.webhook_handler:webhook_handler.py:593 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'level': 'info', 'timestamp': '2026-05-16T19:35:02.425244Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_issue_comment_approve - AssertionError: assert 'ignored' == 'success'
- success
+ ignored
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 177 passed, 3 deselected in 1.13s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/AddNot, occurrence: 32
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -488,7 +488,7 @@
chatops_commands_total.labels(
command="approve",
- status="success" if success else "failure",
+ status="success" if not success else "failure",
).inc()
add_span_attributes(........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.63s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/AddNot, occurrence: 33
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -492,7 +492,7 @@
).inc()
add_span_attributes(
- span, {"chatops.result": "approved" if success else "approval_failed"}
+ span, {"chatops.result": "approved" if not success else "approval_failed"}
)
set_span_ok(span)
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.67s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/AddNot, occurrence: 34
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -497,7 +497,7 @@
set_span_ok(span)
return {
- "status": "success" if success else "error",
+ "status": "success" if not success else "error",
"message": "PR approved" if success else "Failed to approve PR",
}
........................................................................ [ 34%]
........................................................................ [ 68%]
.................................F
=================================== FAILURES ===================================
__________________________ test_issue_comment_approve __________________________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7fef6025dff0>
mock_github_client = <MagicMock name='github_client' id='140666087501712'>
@pytest.mark.asyncio
async def test_issue_comment_approve(webhook_handler, mock_github_client):
"""Test @stampbot approve command."""
payload = load_fixture("issue_comment_approve")
result = await webhook_handler.handle_event("issue_comment", payload)
> assert result["status"] == "success"
E AssertionError: assert 'error' == 'success'
E
E - success
E + error
tests/test_webhook_handler.py:259: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:24:19.225265Z[0m [[32m[1minfo [0m] [1mReceived issue_comment event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'issue_comment', 'action': 'created'}[0m
[2m2026-05-16T19:24:19.225804Z[0m [[32m[1minfo [0m] [1mNo stampbot.toml found in octocat/hello-world, using defaults[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'org_repo': None}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'level': 'info', 'timestamp': '2026-05-16T19:24:19.225265Z'}
INFO stampbot.webhook_handler:webhook_handler.py:593 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'level': 'info', 'timestamp': '2026-05-16T19:24:19.225804Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_issue_comment_approve - AssertionError: assert 'error' == 'success'
- success
+ error
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 177 passed, 3 deselected in 1.13s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/AddNot, occurrence: 35
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -498,7 +498,7 @@
return {
"status": "success" if success else "error",
- "message": "PR approved" if success else "Failed to approve PR",
+ "message": "PR approved" if not success else "Failed to approve PR",
}
# Handle unapprove commands........................................................................ [ 34%]
........................................................................ [ 68%]
.................................F
=================================== FAILURES ===================================
__________________________ test_issue_comment_approve __________________________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7f4f7435dff0>
mock_github_client = <MagicMock name='github_client' id='139979229327248'>
@pytest.mark.asyncio
async def test_issue_comment_approve(webhook_handler, mock_github_client):
"""Test @stampbot approve command."""
payload = load_fixture("issue_comment_approve")
result = await webhook_handler.handle_event("issue_comment", payload)
assert result["status"] == "success"
> assert "approved" in result["message"].lower()
E AssertionError: assert 'approved' in 'failed to approve pr'
E + where 'failed to approve pr' = <built-in method lower of str object at 0x7f4f74645870>()
E + where <built-in method lower of str object at 0x7f4f74645870> = 'Failed to approve PR'.lower
tests/test_webhook_handler.py:260: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:51:17.441516Z[0m [[32m[1minfo [0m] [1mReceived issue_comment event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'issue_comment', 'action': 'created'}[0m
[2m2026-05-16T19:51:17.442050Z[0m [[32m[1minfo [0m] [1mNo stampbot.toml found in octocat/hello-world, using defaults[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'org_repo': None}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'level': 'info', 'timestamp': '2026-05-16T19:51:17.441516Z'}
INFO stampbot.webhook_handler:webhook_handler.py:593 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'level': 'info', 'timestamp': '2026-05-16T19:51:17.442050Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_issue_comment_approve - AssertionError: assert 'approved' in 'failed to approve pr'
+ where 'failed to approve pr' = <built-in method lower of str object at 0x7f4f74645870>()
+ where <built-in method lower of str object at 0x7f4f74645870> = 'Failed to approve PR'.lower
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 177 passed, 3 deselected in 1.12s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/AddNot, occurrence: 36
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -513,7 +513,7 @@
chatops_commands_total.labels(
command="unapprove",
- status="success" if success else "failure",
+ status="success" if not success else "failure",
).inc()
add_span_attributes(........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/AddNot, occurrence: 37
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -517,7 +517,7 @@
).inc()
add_span_attributes(
- span, {"chatops.result": "unapproved" if success else "unapprove_failed"}
+ span, {"chatops.result": "unapproved" if not success else "unapprove_failed"}
)
set_span_ok(span)
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.60s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/AddNot, occurrence: 38
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -522,7 +522,7 @@
set_span_ok(span)
return {
- "status": "success" if success else "error",
+ "status": "success" if not success else "error",
"message": "Approvals dismissed" if success else "Failed to dismiss approvals",
}
........................................................................ [ 34%]
........................................................................ [ 68%]
...................................F
=================================== FAILURES ===================================
_________________________ test_issue_comment_unapprove _________________________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7fd0447377f0>
mock_github_client = <MagicMock name='github_client' id='140532479133744'>
@pytest.mark.asyncio
async def test_issue_comment_unapprove(webhook_handler, mock_github_client):
"""Test @stampbot unapprove command."""
payload = load_fixture("issue_comment_unapprove")
mock_github_client.find_bot_reviews.return_value = [111]
result = await webhook_handler.handle_event("issue_comment", payload)
> assert result["status"] == "success"
E AssertionError: assert 'error' == 'success'
E
E - success
E + error
tests/test_webhook_handler.py:289: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T20:09:28.778621Z[0m [[32m[1minfo [0m] [1mReceived issue_comment event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'issue_comment', 'action': 'created'}[0m
[2m2026-05-16T20:09:28.779182Z[0m [[32m[1minfo [0m] [1mNo stampbot.toml found in octocat/hello-world, using defaults[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'org_repo': None}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'level': 'info', 'timestamp': '2026-05-16T20:09:28.778621Z'}
INFO stampbot.webhook_handler:webhook_handler.py:593 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'level': 'info', 'timestamp': '2026-05-16T20:09:28.779182Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_issue_comment_unapprove - AssertionError: assert 'error' == 'success'
- success
+ error
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 179 passed, 3 deselected in 1.20s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/AddNot, occurrence: 39
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -523,7 +523,7 @@
return {
"status": "success" if success else "error",
- "message": "Approvals dismissed" if success else "Failed to dismiss approvals",
+ "message": "Approvals dismissed" if not success else "Failed to dismiss approvals",
}
chatops_commands_total.labels(command="unknown", status="ignored").inc()........................................................................ [ 34%]
........................................................................ [ 68%]
...................................F
=================================== FAILURES ===================================
_________________________ test_issue_comment_unapprove _________________________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7fa852d1f7f0>
mock_github_client = <MagicMock name='github_client' id='140360921548848'>
@pytest.mark.asyncio
async def test_issue_comment_unapprove(webhook_handler, mock_github_client):
"""Test @stampbot unapprove command."""
payload = load_fixture("issue_comment_unapprove")
mock_github_client.find_bot_reviews.return_value = [111]
result = await webhook_handler.handle_event("issue_comment", payload)
assert result["status"] == "success"
> assert "dismissed" in result["message"].lower()
E AssertionError: assert 'dismissed' in 'failed to dismiss approvals'
E + where 'failed to dismiss approvals' = <built-in method lower of str object at 0x7fa853bb9200>()
E + where <built-in method lower of str object at 0x7fa853bb9200> = 'Failed to dismiss approvals'.lower
tests/test_webhook_handler.py:290: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:26:26.698835Z[0m [[32m[1minfo [0m] [1mReceived issue_comment event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'issue_comment', 'action': 'created'}[0m
[2m2026-05-16T19:26:26.699398Z[0m [[32m[1minfo [0m] [1mNo stampbot.toml found in octocat/hello-world, using defaults[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'org_repo': None}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'level': 'info', 'timestamp': '2026-05-16T19:26:26.698835Z'}
INFO stampbot.webhook_handler:webhook_handler.py:593 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'level': 'info', 'timestamp': '2026-05-16T19:26:26.699398Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_issue_comment_unapprove - AssertionError: assert 'dismissed' in 'failed to dismiss approvals'
+ where 'failed to dismiss approvals' = <built-in method lower of str object at 0x7fa853bb9200>()
+ where <built-in method lower of str object at 0x7fa853bb9200> = 'Failed to dismiss approvals'.lower
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 179 passed, 3 deselected in 1.13s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/AddNot, occurrence: 40
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -567,7 +567,7 @@
default_branch,
)
- if content:
+ if not content:
return self._parse_repo_config(span, content, source_repo=repo_full_name)
org_repo_full_name = None........................................................................ [ 34%]
........................................................................ [ 68%]
......................F
=================================== FAILURES ===================================
_______________ test_pr_label_ignored_when_auto_approve_disabled _______________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7f4459b4e7a0>
mock_github_client = <MagicMock name='github_client' id='139931539660416'>
@pytest.mark.asyncio
async def test_pr_label_ignored_when_auto_approve_disabled(webhook_handler, mock_github_client):
"""Test label approvals are ignored when auto_approve_on_label is disabled."""
payload = load_fixture("pr_opened_with_autoapprove_label")
mock_github_client.get_repo_file.return_value = "auto_approve_on_label = false"
result = await webhook_handler.handle_event("pull_request", payload)
> assert result["status"] == "ignored"
E AssertionError: assert 'success' == 'ignored'
E
E - ignored
E + success
tests/test_webhook_handler.py:112: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:34:09.321099Z[0m [[32m[1minfo [0m] [1mReceived pull_request event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'pull_request', 'action': 'opened'}[0m
[2m2026-05-16T19:34:09.321662Z[0m [[32m[1minfo [0m] [1mNo stampbot.toml found in octocat/hello-world, using defaults[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'org_repo': None}[0m
[2m2026-05-16T19:34:09.322491Z[0m [[32m[1minfo [0m] [1mPR #42 has approval label: autoapprove[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'level': 'info', 'timestamp': '2026-05-16T19:34:09.321099Z'}
INFO stampbot.webhook_handler:webhook_handler.py:593 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'level': 'info', 'timestamp': '2026-05-16T19:34:09.321662Z'}
INFO stampbot.webhook_handler:webhook_handler.py:278 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'level': 'info', 'timestamp': '2026-05-16T19:34:09.322491Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_label_ignored_when_auto_approve_disabled - AssertionError: assert 'success' == 'ignored'
- ignored
+ success
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 166 passed, 3 deselected in 1.07s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/AddNot, occurrence: 41
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -571,7 +571,7 @@
return self._parse_repo_config(span, content, source_repo=repo_full_name)
org_repo_full_name = None
- if (
+ if not (
owner_type == "Organization"
and owner_login
and repo_full_name != f"{owner_login}/.github"........................................................................ [ 34%]
........................................................................ [ 68%]
............................................F
=================================== FAILURES ===================================
_____________________ test_repo_config_uses_default_branch _____________________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7fc9127af3b0>
mock_github_client = <MagicMock name='github_client' id='140501572830608'>
@pytest.mark.asyncio
async def test_repo_config_uses_default_branch(webhook_handler, mock_github_client):
"""Test repo config is loaded from the default branch."""
payload = load_fixture("pr_opened_with_autoapprove_label")
payload["repository"]["default_branch"] = "develop"
payload["pull_request"]["base"]["ref"] = "release"
result = await webhook_handler.handle_event("pull_request", payload)
assert result["status"] == "success"
args = mock_github_client.get_repo_file.call_args[0]
> assert args[3] == "develop"
E AssertionError: assert None == 'develop'
tests/test_webhook_handler.py:434: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T20:00:20.782864Z[0m [[32m[1minfo [0m] [1mReceived pull_request event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'pull_request', 'action': 'opened'}[0m
[2m2026-05-16T20:00:20.783555Z[0m [[32m[1minfo [0m] [1mNo stampbot.toml found in octocat/hello-world or octocat/.github, using defaults[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'org_repo': 'octocat/.github'}[0m
[2m2026-05-16T20:00:20.784385Z[0m [[32m[1minfo [0m] [1mPR #42 has approval label: autoapprove[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'level': 'info', 'timestamp': '2026-05-16T20:00:20.782864Z'}
INFO stampbot.webhook_handler:webhook_handler.py:593 {'extra': {'repo': 'octocat/hello-world', 'org_repo': 'octocat/.github'}, 'event': 'No stampbot.toml found in octocat/hello-world or octocat/.github, using defaults', 'level': 'info', 'timestamp': '2026-05-16T20:00:20.783555Z'}
INFO stampbot.webhook_handler:webhook_handler.py:278 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'level': 'info', 'timestamp': '2026-05-16T20:00:20.784385Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_repo_config_uses_default_branch - AssertionError: assert None == 'develop'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 188 passed, 3 deselected in 1.59s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/AddNot, occurrence: 42
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -584,7 +584,7 @@
"stampbot.toml",
None,
)
- if org_content:
+ if not org_content:
return self._parse_repo_config(
span, org_content, source_repo=org_repo_full_name
)........................................................................ [ 34%]
........................................................................ [ 68%]
.............................................F
=================================== FAILURES ===================================
_____________________ test_org_github_repo_config_fallback _____________________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7f7e22da3830>
mock_github_client = <MagicMock name='github_client' id='140179728483392'>
@pytest.mark.asyncio
async def test_org_github_repo_config_fallback(webhook_handler, mock_github_client):
"""Test org .github stampbot.toml is used when repo config is missing."""
payload = load_fixture("pr_opened_with_autoapprove_label")
payload["repository"]["full_name"] = "acme/widgets"
payload["repository"]["default_branch"] = "main"
payload["repository"]["owner"] = {"login": "acme", "type": "Organization"}
payload["pull_request"]["labels"] = [{"name": "org-approve"}]
def get_repo_file_side_effect(_installation_id, repo_full_name, _file_path, _ref):
if repo_full_name == "acme/widgets":
return None
if repo_full_name == "acme/.github":
return 'approval_labels = ["org-approve"]'
return None
mock_github_client.get_repo_file.side_effect = get_repo_file_side_effect
result = await webhook_handler.handle_event("pull_request", payload)
> assert result["status"] == "success"
E AssertionError: assert 'ignored' == 'success'
E
E - success
E + ignored
tests/test_webhook_handler.py:457: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:37:34.419550Z[0m [[32m[1minfo [0m] [1mReceived pull_request event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'pull_request', 'action': 'opened'}[0m
[2m2026-05-16T19:37:34.420231Z[0m [[32m[1minfo [0m] [1mNo stampbot.toml found in acme/widgets or acme/.github, using defaults[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'acme/widgets', 'org_repo': 'acme/.github'}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'level': 'info', 'timestamp': '2026-05-16T19:37:34.419550Z'}
INFO stampbot.webhook_handler:webhook_handler.py:593 {'extra': {'repo': 'acme/widgets', 'org_repo': 'acme/.github'}, 'event': 'No stampbot.toml found in acme/widgets or acme/.github, using defaults', 'level': 'info', 'timestamp': '2026-05-16T19:37:34.420231Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_org_github_repo_config_fallback - AssertionError: assert 'ignored' == 'success'
- success
+ ignored
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 189 passed, 3 deselected in 1.56s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/AddNot, occurrence: 43
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -593,7 +593,7 @@
logger.info(
"No stampbot.toml found in %s%s, using defaults",
repo_full_name,
- f" or {org_repo_full_name}" if org_repo_full_name else "",
+ f" or {org_repo_full_name}" if not org_repo_full_name else "",
extra={"repo": repo_full_name, "org_repo": org_repo_full_name},
)
add_span_attributes(span, {"config.result": "default"})........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.60s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/AddNot, occurrence: 44
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -689,7 +689,7 @@
pr_number,
)
- if existing_approvals:
+ if not existing_approvals:
logger.info(
"PR #%d in %s already has active approval, skipping",
pr_number,........................................................................ [ 34%]
........................................................................ [ 68%]
...................F
=================================== FAILURES ===================================
____________________ test_pr_opened_with_autoapprove_label _____________________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7f00601fbe10>
mock_github_client = <MagicMock name='github_client' id='139639585719024'>
@pytest.mark.asyncio
async def test_pr_opened_with_autoapprove_label(webhook_handler, mock_github_client):
"""Test PR opened with autoapprove label triggers approval."""
payload = load_fixture("pr_opened_with_autoapprove_label")
result = await webhook_handler.handle_event("pull_request", payload)
assert result["status"] == "success"
assert "approved" in result["message"].lower()
> mock_github_client.approve_pr.assert_called_once_with(
12345, # installation_id
"octocat/hello-world", # repo
42, # pr_number
"Auto-approved by Stampbot (label: autoapprove)",
)
tests/test_webhook_handler.py:72:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <MagicMock name='github_client.approve_pr' id='139639585719360'>
args = (12345, 'octocat/hello-world', 42, 'Auto-approved by Stampbot (label: autoapprove)')
kwargs = {}, msg = "Expected 'approve_pr' to be called once. Called 0 times."
def assert_called_once_with(self, /, *args, **kwargs):
"""assert that the mock was called exactly once and that that call was
with the specified arguments."""
if not self.call_count == 1:
msg = ("Expected '%s' to be called once. Called %s times.%s"
% (self._mock_name or 'mock',
self.call_count,
self._calls_repr()))
> raise AssertionError(msg)
E AssertionError: Expected 'approve_pr' to be called once. Called 0 times.
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:996: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:42:48.291818Z[0m [[32m[1minfo [0m] [1mReceived pull_request event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'pull_request', 'action': 'opened'}[0m
[2m2026-05-16T19:42:48.292523Z[0m [[32m[1minfo [0m] [1mNo stampbot.toml found in octocat/hello-world, using defaults[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'org_repo': None}[0m
[2m2026-05-16T19:42:48.293402Z[0m [[32m[1minfo [0m] [1mPR #42 has approval label: autoapprove[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}[0m
[2m2026-05-16T19:42:48.293733Z[0m [[32m[1minfo [0m] [1mPR #42 in octocat/hello-world already has active approval, skipping[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'pr_number': 42, 'existing_review_ids': []}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'level': 'info', 'timestamp': '2026-05-16T19:42:48.291818Z'}
INFO stampbot.webhook_handler:webhook_handler.py:593 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'level': 'info', 'timestamp': '2026-05-16T19:42:48.292523Z'}
INFO stampbot.webhook_handler:webhook_handler.py:278 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'level': 'info', 'timestamp': '2026-05-16T19:42:48.293402Z'}
INFO stampbot.webhook_handler:webhook_handler.py:693 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'existing_review_ids': []}, 'event': 'PR #42 in octocat/hello-world already has active approval, skipping', 'level': 'info', 'timestamp': '2026-05-16T19:42:48.293733Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_opened_with_autoapprove_label - AssertionError: Expected 'approve_pr' to be called once. Called 0 times.
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 163 passed, 3 deselected in 1.13s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/AddNot, occurrence: 45
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -723,7 +723,7 @@
duration = time.time() - start_time
pr_approval_duration_seconds.observe(duration)
- status = "success" if success else "failure"
+ status = "success" if not success else "failure"
pr_approvals_total.labels(
trigger_type=trigger_type,
status=status,........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.60s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/AddNot, occurrence: 46
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -729,7 +729,7 @@
status=status,
).inc()
- if not success:
+ if not not success:
errors_total.labels(error_type="approval_failed").inc()
add_span_attributes(span, {"approval.result": status})........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.66s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/AddNot, occurrence: 47
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -778,7 +778,7 @@
add_span_attributes(span, {"dismissal.reviews_found": len(review_ids)})
- if not review_ids:
+ if not not review_ids:
logger.info(
"No bot approvals found on PR #%d",
pr_number,........................................................................ [ 34%]
........................................................................ [ 68%]
.........................F
=================================== FAILURES ===================================
________________________ test_pr_unlabeled_autoapprove _________________________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7fd0177ed450>
mock_github_client = <MagicMock name='github_client' id='140531786245728'>
@pytest.mark.asyncio
async def test_pr_unlabeled_autoapprove(webhook_handler, mock_github_client):
"""Test removing autoapprove label dismisses approvals."""
payload = load_fixture("pr_unlabeled_autoapprove")
mock_github_client.find_bot_reviews.return_value = [111, 222] # Mock review IDs
result = await webhook_handler.handle_event("pull_request", payload)
assert result["status"] == "success"
assert "dismissed" in result["message"].lower()
mock_github_client.find_bot_reviews.assert_called_once()
> assert mock_github_client.dismiss_approval.call_count == 2
E AssertionError: assert 0 == 2
E + where 0 = <MagicMock name='github_client.dismiss_approval' id='140531786244720'>.call_count
E + where <MagicMock name='github_client.dismiss_approval' id='140531786244720'> = <MagicMock name='github_client' id='140531786245728'>.dismiss_approval
tests/test_webhook_handler.py:155: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:37:13.640250Z[0m [[32m[1minfo [0m] [1mReceived pull_request event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'pull_request', 'action': 'unlabeled'}[0m
[2m2026-05-16T19:37:13.640773Z[0m [[32m[1minfo [0m] [1mNo stampbot.toml found in octocat/hello-world, using defaults[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'org_repo': None}[0m
[2m2026-05-16T19:37:13.641318Z[0m [[32m[1minfo [0m] [1mApproval label autoapprove removed from PR #42[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}[0m
[2m2026-05-16T19:37:13.641720Z[0m [[32m[1minfo [0m] [1mNo bot approvals found on PR #42[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'pr_number': 42}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'pull_request', 'action': 'unlabeled'}, 'event': 'Received pull_request event', 'level': 'info', 'timestamp': '2026-05-16T19:37:13.640250Z'}
INFO stampbot.webhook_handler:webhook_handler.py:593 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'level': 'info', 'timestamp': '2026-05-16T19:37:13.640773Z'}
INFO stampbot.webhook_handler:webhook_handler.py:320 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'Approval label autoapprove removed from PR #42', 'level': 'info', 'timestamp': '2026-05-16T19:37:13.641318Z'}
INFO stampbot.webhook_handler:webhook_handler.py:782 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42}, 'event': 'No bot approvals found on PR #42', 'level': 'info', 'timestamp': '2026-05-16T19:37:13.641720Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_unlabeled_autoapprove - AssertionError: assert 0 == 2
+ where 0 = <MagicMock name='github_client.dismiss_approval' id='140531786244720'>.call_count
+ where <MagicMock name='github_client.dismiss_approval' id='140531786244720'> = <MagicMock name='github_client' id='140531786245728'>.dismiss_approval
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 169 passed, 3 deselected in 1.09s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/AddNot, occurrence: 48
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -807,7 +807,7 @@
duration = time.time() - start_time
pr_dismissal_duration_seconds.observe(duration)
- status = "success" if success else "failure"
+ status = "success" if not success else "failure"
pr_dismissals_total.labels(trigger_type=trigger_type, status=status).inc()
add_span_attributes(span, {"dismissal.result": status})........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.60s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceTrueWithFalse, occurrence: 0
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -708,7 +708,7 @@
},
)
set_span_ok(span)
- return True
+ return False
start_time = time.time()
........................................................................ [ 34%]
........................................................................ [ 68%]
........................F
=================================== FAILURES ===================================
___________________ test_pr_labeled_skips_duplicate_approval ___________________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7fc7503e1250>
mock_github_client = <MagicMock name='github_client' id='140494083615984'>
@pytest.mark.asyncio
async def test_pr_labeled_skips_duplicate_approval(webhook_handler, mock_github_client):
"""Test that existing approval prevents duplicate approval comment."""
payload = load_fixture("pr_labeled_autoapprove")
# Simulate existing active approval
mock_github_client.find_bot_reviews.return_value = [12345]
result = await webhook_handler.handle_event("pull_request", payload)
> assert result["status"] == "success"
E AssertionError: assert 'error' == 'success'
E
E - success
E + error
tests/test_webhook_handler.py:137: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:28:10.377818Z[0m [[32m[1minfo [0m] [1mReceived pull_request event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'pull_request', 'action': 'labeled'}[0m
[2m2026-05-16T19:28:10.378375Z[0m [[32m[1minfo [0m] [1mNo stampbot.toml found in octocat/hello-world, using defaults[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'org_repo': None}[0m
[2m2026-05-16T19:28:10.378896Z[0m [[32m[1minfo [0m] [1mPR #42 has approval label: autoapprove[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}[0m
[2m2026-05-16T19:28:10.379243Z[0m [[32m[1minfo [0m] [1mPR #42 in octocat/hello-world already has active approval, skipping[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'pr_number': 42, 'existing_review_ids': [12345]}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'pull_request', 'action': 'labeled'}, 'event': 'Received pull_request event', 'level': 'info', 'timestamp': '2026-05-16T19:28:10.377818Z'}
INFO stampbot.webhook_handler:webhook_handler.py:593 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'level': 'info', 'timestamp': '2026-05-16T19:28:10.378375Z'}
INFO stampbot.webhook_handler:webhook_handler.py:278 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'level': 'info', 'timestamp': '2026-05-16T19:28:10.378896Z'}
INFO stampbot.webhook_handler:webhook_handler.py:693 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'existing_review_ids': [12345]}, 'event': 'PR #42 in octocat/hello-world already has active approval, skipping', 'level': 'info', 'timestamp': '2026-05-16T19:28:10.379243Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_labeled_skips_duplicate_approval - AssertionError: assert 'error' == 'success'
- success
+ error
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 168 passed, 3 deselected in 1.09s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceTrueWithFalse, occurrence: 1
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -789,7 +789,7 @@
pr_dismissals_total.labels(trigger_type=trigger_type, status="success").inc()
add_span_attributes(span, {"dismissal.result": "no_reviews"})
set_span_ok(span)
- return True
+ return False
# Dismiss each review
success = True........................................................................ [ 34%]
........................................................................ [ 68%]
...........................F
=================================== FAILURES ===================================
_______________________ test_pr_unlabeled_no_bot_reviews _______________________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7fa8ac5b54f0>
mock_github_client = <MagicMock name='github_client' id='140362418498464'>
@pytest.mark.asyncio
async def test_pr_unlabeled_no_bot_reviews(webhook_handler, mock_github_client):
"""Test removing label when no bot reviews exist."""
payload = load_fixture("pr_unlabeled_autoapprove")
mock_github_client.find_bot_reviews.return_value = [] # No reviews to dismiss
result = await webhook_handler.handle_event("pull_request", payload)
> assert result["status"] == "success"
E AssertionError: assert 'error' == 'success'
E
E - success
E + error
tests/test_webhook_handler.py:179: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:58:41.825344Z[0m [[32m[1minfo [0m] [1mReceived pull_request event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'pull_request', 'action': 'unlabeled'}[0m
[2m2026-05-16T19:58:41.825881Z[0m [[32m[1minfo [0m] [1mNo stampbot.toml found in octocat/hello-world, using defaults[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'org_repo': None}[0m
[2m2026-05-16T19:58:41.826410Z[0m [[32m[1minfo [0m] [1mApproval label autoapprove removed from PR #42[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}[0m
[2m2026-05-16T19:58:41.826841Z[0m [[32m[1minfo [0m] [1mNo bot approvals found on PR #42[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'pr_number': 42}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'pull_request', 'action': 'unlabeled'}, 'event': 'Received pull_request event', 'level': 'info', 'timestamp': '2026-05-16T19:58:41.825344Z'}
INFO stampbot.webhook_handler:webhook_handler.py:593 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'level': 'info', 'timestamp': '2026-05-16T19:58:41.825881Z'}
INFO stampbot.webhook_handler:webhook_handler.py:320 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'Approval label autoapprove removed from PR #42', 'level': 'info', 'timestamp': '2026-05-16T19:58:41.826410Z'}
INFO stampbot.webhook_handler:webhook_handler.py:782 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42}, 'event': 'No bot approvals found on PR #42', 'level': 'info', 'timestamp': '2026-05-16T19:58:41.826841Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_unlabeled_no_bot_reviews - AssertionError: assert 'error' == 'success'
- success
+ error
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 171 passed, 3 deselected in 1.11s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceTrueWithFalse, occurrence: 2
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -792,7 +792,7 @@
return True
# Dismiss each review
- success = True
+ success = False
for review_id in review_ids:
result = await run_in_threadpool(
github_client.dismiss_approval,........................................................................ [ 34%]
........................................................................ [ 68%]
.........................F
=================================== FAILURES ===================================
________________________ test_pr_unlabeled_autoapprove _________________________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7f029aa01450>
mock_github_client = <MagicMock name='github_client' id='139649224959584'>
@pytest.mark.asyncio
async def test_pr_unlabeled_autoapprove(webhook_handler, mock_github_client):
"""Test removing autoapprove label dismisses approvals."""
payload = load_fixture("pr_unlabeled_autoapprove")
mock_github_client.find_bot_reviews.return_value = [111, 222] # Mock review IDs
result = await webhook_handler.handle_event("pull_request", payload)
> assert result["status"] == "success"
E AssertionError: assert 'error' == 'success'
E
E - success
E + error
tests/test_webhook_handler.py:152: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:41:07.975056Z[0m [[32m[1minfo [0m] [1mReceived pull_request event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'pull_request', 'action': 'unlabeled'}[0m
[2m2026-05-16T19:41:07.975622Z[0m [[32m[1minfo [0m] [1mNo stampbot.toml found in octocat/hello-world, using defaults[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'org_repo': None}[0m
[2m2026-05-16T19:41:07.976128Z[0m [[32m[1minfo [0m] [1mApproval label autoapprove removed from PR #42[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'pull_request', 'action': 'unlabeled'}, 'event': 'Received pull_request event', 'level': 'info', 'timestamp': '2026-05-16T19:41:07.975056Z'}
INFO stampbot.webhook_handler:webhook_handler.py:593 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'level': 'info', 'timestamp': '2026-05-16T19:41:07.975622Z'}
INFO stampbot.webhook_handler:webhook_handler.py:320 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'Approval label autoapprove removed from PR #42', 'level': 'info', 'timestamp': '2026-05-16T19:41:07.976128Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_unlabeled_autoapprove - AssertionError: assert 'error' == 'success'
- success
+ error
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 169 passed, 3 deselected in 1.09s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceFalseWithTrue, occurrence: 0
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -68,7 +68,7 @@
True if signature is valid
"""
if not signature:
- return False
+ return True
expected_signature = (
"sha256=" + hmac.new(self.webhook_secret, payload, hashlib.sha256).hexdigest()........................................................................ [ 34%]
.........................F
=================================== FAILURES ===================================
________________________ test_webhook_missing_signature ________________________
test_client = <starlette.testclient.TestClient object at 0x7f93e23c45a0>
def test_webhook_missing_signature(test_client: TestClient):
"""Test webhook rejects requests without signature."""
response = test_client.post(
"/webhook",
json={"zen": "test"},
headers={"X-GitHub-Event": "ping"},
)
> assert response.status_code == 401
E assert 200 == 401
E + where 200 = <Response [200 OK]>.status_code
tests/test_main.py:89: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:25:50.813674Z[0m [[32m[1minfo [0m] [1mReceived ping event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mclient_ip[0m=[35mtestclient[0m [36mextra[0m=[35m{'event_type': 'ping', 'action': ''}[0m
[2m2026-05-16T19:25:50.814621Z[0m [[32m[1minfo [0m] [1mHTTP Request: POST http://testserver/webhook "HTTP/1.1 200 OK"[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'ping', 'action': ''}, 'event': 'Received ping event', 'client_ip': 'testclient', 'level': 'info', 'timestamp': '2026-05-16T19:25:50.813674Z'}
INFO httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 200 OK"
=========================== short test summary info ============================
FAILED tests/test_main.py::test_webhook_missing_signature - assert 200 == 401
+ where 200 = <Response [200 OK]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 97 passed, 3 deselected in 0.84s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceFalseWithTrue, occurrence: 1
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -214,7 +214,7 @@
repo_full_name,
label,
)
- if label_exists is False:
+ if label_exists is True:
logger.warning(
"Approval label %s not found in %s",
label,........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.63s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceFalseWithTrue, occurrence: 2
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -832,7 +832,7 @@
errors_total.labels(error_type="dismiss_failed").inc()
set_span_error(span, e)
- return False
+ return True
# Global handler instance........................................................................ [ 34%]
........................................................................ [ 68%]
............................................................F
=================================== FAILURES ===================================
_______________________ test_dismiss_approvals_exception _______________________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7f3131e901d0>
mock_github_client = <MagicMock name='github_client' id='139849261797728'>
@pytest.mark.asyncio
async def test_dismiss_approvals_exception(webhook_handler, mock_github_client):
"""Test _dismiss_approvals handles exceptions gracefully."""
payload = load_fixture("pr_unlabeled_autoapprove")
# Make find_bot_reviews raise an exception
mock_github_client.find_bot_reviews.side_effect = Exception("API error")
result = await webhook_handler.handle_event("pull_request", payload)
> assert result["status"] == "error"
E AssertionError: assert 'success' == 'error'
E
E - error
E + success
tests/test_webhook_handler.py:729: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T20:06:22.283156Z[0m [[32m[1minfo [0m] [1mReceived pull_request event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'pull_request', 'action': 'unlabeled'}[0m
[2m2026-05-16T20:06:22.283745Z[0m [[32m[1minfo [0m] [1mNo stampbot.toml found in octocat/hello-world, using defaults[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'org_repo': None}[0m
[2m2026-05-16T20:06:22.284302Z[0m [[32m[1minfo [0m] [1mApproval label autoapprove removed from PR #42[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}[0m
[2m2026-05-16T20:06:22.284676Z[0m [[31m[1merror [0m] [1mError dismissing approvals: API error[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35merror[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'pr_number': 42, 'error': 'API error'}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'pull_request', 'action': 'unlabeled'}, 'event': 'Received pull_request event', 'level': 'info', 'timestamp': '2026-05-16T20:06:22.283156Z'}
INFO stampbot.webhook_handler:webhook_handler.py:593 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'level': 'info', 'timestamp': '2026-05-16T20:06:22.283745Z'}
INFO stampbot.webhook_handler:webhook_handler.py:320 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'Approval label autoapprove removed from PR #42', 'level': 'info', 'timestamp': '2026-05-16T20:06:22.284302Z'}
ERROR stampbot.webhook_handler:webhook_handler.py:823 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'error': 'API error'}, 'event': 'Error dismissing approvals: API error', 'level': 'error', 'timestamp': '2026-05-16T20:06:22.284676Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_dismiss_approvals_exception - AssertionError: assert 'success' == 'error'
- error
+ success
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 204 passed, 3 deselected in 1.63s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceAndWithOr, occurrence: 0
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -223,7 +223,7 @@
)
# Check if we should approve based on labels
- if repo_config.auto_approve_on_label and action in [
+ if repo_config.auto_approve_on_label or action in [
"opened",
"reopened",
"labeled",........................................................................ [ 34%]
........................................................................ [ 68%]
......................F
=================================== FAILURES ===================================
_______________ test_pr_label_ignored_when_auto_approve_disabled _______________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7f448ae06580>
mock_github_client = <MagicMock name='github_client' id='139932364595152'>
@pytest.mark.asyncio
async def test_pr_label_ignored_when_auto_approve_disabled(webhook_handler, mock_github_client):
"""Test label approvals are ignored when auto_approve_on_label is disabled."""
payload = load_fixture("pr_opened_with_autoapprove_label")
mock_github_client.get_repo_file.return_value = "auto_approve_on_label = false"
result = await webhook_handler.handle_event("pull_request", payload)
> assert result["status"] == "ignored"
E AssertionError: assert 'success' == 'ignored'
E
E - ignored
E + success
tests/test_webhook_handler.py:112: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:24:57.818219Z[0m [[32m[1minfo [0m] [1mReceived pull_request event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'pull_request', 'action': 'opened'}[0m
[2m2026-05-16T19:24:57.819486Z[0m [[32m[1minfo [0m] [1mPR #42 has approval label: autoapprove[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'level': 'info', 'timestamp': '2026-05-16T19:24:57.818219Z'}
INFO stampbot.webhook_handler:webhook_handler.py:278 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'level': 'info', 'timestamp': '2026-05-16T19:24:57.819486Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_label_ignored_when_auto_approve_disabled - AssertionError: assert 'success' == 'ignored'
- ignored
+ success
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 166 passed, 3 deselected in 1.08s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceAndWithOr, occurrence: 1
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -237,7 +237,7 @@
if label in repo_config.approval_labels:
# Check if team membership verification is needed
author_team_slugs: list[str] | None = None
- if repo_config.needs_team_check(pr_author) and owner_login:
+ if repo_config.needs_team_check(pr_author) or owner_login:
author_team_slugs = await run_in_threadpool(
github_client.get_user_team_slugs,
installation_id,........................................................................ [ 34%]
........................................................................ [ 68%]
..................................................................F
=================================== FAILURES ===================================
_________________ test_pr_with_allowed_users_skips_team_check __________________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7f820ff1b170>
mock_github_client = <MagicMock name='github_client' id='140196590295360'>
@pytest.mark.asyncio
async def test_pr_with_allowed_users_skips_team_check(webhook_handler, mock_github_client):
"""Test PR with user in allowed_users skips team membership check."""
payload = load_fixture("pr_opened_with_autoapprove_label")
# The fixture has user "contributor"
mock_github_client.get_repo_file.return_value = """
allowed_users = ["contributor"]
allowed_teams = ["acme/release-team"]
"""
result = await webhook_handler.handle_event("pull_request", payload)
assert result["status"] == "success"
# Should NOT call get_user_team_slugs since user is in allowed_users
> mock_github_client.get_user_team_slugs.assert_not_called()
tests/test_webhook_handler.py:848:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <MagicMock name='github_client.get_user_team_slugs' id='140196590298048'>
def assert_not_called(self):
"""assert that the mock was never called.
"""
if self.call_count != 0:
msg = ("Expected '%s' to not have been called. Called %s times.%s"
% (self._mock_name or 'mock',
self.call_count,
self._calls_repr()))
> raise AssertionError(msg)
E AssertionError: Expected 'get_user_team_slugs' to not have been called. Called 1 times.
E Calls: [call(12345, 'octocat', 'contributor', ['acme/release-team'])].
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:946: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:51:15.192520Z[0m [[32m[1minfo [0m] [1mReceived pull_request event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'pull_request', 'action': 'opened'}[0m
[2m2026-05-16T19:51:15.193952Z[0m [[32m[1minfo [0m] [1mPR #42 has approval label: autoapprove[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'level': 'info', 'timestamp': '2026-05-16T19:51:15.192520Z'}
INFO stampbot.webhook_handler:webhook_handler.py:278 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'level': 'info', 'timestamp': '2026-05-16T19:51:15.193952Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_with_allowed_users_skips_team_check - AssertionError: Expected 'get_user_team_slugs' to not have been called. Called 1 times.
Calls: [call(12345, 'octocat', 'contributor', ['acme/release-team'])].
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 210 passed, 3 deselected in 1.68s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceAndWithOr, occurrence: 2
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -314,7 +314,7 @@
}
# Check if we should remove approval when label is removed
- if repo_config.auto_approve_on_label and action == "unlabeled":
+ if repo_config.auto_approve_on_label or action == "unlabeled":
removed_label = payload.get("label", {}).get("name")
if removed_label in repo_config.approval_labels:
logger.info(........................................................................ [ 34%]
........................................................................ [ 68%]
..........................F
=================================== FAILURES ===================================
_____________ test_pr_unlabeled_ignored_when_auto_approve_disabled _____________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7efd5c2a54f0>
mock_github_client = <MagicMock name='github_client' id='139626565514112'>
@pytest.mark.asyncio
async def test_pr_unlabeled_ignored_when_auto_approve_disabled(webhook_handler, mock_github_client):
"""Test label removal is ignored when auto_approve_on_label is disabled."""
payload = load_fixture("pr_unlabeled_autoapprove")
mock_github_client.get_repo_file.return_value = "auto_approve_on_label = false"
result = await webhook_handler.handle_event("pull_request", payload)
> assert result["status"] == "ignored"
E AssertionError: assert 'success' == 'ignored'
E
E - ignored
E + success
tests/test_webhook_handler.py:166: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T20:05:48.057152Z[0m [[32m[1minfo [0m] [1mReceived pull_request event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'pull_request', 'action': 'unlabeled'}[0m
[2m2026-05-16T20:05:48.058161Z[0m [[32m[1minfo [0m] [1mApproval label autoapprove removed from PR #42[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}[0m
[2m2026-05-16T20:05:48.058672Z[0m [[32m[1minfo [0m] [1mNo bot approvals found on PR #42[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'pr_number': 42}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'pull_request', 'action': 'unlabeled'}, 'event': 'Received pull_request event', 'level': 'info', 'timestamp': '2026-05-16T20:05:48.057152Z'}
INFO stampbot.webhook_handler:webhook_handler.py:320 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'Approval label autoapprove removed from PR #42', 'level': 'info', 'timestamp': '2026-05-16T20:05:48.058161Z'}
INFO stampbot.webhook_handler:webhook_handler.py:782 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42}, 'event': 'No bot approvals found on PR #42', 'level': 'info', 'timestamp': '2026-05-16T20:05:48.058672Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_unlabeled_ignored_when_auto_approve_disabled - AssertionError: assert 'success' == 'ignored'
- ignored
+ success
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 170 passed, 3 deselected in 1.15s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceAndWithOr, occurrence: 3
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -573,7 +573,7 @@
org_repo_full_name = None
if (
owner_type == "Organization"
- and owner_login
+ or owner_login
and repo_full_name != f"{owner_login}/.github"
):
org_repo_full_name = f"{owner_login}/.github"........................................................................ [ 34%]
........................................................................ [ 68%]
............................................F
=================================== FAILURES ===================================
_____________________ test_repo_config_uses_default_branch _____________________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7ff7e9887530>
mock_github_client = <MagicMock name='github_client' id='140702750524816'>
@pytest.mark.asyncio
async def test_repo_config_uses_default_branch(webhook_handler, mock_github_client):
"""Test repo config is loaded from the default branch."""
payload = load_fixture("pr_opened_with_autoapprove_label")
payload["repository"]["default_branch"] = "develop"
payload["pull_request"]["base"]["ref"] = "release"
result = await webhook_handler.handle_event("pull_request", payload)
assert result["status"] == "success"
args = mock_github_client.get_repo_file.call_args[0]
> assert args[3] == "develop"
E AssertionError: assert None == 'develop'
tests/test_webhook_handler.py:434: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:27:42.904819Z[0m [[32m[1minfo [0m] [1mReceived pull_request event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'pull_request', 'action': 'opened'}[0m
[2m2026-05-16T19:27:42.905511Z[0m [[32m[1minfo [0m] [1mNo stampbot.toml found in octocat/hello-world or octocat/.github, using defaults[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'org_repo': 'octocat/.github'}[0m
[2m2026-05-16T19:27:42.906319Z[0m [[32m[1minfo [0m] [1mPR #42 has approval label: autoapprove[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'level': 'info', 'timestamp': '2026-05-16T19:27:42.904819Z'}
INFO stampbot.webhook_handler:webhook_handler.py:593 {'extra': {'repo': 'octocat/hello-world', 'org_repo': 'octocat/.github'}, 'event': 'No stampbot.toml found in octocat/hello-world or octocat/.github, using defaults', 'level': 'info', 'timestamp': '2026-05-16T19:27:42.905511Z'}
INFO stampbot.webhook_handler:webhook_handler.py:278 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'level': 'info', 'timestamp': '2026-05-16T19:27:42.906319Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_repo_config_uses_default_branch - AssertionError: assert None == 'develop'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 188 passed, 3 deselected in 1.54s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceAndWithOr, occurrence: 4
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -574,7 +574,7 @@
if (
owner_type == "Organization"
and owner_login
- and repo_full_name != f"{owner_login}/.github"
+ or repo_full_name != f"{owner_login}/.github"
):
org_repo_full_name = f"{owner_login}/.github"
org_content = await run_in_threadpool(........................................................................ [ 34%]
........................................................................ [ 68%]
............................................F
=================================== FAILURES ===================================
_____________________ test_repo_config_uses_default_branch _____________________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7fe24a0735f0>
mock_github_client = <MagicMock name='github_client' id='140609880110960'>
@pytest.mark.asyncio
async def test_repo_config_uses_default_branch(webhook_handler, mock_github_client):
"""Test repo config is loaded from the default branch."""
payload = load_fixture("pr_opened_with_autoapprove_label")
payload["repository"]["default_branch"] = "develop"
payload["pull_request"]["base"]["ref"] = "release"
result = await webhook_handler.handle_event("pull_request", payload)
assert result["status"] == "success"
args = mock_github_client.get_repo_file.call_args[0]
> assert args[3] == "develop"
E AssertionError: assert None == 'develop'
tests/test_webhook_handler.py:434: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:24:28.702531Z[0m [[32m[1minfo [0m] [1mReceived pull_request event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'pull_request', 'action': 'opened'}[0m
[2m2026-05-16T19:24:28.703226Z[0m [[32m[1minfo [0m] [1mNo stampbot.toml found in octocat/hello-world or octocat/.github, using defaults[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'org_repo': 'octocat/.github'}[0m
[2m2026-05-16T19:24:28.704007Z[0m [[32m[1minfo [0m] [1mPR #42 has approval label: autoapprove[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'level': 'info', 'timestamp': '2026-05-16T19:24:28.702531Z'}
INFO stampbot.webhook_handler:webhook_handler.py:593 {'extra': {'repo': 'octocat/hello-world', 'org_repo': 'octocat/.github'}, 'event': 'No stampbot.toml found in octocat/hello-world or octocat/.github, using defaults', 'level': 'info', 'timestamp': '2026-05-16T19:24:28.703226Z'}
INFO stampbot.webhook_handler:webhook_handler.py:278 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'level': 'info', 'timestamp': '2026-05-16T19:24:28.704007Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_repo_config_uses_default_branch - AssertionError: assert None == 'develop'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 188 passed, 3 deselected in 1.55s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceAndWithOr, occurrence: 5
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -802,7 +802,7 @@
review_id,
message,
)
- success = success and result
+ success = success or result
duration = time.time() - start_time
pr_dismissal_duration_seconds.observe(duration)........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ReplaceOrWithAnd, occurrence: 0
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -143,7 +143,7 @@
pr_number = pr.get("number")
repo = payload.get("repository", {})
repo_full_name = repo.get("full_name")
- repo_default_branch = repo.get("default_branch") or "main"
+ repo_default_branch = repo.get("default_branch") and "main"
repo_owner = repo.get("owner", {})
owner_login = repo_owner.get("login")
owner_type = repo_owner.get("type")........................................................................ [ 34%]
........................................................................ [ 68%]
............................................F
=================================== FAILURES ===================================
_____________________ test_repo_config_uses_default_branch _____________________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7f3fb1a9fad0>
mock_github_client = <MagicMock name='github_client' id='139911539505008'>
@pytest.mark.asyncio
async def test_repo_config_uses_default_branch(webhook_handler, mock_github_client):
"""Test repo config is loaded from the default branch."""
payload = load_fixture("pr_opened_with_autoapprove_label")
payload["repository"]["default_branch"] = "develop"
payload["pull_request"]["base"]["ref"] = "release"
result = await webhook_handler.handle_event("pull_request", payload)
assert result["status"] == "success"
args = mock_github_client.get_repo_file.call_args[0]
> assert args[3] == "develop"
E AssertionError: assert 'main' == 'develop'
E
E - develop
E + main
tests/test_webhook_handler.py:434: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:46:05.014920Z[0m [[32m[1minfo [0m] [1mReceived pull_request event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'pull_request', 'action': 'opened'}[0m
[2m2026-05-16T19:46:05.015476Z[0m [[32m[1minfo [0m] [1mNo stampbot.toml found in octocat/hello-world, using defaults[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'org_repo': None}[0m
[2m2026-05-16T19:46:05.016276Z[0m [[32m[1minfo [0m] [1mPR #42 has approval label: autoapprove[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'level': 'info', 'timestamp': '2026-05-16T19:46:05.014920Z'}
INFO stampbot.webhook_handler:webhook_handler.py:593 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'level': 'info', 'timestamp': '2026-05-16T19:46:05.015476Z'}
INFO stampbot.webhook_handler:webhook_handler.py:278 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'level': 'info', 'timestamp': '2026-05-16T19:46:05.016276Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_repo_config_uses_default_branch - AssertionError: assert 'main' == 'develop'
- develop
+ main
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 188 passed, 3 deselected in 1.58s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceOrWithAnd, occurrence: 1
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -152,7 +152,7 @@
with create_span(
"webhook.handle_pull_request",
{
- "github.repo": repo_full_name or "unknown",
+ "github.repo": repo_full_name and "unknown",
"github.pr_number": pr_number or 0,
"github.action": action or "unknown",
},........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.69s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceOrWithAnd, occurrence: 2
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -153,7 +153,7 @@
"webhook.handle_pull_request",
{
"github.repo": repo_full_name or "unknown",
- "github.pr_number": pr_number or 0,
+ "github.pr_number": pr_number and 0,
"github.action": action or "unknown",
},
) as span:........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceOrWithAnd, occurrence: 3
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -154,7 +154,7 @@
{
"github.repo": repo_full_name or "unknown",
"github.pr_number": pr_number or 0,
- "github.action": action or "unknown",
+ "github.action": action and "unknown",
},
) as span:
if not all([pr_number, repo_full_name, installation_id]):........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.62s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceOrWithAnd, occurrence: 4
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -391,7 +391,7 @@
repo = payload.get("repository", {})
repo_full_name = repo.get("full_name")
- repo_default_branch = repo.get("default_branch") or "main"
+ repo_default_branch = repo.get("default_branch") and "main"
repo_owner = repo.get("owner", {})
owner_login = repo_owner.get("login")
owner_type = repo_owner.get("type")........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceOrWithAnd, occurrence: 5
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -401,7 +401,7 @@
with create_span(
"webhook.handle_chatops",
{
- "github.repo": repo_full_name or "unknown",
+ "github.repo": repo_full_name and "unknown",
"github.pr_number": pr_number,
"chatops.commenter": commenter,
},........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.60s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/ReplaceOrWithAnd, occurrence: 6
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -556,7 +556,7 @@
"""
with create_span(
"webhook.get_repo_config",
- {"github.repo": repo_full_name, "github.ref": default_branch or "default"},
+ {"github.repo": repo_full_name, "github.ref": default_branch and "default"},
) as span:
try:
content = await run_in_threadpool(........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.60s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ExceptionReplacer, occurrence: 0
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -600,7 +600,7 @@
set_span_ok(span)
return RepoConfig.default()
- except Exception as e:
+ except CosmicRayTestingException as e:
repo_config_loads_total.labels(status="error").inc()
logger.warning(
"Error loading config from %s: %s, using defaults",........................................................................ [ 34%]
........................................................................ [ 68%]
...........................................................F
=================================== FAILURES ===================================
________________________ test_get_repo_config_exception ________________________
self = <stampbot.webhook_handler.WebhookHandler object at 0x7fe49d1ae030>
installation_id = 12345, repo_full_name = 'octocat/hello-world'
default_branch = 'main', owner_login = 'octocat', owner_type = 'User'
async def _get_repo_config(
self,
installation_id: int,
repo_full_name: str,
default_branch: str,
owner_login: str | None,
owner_type: str | None,
) -> RepoConfig:
"""Get repository configuration from stampbot.toml.
Reads from the repo's default branch first, then falls back to the
organization-wide .github repo if available.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name
default_branch: Repository default branch
owner_login: Repository owner login
owner_type: Repository owner type (Organization/User)
Returns:
RepoConfig instance (default if file not found)
"""
with create_span(
"webhook.get_repo_config",
{"github.repo": repo_full_name, "github.ref": default_branch or "default"},
) as span:
try:
> content = await run_in_threadpool(
github_client.get_repo_file,
installation_id,
repo_full_name,
"stampbot.toml",
default_branch,
)
stampbot/webhook_handler.py:562:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/concurrency.py:32: in run_in_threadpool
return await anyio.to_thread.run_sync(func)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/to_thread.py:63: in run_sync
return await get_async_backend().run_sync_in_worker_thread(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/_backends/_asyncio.py:2502: in run_sync_in_worker_thread
return await future
^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/_backends/_asyncio.py:986: in run
result = context.run(func, *args)
^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1175: in __call__
return self._mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1179: in _mock_call
return self._execute_mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <MagicMock name='github_client.get_repo_file' id='140619858200288'>
args = (12345, 'octocat/hello-world', 'stampbot.toml', 'main'), kwargs = {}
effect = Exception('Network error')
def _execute_mock_call(self, /, *args, **kwargs):
# separate from _increment_mock_call so that awaited functions are
# executed separately from their call, also AsyncMock overrides this method
effect = self.side_effect
if effect is not None:
if _is_exception(effect):
> raise effect
E Exception: Network error
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1234: Exception
During handling of the above exception, another exception occurred:
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7fe49d1ae030>
mock_github_client = <MagicMock name='github_client' id='140619858199952'>
@pytest.mark.asyncio
async def test_get_repo_config_exception(webhook_handler, mock_github_client):
"""Test _get_repo_config handles exceptions gracefully."""
payload = load_fixture("pr_opened_with_autoapprove_label")
# Make get_repo_file raise an exception
mock_github_client.get_repo_file.side_effect = Exception("Network error")
# Should still work, using defaults
> result = await webhook_handler.handle_event("pull_request", payload)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_webhook_handler.py:713:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
stampbot/webhook_handler.py:113: in handle_event
result = await self._handle_pull_request(payload)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/webhook_handler.py:167: in _handle_pull_request
repo_config = await self._get_repo_config(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.webhook_handler.WebhookHandler object at 0x7fe49d1ae030>
installation_id = 12345, repo_full_name = 'octocat/hello-world'
default_branch = 'main', owner_login = 'octocat', owner_type = 'User'
async def _get_repo_config(
self,
installation_id: int,
repo_full_name: str,
default_branch: str,
owner_login: str | None,
owner_type: str | None,
) -> RepoConfig:
"""Get repository configuration from stampbot.toml.
Reads from the repo's default branch first, then falls back to the
organization-wide .github repo if available.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name
default_branch: Repository default branch
owner_login: Repository owner login
owner_type: Repository owner type (Organization/User)
Returns:
RepoConfig instance (default if file not found)
"""
with create_span(
"webhook.get_repo_config",
{"github.repo": repo_full_name, "github.ref": default_branch or "default"},
) as span:
try:
content = await run_in_threadpool(
github_client.get_repo_file,
installation_id,
repo_full_name,
"stampbot.toml",
default_branch,
)
if content:
return self._parse_repo_config(span, content, source_repo=repo_full_name)
org_repo_full_name = None
if (
owner_type == "Organization"
and owner_login
and repo_full_name != f"{owner_login}/.github"
):
org_repo_full_name = f"{owner_login}/.github"
org_content = await run_in_threadpool(
github_client.get_repo_file,
installation_id,
org_repo_full_name,
"stampbot.toml",
None,
)
if org_content:
return self._parse_repo_config(
span, org_content, source_repo=org_repo_full_name
)
repo_config_loads_total.labels(status="default").inc()
logger.info(
"No stampbot.toml found in %s%s, using defaults",
repo_full_name,
f" or {org_repo_full_name}" if org_repo_full_name else "",
extra={"repo": repo_full_name, "org_repo": org_repo_full_name},
)
add_span_attributes(span, {"config.result": "default"})
set_span_ok(span)
return RepoConfig.default()
> except CosmicRayTestingException as e:
^^^^^^^^^^^^^^^^^^^^^^^^^
E NameError: name 'CosmicRayTestingException' is not defined
stampbot/webhook_handler.py:603: NameError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:45:20.299756Z[0m [[32m[1minfo [0m] [1mReceived pull_request event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'pull_request', 'action': 'opened'}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'level': 'info', 'timestamp': '2026-05-16T19:45:20.299756Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_get_repo_config_exception - NameError: name 'CosmicRayTestingException' is not defined
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 203 passed, 3 deselected in 1.81s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ExceptionReplacer, occurrence: 1
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -630,7 +630,7 @@
"""
try:
repo_config = RepoConfig.from_toml(toml_content)
- except ValueError as e:
+ except CosmicRayTestingException as e:
repo_config_loads_total.labels(status="error").inc()
add_span_attributes(
span,........................................................................ [ 34%]
........................................................................ [ 68%]
................................................F
=================================== FAILURES ===================================
____________________ test_invalid_repo_config_posts_review _____________________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7f736c3b3ad0>
mock_github_client = <MagicMock name='github_client' id='140133714701216'>
@pytest.mark.asyncio
async def test_invalid_repo_config_posts_review(webhook_handler, mock_github_client):
"""Test invalid repo config logs and posts a review comment."""
payload = load_fixture("pr_opened_with_autoapprove_label")
mock_github_client.get_repo_file.return_value = 'chatops_required_permission = "invalid"'
result = await webhook_handler.handle_event("pull_request", payload)
> assert result["status"] == "error"
E AssertionError: assert 'success' == 'error'
E
E - error
E + success
tests/test_webhook_handler.py:516: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T20:00:18.034637Z[0m [[32m[1minfo [0m] [1mReceived pull_request event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'pull_request', 'action': 'opened'}[0m
[2m2026-05-16T20:00:18.035653Z[0m [[33m[1mwarning [0m] [1mError loading config from octocat/hello-world: name 'CosmicRayTestingException' is not defined, using defaults[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35mwarning[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'error': "name 'CosmicRayTestingException' is not defined"}[0m
[2m2026-05-16T20:00:18.036479Z[0m [[32m[1minfo [0m] [1mPR #42 has approval label: autoapprove[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'level': 'info', 'timestamp': '2026-05-16T20:00:18.034637Z'}
WARNING stampbot.webhook_handler:webhook_handler.py:605 {'extra': {'repo': 'octocat/hello-world', 'error': "name 'CosmicRayTestingException' is not defined"}, 'event': "Error loading config from octocat/hello-world: name 'CosmicRayTestingException' is not defined, using defaults", 'level': 'warning', 'timestamp': '2026-05-16T20:00:18.035653Z'}
INFO stampbot.webhook_handler:webhook_handler.py:278 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'level': 'info', 'timestamp': '2026-05-16T20:00:18.036479Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_invalid_repo_config_posts_review - AssertionError: assert 'success' == 'error'
- error
+ success
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 192 passed, 3 deselected in 1.59s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ExceptionReplacer, occurrence: 2
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -815,7 +815,7 @@
return success
- except Exception as e:
+ except CosmicRayTestingException as e:
duration = time.time() - start_time
pr_dismissal_duration_seconds.observe(duration)
pr_dismissals_total.labels(trigger_type=trigger_type, status="failure").inc()........................................................................ [ 34%]
........................................................................ [ 68%]
............................................................F
=================================== FAILURES ===================================
_______________________ test_dismiss_approvals_exception _______________________
self = <stampbot.webhook_handler.WebhookHandler object at 0x7f3297c301d0>
installation_id = 12345, repo_full_name = 'octocat/hello-world', pr_number = 42
message = 'Label autoapprove removed', trigger_type = 'label_removed'
async def _dismiss_approvals(
self,
installation_id: int,
repo_full_name: str,
pr_number: int,
message: str,
trigger_type: str,
) -> bool:
"""Dismiss bot approvals on a PR.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name
pr_number: PR number
message: Dismissal message
trigger_type: What triggered the dismissal (label_removed, chatops)
Returns:
True if successful
"""
with create_span(
"webhook.dismiss_approvals",
{
"github.repo": repo_full_name,
"github.pr_number": pr_number,
"dismissal.trigger_type": trigger_type,
},
) as span:
start_time = time.time()
try:
# Find all bot reviews
> review_ids = await run_in_threadpool(
github_client.find_bot_reviews,
installation_id,
repo_full_name,
pr_number,
)
stampbot/webhook_handler.py:772:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/concurrency.py:32: in run_in_threadpool
return await anyio.to_thread.run_sync(func)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/to_thread.py:63: in run_sync
return await get_async_backend().run_sync_in_worker_thread(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/_backends/_asyncio.py:2502: in run_sync_in_worker_thread
return await future
^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/_backends/_asyncio.py:986: in run
result = context.run(func, *args)
^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1175: in __call__
return self._mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1179: in _mock_call
return self._execute_mock_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <MagicMock name='github_client.find_bot_reviews' id='139855271090160'>
args = (12345, 'octocat/hello-world', 42), kwargs = {}
effect = Exception('API error')
def _execute_mock_call(self, /, *args, **kwargs):
# separate from _increment_mock_call so that awaited functions are
# executed separately from their call, also AsyncMock overrides this method
effect = self.side_effect
if effect is not None:
if _is_exception(effect):
> raise effect
E Exception: API error
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/unittest/mock.py:1234: Exception
During handling of the above exception, another exception occurred:
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7f3297c301d0>
mock_github_client = <MagicMock name='github_client' id='139855271088480'>
@pytest.mark.asyncio
async def test_dismiss_approvals_exception(webhook_handler, mock_github_client):
"""Test _dismiss_approvals handles exceptions gracefully."""
payload = load_fixture("pr_unlabeled_autoapprove")
# Make find_bot_reviews raise an exception
mock_github_client.find_bot_reviews.side_effect = Exception("API error")
> result = await webhook_handler.handle_event("pull_request", payload)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_webhook_handler.py:727:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
stampbot/webhook_handler.py:113: in handle_event
result = await self._handle_pull_request(payload)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/webhook_handler.py:332: in _handle_pull_request
success = await self._dismiss_approvals(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.webhook_handler.WebhookHandler object at 0x7f3297c301d0>
installation_id = 12345, repo_full_name = 'octocat/hello-world', pr_number = 42
message = 'Label autoapprove removed', trigger_type = 'label_removed'
async def _dismiss_approvals(
self,
installation_id: int,
repo_full_name: str,
pr_number: int,
message: str,
trigger_type: str,
) -> bool:
"""Dismiss bot approvals on a PR.
Args:
installation_id: GitHub App installation ID
repo_full_name: Repository full name
pr_number: PR number
message: Dismissal message
trigger_type: What triggered the dismissal (label_removed, chatops)
Returns:
True if successful
"""
with create_span(
"webhook.dismiss_approvals",
{
"github.repo": repo_full_name,
"github.pr_number": pr_number,
"dismissal.trigger_type": trigger_type,
},
) as span:
start_time = time.time()
try:
# Find all bot reviews
review_ids = await run_in_threadpool(
github_client.find_bot_reviews,
installation_id,
repo_full_name,
pr_number,
)
add_span_attributes(span, {"dismissal.reviews_found": len(review_ids)})
if not review_ids:
logger.info(
"No bot approvals found on PR #%d",
pr_number,
extra={"repo": repo_full_name, "pr_number": pr_number},
)
duration = time.time() - start_time
pr_dismissal_duration_seconds.observe(duration)
pr_dismissals_total.labels(trigger_type=trigger_type, status="success").inc()
add_span_attributes(span, {"dismissal.result": "no_reviews"})
set_span_ok(span)
return True
# Dismiss each review
success = True
for review_id in review_ids:
result = await run_in_threadpool(
github_client.dismiss_approval,
installation_id,
repo_full_name,
pr_number,
review_id,
message,
)
success = success and result
duration = time.time() - start_time
pr_dismissal_duration_seconds.observe(duration)
status = "success" if success else "failure"
pr_dismissals_total.labels(trigger_type=trigger_type, status=status).inc()
add_span_attributes(span, {"dismissal.result": status})
set_span_ok(span)
return success
> except CosmicRayTestingException as e:
^^^^^^^^^^^^^^^^^^^^^^^^^
E NameError: name 'CosmicRayTestingException' is not defined
stampbot/webhook_handler.py:818: NameError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:42:14.144608Z[0m [[32m[1minfo [0m] [1mReceived pull_request event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'pull_request', 'action': 'unlabeled'}[0m
[2m2026-05-16T19:42:14.145156Z[0m [[32m[1minfo [0m] [1mNo stampbot.toml found in octocat/hello-world, using defaults[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'org_repo': None}[0m
[2m2026-05-16T19:42:14.145701Z[0m [[32m[1minfo [0m] [1mApproval label autoapprove removed from PR #42[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'pull_request', 'action': 'unlabeled'}, 'event': 'Received pull_request event', 'level': 'info', 'timestamp': '2026-05-16T19:42:14.144608Z'}
INFO stampbot.webhook_handler:webhook_handler.py:593 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'level': 'info', 'timestamp': '2026-05-16T19:42:14.145156Z'}
INFO stampbot.webhook_handler:webhook_handler.py:320 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'Approval label autoapprove removed from PR #42', 'level': 'info', 'timestamp': '2026-05-16T19:42:14.145701Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_dismiss_approvals_exception - NameError: name 'CosmicRayTestingException' is not defined
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 204 passed, 3 deselected in 1.82s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/NumberReplacer, occurrence: 0
--- mutation diff --- --- astampbot/webhook_handler.py +++ bstampbot/webhook_handler.py @@ -29,7 +29,7 @@ logger = get_logger(__name__) # Security: Maximum length for user-controlled strings to prevent DoS -MAX_COMMENT_LENGTH = 65536 # 64KB - generous but prevents abuse +MAX_COMMENT_LENGTH = 65537 # 64KB - generous but prevents abuse class WebhookHandler:
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.61s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/NumberReplacer, occurrence: 1
--- mutation diff --- --- astampbot/webhook_handler.py +++ bstampbot/webhook_handler.py @@ -29,7 +29,7 @@ logger = get_logger(__name__) # Security: Maximum length for user-controlled strings to prevent DoS -MAX_COMMENT_LENGTH = 65536 # 64KB - generous but prevents abuse +MAX_COMMENT_LENGTH = 65535 # 64KB - generous but prevents abuse class WebhookHandler:
........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.65s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/NumberReplacer, occurrence: 2
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -153,7 +153,7 @@
"webhook.handle_pull_request",
{
"github.repo": repo_full_name or "unknown",
- "github.pr_number": pr_number or 0,
+ "github.pr_number": pr_number or 1,
"github.action": action or "unknown",
},
) as span:........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.70s
SURVIVED
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.SURVIVED
operator: core/NumberReplacer, occurrence: 3
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -153,7 +153,7 @@
"webhook.handle_pull_request",
{
"github.repo": repo_full_name or "unknown",
- "github.pr_number": pr_number or 0,
+ "github.pr_number": pr_number or -1,
"github.action": action or "unknown",
},
) as span:........................................................................ [ 34%] ........................................................................ [ 68%] ................................................................... [100%] 211 passed, 3 deselected in 1.60s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/NumberReplacer, occurrence: 4
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -383,7 +383,7 @@
if "pull_request" in payload.get("issue", {}):
pr_url = payload["issue"]["pull_request"]["url"]
# Extract PR number from URL
- pr_number = int(pr_url.split("/")[-1])
+ pr_number = int(pr_url.split("/")[- 2])
elif "pull_request" in payload:
pr_number = payload["pull_request"]["number"]
else:........................................................................ [ 34%]
........................................................................ [ 68%]
.................................F
=================================== FAILURES ===================================
__________________________ test_issue_comment_approve __________________________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7f4c6da5f330>
mock_github_client = <MagicMock name='github_client' id='139966234357648'>
@pytest.mark.asyncio
async def test_issue_comment_approve(webhook_handler, mock_github_client):
"""Test @stampbot approve command."""
payload = load_fixture("issue_comment_approve")
> result = await webhook_handler.handle_event("issue_comment", payload)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_webhook_handler.py:257:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
stampbot/webhook_handler.py:119: in handle_event
result = await self._handle_pr_comment(payload)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.webhook_handler.WebhookHandler object at 0x7f4c6da5f330>
payload = {'action': 'created', 'comment': {'body': '@stampbot approve\n\nLGTM, approving this PR.', 'created_at': '2025-01-10T1...U='}, 'issue': {'assignee': None, 'assignees': [], 'body': 'This PR adds a new feature.', 'closed_at': None, ...}, ...}
async def _handle_pr_comment(self, payload: dict[str, Any]) -> dict[str, Any]:
"""Handle PR comment events for chatops.
Args:
payload: Event payload
Returns:
Response dictionary
"""
comment = payload.get("comment", {})
comment_body = comment.get("body", "")
# Security: limit comment length to prevent DoS
if len(comment_body) > MAX_COMMENT_LENGTH:
return {"status": "ignored", "message": "Comment too long"}
comment_body = comment_body.lower().strip()
# Check if bot is mentioned
if "@stampbot" not in comment_body:
return {"status": "ignored", "message": "Bot not mentioned"}
# Extract PR info
if "pull_request" in payload.get("issue", {}):
pr_url = payload["issue"]["pull_request"]["url"]
# Extract PR number from URL
> pr_number = int(pr_url.split("/")[- 2])
^^^^^^^^^^^^^^^^^^^^^^^^^^^
E ValueError: invalid literal for int() with base 10: 'pulls'
stampbot/webhook_handler.py:386: ValueError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:38:54.945681Z[0m [[32m[1minfo [0m] [1mReceived issue_comment event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'issue_comment', 'action': 'created'}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'level': 'info', 'timestamp': '2026-05-16T19:38:54.945681Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_issue_comment_approve - ValueError: invalid literal for int() with base 10: 'pulls'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 177 passed, 3 deselected in 1.16s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/NumberReplacer, occurrence: 5
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -383,7 +383,7 @@
if "pull_request" in payload.get("issue", {}):
pr_url = payload["issue"]["pull_request"]["url"]
# Extract PR number from URL
- pr_number = int(pr_url.split("/")[-1])
+ pr_number = int(pr_url.split("/")[- 0])
elif "pull_request" in payload:
pr_number = payload["pull_request"]["number"]
else:........................................................................ [ 34%]
........................................................................ [ 68%]
.................................F
=================================== FAILURES ===================================
__________________________ test_issue_comment_approve __________________________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7fd501257330>
mock_github_client = <MagicMock name='github_client' id='140552824483728'>
@pytest.mark.asyncio
async def test_issue_comment_approve(webhook_handler, mock_github_client):
"""Test @stampbot approve command."""
payload = load_fixture("issue_comment_approve")
> result = await webhook_handler.handle_event("issue_comment", payload)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_webhook_handler.py:257:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
stampbot/webhook_handler.py:119: in handle_event
result = await self._handle_pr_comment(payload)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.webhook_handler.WebhookHandler object at 0x7fd501257330>
payload = {'action': 'created', 'comment': {'body': '@stampbot approve\n\nLGTM, approving this PR.', 'created_at': '2025-01-10T1...U='}, 'issue': {'assignee': None, 'assignees': [], 'body': 'This PR adds a new feature.', 'closed_at': None, ...}, ...}
async def _handle_pr_comment(self, payload: dict[str, Any]) -> dict[str, Any]:
"""Handle PR comment events for chatops.
Args:
payload: Event payload
Returns:
Response dictionary
"""
comment = payload.get("comment", {})
comment_body = comment.get("body", "")
# Security: limit comment length to prevent DoS
if len(comment_body) > MAX_COMMENT_LENGTH:
return {"status": "ignored", "message": "Comment too long"}
comment_body = comment_body.lower().strip()
# Check if bot is mentioned
if "@stampbot" not in comment_body:
return {"status": "ignored", "message": "Bot not mentioned"}
# Extract PR info
if "pull_request" in payload.get("issue", {}):
pr_url = payload["issue"]["pull_request"]["url"]
# Extract PR number from URL
> pr_number = int(pr_url.split("/")[- 0])
^^^^^^^^^^^^^^^^^^^^^^^^^^^
E ValueError: invalid literal for int() with base 10: 'https:'
stampbot/webhook_handler.py:386: ValueError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:31:14.842408Z[0m [[32m[1minfo [0m] [1mReceived issue_comment event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'issue_comment', 'action': 'created'}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'level': 'info', 'timestamp': '2026-05-16T19:31:14.842408Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_issue_comment_approve - ValueError: invalid literal for int() with base 10: 'https:'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 177 passed, 3 deselected in 1.16s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/NumberReplacer, occurrence: 6
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -448,7 +448,7 @@
set_span_ok(span)
return {"status": "ignored", "message": "No command found"}
- command = command_match.group(1).lower()
+ command = command_match.group( 2).lower()
add_span_attributes(span, {"chatops.command": command})
if command in repo_config.approve_commands + repo_config.unapprove_commands:........................................................................ [ 34%]
........................................................................ [ 68%]
.................................F
=================================== FAILURES ===================================
__________________________ test_issue_comment_approve __________________________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7ff76713dff0>
mock_github_client = <MagicMock name='github_client' id='140700563500944'>
@pytest.mark.asyncio
async def test_issue_comment_approve(webhook_handler, mock_github_client):
"""Test @stampbot approve command."""
payload = load_fixture("issue_comment_approve")
> result = await webhook_handler.handle_event("issue_comment", payload)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
tests/test_webhook_handler.py:257:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
stampbot/webhook_handler.py:119: in handle_event
result = await self._handle_pr_comment(payload)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stampbot.webhook_handler.WebhookHandler object at 0x7ff76713dff0>
payload = {'action': 'created', 'comment': {'body': '@stampbot approve\n\nLGTM, approving this PR.', 'created_at': '2025-01-10T1...U='}, 'issue': {'assignee': None, 'assignees': [], 'body': 'This PR adds a new feature.', 'closed_at': None, ...}, ...}
async def _handle_pr_comment(self, payload: dict[str, Any]) -> dict[str, Any]:
"""Handle PR comment events for chatops.
Args:
payload: Event payload
Returns:
Response dictionary
"""
comment = payload.get("comment", {})
comment_body = comment.get("body", "")
# Security: limit comment length to prevent DoS
if len(comment_body) > MAX_COMMENT_LENGTH:
return {"status": "ignored", "message": "Comment too long"}
comment_body = comment_body.lower().strip()
# Check if bot is mentioned
if "@stampbot" not in comment_body:
return {"status": "ignored", "message": "Bot not mentioned"}
# Extract PR info
if "pull_request" in payload.get("issue", {}):
pr_url = payload["issue"]["pull_request"]["url"]
# Extract PR number from URL
pr_number = int(pr_url.split("/")[-1])
elif "pull_request" in payload:
pr_number = payload["pull_request"]["number"]
else:
return {"status": "error", "message": "Not a PR comment"}
repo = payload.get("repository", {})
repo_full_name = repo.get("full_name")
repo_default_branch = repo.get("default_branch") or "main"
repo_owner = repo.get("owner", {})
owner_login = repo_owner.get("login")
owner_type = repo_owner.get("type")
installation_id = payload.get("installation", {}).get("id")
commenter = comment.get("user", {}).get("login", "unknown")
with create_span(
"webhook.handle_chatops",
{
"github.repo": repo_full_name or "unknown",
"github.pr_number": pr_number,
"chatops.commenter": commenter,
},
) as span:
if not all([pr_number, repo_full_name, installation_id]):
logger.warning("Missing required fields in comment event")
add_span_attributes(span, {"chatops.result": "missing_fields"})
set_span_ok(span)
return {"status": "error", "message": "Missing required fields"}
# Get repository configuration
repo_config = await self._get_repo_config(
installation_id,
repo_full_name,
repo_default_branch,
owner_login,
owner_type,
)
if repo_config.config_error:
add_span_attributes(
span,
{
"chatops.result": "config_error",
"chatops.config_error": repo_config.config_error,
},
)
set_span_ok(span)
return {
"status": "error",
"message": "Invalid repository configuration",
}
if not repo_config.chatops_enabled:
add_span_attributes(span, {"chatops.result": "disabled"})
set_span_ok(span)
return {"status": "ignored", "message": "Chatops not enabled"}
# Parse command
command_match = re.search(r"@stampbot\s+(\w+)", comment_body)
if not command_match:
chatops_commands_total.labels(command="none", status="ignored").inc()
add_span_attributes(span, {"chatops.result": "no_command"})
set_span_ok(span)
return {"status": "ignored", "message": "No command found"}
> command = command_match.group( 2).lower()
^^^^^^^^^^^^^^^^^^^^^^^
E IndexError: no such group
stampbot/webhook_handler.py:451: IndexError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T20:04:23.163698Z[0m [[32m[1minfo [0m] [1mReceived issue_comment event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'issue_comment', 'action': 'created'}[0m
[2m2026-05-16T20:04:23.164271Z[0m [[32m[1minfo [0m] [1mNo stampbot.toml found in octocat/hello-world, using defaults[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'org_repo': None}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'level': 'info', 'timestamp': '2026-05-16T20:04:23.163698Z'}
INFO stampbot.webhook_handler:webhook_handler.py:593 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'level': 'info', 'timestamp': '2026-05-16T20:04:23.164271Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_issue_comment_approve - IndexError: no such group
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 177 passed, 3 deselected in 1.17s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/NumberReplacer, occurrence: 7
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -448,7 +448,7 @@
set_span_ok(span)
return {"status": "ignored", "message": "No command found"}
- command = command_match.group(1).lower()
+ command = command_match.group( 0).lower()
add_span_attributes(span, {"chatops.command": command})
if command in repo_config.approve_commands + repo_config.unapprove_commands:........................................................................ [ 34%]
........................................................................ [ 68%]
.................................F
=================================== FAILURES ===================================
__________________________ test_issue_comment_approve __________________________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7fb872f5f330>
mock_github_client = <MagicMock name='github_client' id='140430179921808'>
@pytest.mark.asyncio
async def test_issue_comment_approve(webhook_handler, mock_github_client):
"""Test @stampbot approve command."""
payload = load_fixture("issue_comment_approve")
result = await webhook_handler.handle_event("issue_comment", payload)
> assert result["status"] == "success"
E AssertionError: assert 'ignored' == 'success'
E
E - success
E + ignored
tests/test_webhook_handler.py:259: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:51:08.007554Z[0m [[32m[1minfo [0m] [1mReceived issue_comment event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'issue_comment', 'action': 'created'}[0m
[2m2026-05-16T19:51:08.008090Z[0m [[32m[1minfo [0m] [1mNo stampbot.toml found in octocat/hello-world, using defaults[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'org_repo': None}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'level': 'info', 'timestamp': '2026-05-16T19:51:08.007554Z'}
INFO stampbot.webhook_handler:webhook_handler.py:593 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'level': 'info', 'timestamp': '2026-05-16T19:51:08.008090Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_issue_comment_approve - AssertionError: assert 'ignored' == 'success'
- success
+ ignored
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 177 passed, 3 deselected in 1.11s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/RemoveDecorator, occurrence: 0
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -38,8 +38,6 @@
def __init__(self) -> None:
"""Initialize webhook handler (lazy initialization)."""
self._webhook_secret: bytes | None = None
-
- @property
def webhook_secret(self) -> bytes:
"""Get webhook secret, initializing if needed.
........................................................................ [ 34%]
........................F
=================================== FAILURES ===================================
_______________________ test_request_with_content_length _______________________
test_client = <starlette.testclient.TestClient object at 0x7f02dc1af770>
def test_request_with_content_length(test_client: TestClient):
"""Test that requests with Content-Length header are tracked."""
# POST with a body will have Content-Length set automatically
> response = test_client.post(
"/webhook",
content=b'{"test": "data"}',
headers={
"Content-Type": "application/json",
"X-GitHub-Event": "ping",
"X-Hub-Signature-256": "sha256=invalid",
},
)
tests/test_main.py:69:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:546: in post
return super().post(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1144: in post
return self.request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:445: in request
return super().request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:825: in request
return self.send(request, auth=auth, follow_redirects=follow_redirects)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:914: in send
response = self._send_handling_auth(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:942: in _send_handling_auth
response = self._send_handling_redirects(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:979: in _send_handling_redirects
response = self._send_single_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1014: in _send_single_request
response = transport.handle_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:348: in handle_request
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:345: in handle_request
portal.call(self.app, scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:334: in call
return cast(T_Retval, self.start_task_soon(func, *args).result())
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:443: in result
return self.__get_result()
^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/concurrent/futures/_base.py:395: in __get_result
raise self._exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:259: in _call_func
retval = await retval_or_awaitable
^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1135: in __call__
await super().__call__(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:107: in __call__
await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:186: in __call__
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:164: in __call__
await self.app(scope, receive, _send)
stampbot/main.py:205: in __call__
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:191: in __call__
with recv_stream, send_stream, collapse_excgroups():
^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/contextlib.py:162: in __exit__
self.gen.throw(value)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_utils.py:85: in collapse_excgroups
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:193: in __call__
response = await self.dispatch_func(request, call_next)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:120: in metrics_middleware
response = await call_next(request)
^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:168: in call_next
raise app_exc from app_exc.__cause__ or app_exc.__context__
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:144: in coro
await self.app(scope, receive_or_disconnect, send_no_error)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/exceptions.py:63: in __call__
await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/middleware/asyncexitstack.py:18: in __call__
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:716: in __call__
await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:736: in app
await route.handle(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:290: in handle
await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:115: in app
await wrap_app_handling_exceptions(app, request)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:101: in app
response = await f(request)
^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:355: in app
raw_response = await run_endpoint_function(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:243: in run_endpoint_function
return await dependant.call(**values)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:296: in webhook
if not webhook_handler.verify_signature(body, x_hub_signature_256):
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/webhook_handler.py:72: in verify_signature
"sha256=" + hmac.new(self.webhook_secret, payload, hashlib.sha256).hexdigest()
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/hmac.py:218: in new
return HMAC(key, msg, digestmod)
^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <hmac.HMAC object at 0x7f02d7ebd940>
key = <bound method WebhookHandler.webhook_secret of <stampbot.webhook_handler.WebhookHandler object at 0x7f02dc1b2e40>>
msg = b'{"test": "data"}', digestmod = <built-in function openssl_sha256>
def __init__(self, key, msg=None, digestmod=''):
"""Create a new HMAC object.
key: bytes or buffer, key for the keyed hash object.
msg: bytes or buffer, Initial input for the hash or None.
digestmod: A hash name suitable for hashlib.new(). *OR*
A hashlib constructor returning a new hash object. *OR*
A module supporting PEP 247.
Required as of 3.8, despite its position after the optional
msg argument. Passing it as a keyword argument is
recommended, though not required for legacy API reasons.
"""
if not isinstance(key, (bytes, bytearray)):
> raise TypeError(f"key: expected bytes or bytearray, "
f"but got {type(key).__name__!r}")
E TypeError: key: expected bytes or bytearray, but got 'method'
/opt/hostedtoolcache/Python/3.14.3/x64/lib/python3.14/hmac.py:71: TypeError
=========================== short test summary info ============================
FAILED tests/test_main.py::test_request_with_content_length - TypeError: key: expected bytes or bytearray, but got 'method'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 96 passed, 3 deselected in 1.17s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ZeroIterationForLoop, occurrence: 0
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -207,7 +207,7 @@
}
if action == "opened":
- for label in repo_config.approval_labels:
+ for label in []:
label_exists = await run_in_threadpool(
github_client.repo_has_label,
installation_id,........................................................................ [ 34%]
........................................................................ [ 68%]
.....................F
=================================== FAILURES ===================================
__________________ test_pr_opened_missing_label_logs_warning ___________________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7f381dfcc830>
mock_github_client = <MagicMock name='github_client' id='139878935538928'>
@pytest.mark.asyncio
async def test_pr_opened_missing_label_logs_warning(webhook_handler, mock_github_client):
"""Test missing approval label triggers label check."""
payload = load_fixture("pr_opened_with_autoapprove_label")
mock_github_client.repo_has_label.return_value = False
result = await webhook_handler.handle_event("pull_request", payload)
assert result["status"] == "success"
> assert mock_github_client.repo_has_label.call_count == 2
E AssertionError: assert 0 == 2
E + where 0 = <MagicMock name='github_client.repo_has_label' id='139878935536240'>.call_count
E + where <MagicMock name='github_client.repo_has_label' id='139878935536240'> = <MagicMock name='github_client' id='139878935538928'>.repo_has_label
tests/test_webhook_handler.py:101: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:28:52.002393Z[0m [[32m[1minfo [0m] [1mReceived pull_request event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'pull_request', 'action': 'opened'}[0m
[2m2026-05-16T19:28:52.002930Z[0m [[32m[1minfo [0m] [1mNo stampbot.toml found in octocat/hello-world, using defaults[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'org_repo': None}[0m
[2m2026-05-16T19:28:52.003466Z[0m [[32m[1minfo [0m] [1mPR #42 has approval label: autoapprove[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'level': 'info', 'timestamp': '2026-05-16T19:28:52.002393Z'}
INFO stampbot.webhook_handler:webhook_handler.py:593 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'level': 'info', 'timestamp': '2026-05-16T19:28:52.002930Z'}
INFO stampbot.webhook_handler:webhook_handler.py:278 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'level': 'info', 'timestamp': '2026-05-16T19:28:52.003466Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_opened_missing_label_logs_warning - AssertionError: assert 0 == 2
+ where 0 = <MagicMock name='github_client.repo_has_label' id='139878935536240'>.call_count
+ where <MagicMock name='github_client.repo_has_label' id='139878935536240'> = <MagicMock name='github_client' id='139878935538928'>.repo_has_label
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 165 passed, 3 deselected in 1.07s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ZeroIterationForLoop, occurrence: 1
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -233,7 +233,7 @@
pr_title = pr.get("title", "")
pr_author = pr.get("user", {}).get("login", "")
- for label in labels:
+ for label in []:
if label in repo_config.approval_labels:
# Check if team membership verification is needed
author_team_slugs: list[str] | None = None........................................................................ [ 34%]
........................................................................ [ 68%]
...................F
=================================== FAILURES ===================================
____________________ test_pr_opened_with_autoapprove_label _____________________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7ff76440be10>
mock_github_client = <MagicMock name='github_client' id='140700514009840'>
@pytest.mark.asyncio
async def test_pr_opened_with_autoapprove_label(webhook_handler, mock_github_client):
"""Test PR opened with autoapprove label triggers approval."""
payload = load_fixture("pr_opened_with_autoapprove_label")
result = await webhook_handler.handle_event("pull_request", payload)
> assert result["status"] == "success"
E AssertionError: assert 'ignored' == 'success'
E
E - success
E + ignored
tests/test_webhook_handler.py:70: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T19:37:11.471483Z[0m [[32m[1minfo [0m] [1mReceived pull_request event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'pull_request', 'action': 'opened'}[0m
[2m2026-05-16T19:37:11.472113Z[0m [[32m[1minfo [0m] [1mNo stampbot.toml found in octocat/hello-world, using defaults[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'org_repo': None}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'level': 'info', 'timestamp': '2026-05-16T19:37:11.471483Z'}
INFO stampbot.webhook_handler:webhook_handler.py:593 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'level': 'info', 'timestamp': '2026-05-16T19:37:11.472113Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_opened_with_autoapprove_label - AssertionError: assert 'ignored' == 'success'
- success
+ ignored
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 163 passed, 3 deselected in 1.06s
worker outcome: WorkerOutcome.NORMAL
test outcome: TestOutcome.KILLED
operator: core/ZeroIterationForLoop, occurrence: 2
--- mutation diff ---
--- astampbot/webhook_handler.py
+++ bstampbot/webhook_handler.py
@@ -793,7 +793,7 @@
# Dismiss each review
success = True
- for review_id in review_ids:
+ for review_id in []:
result = await run_in_threadpool(
github_client.dismiss_approval,
installation_id,........................................................................ [ 34%]
........................................................................ [ 68%]
.........................F
=================================== FAILURES ===================================
________________________ test_pr_unlabeled_autoapprove _________________________
webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7f8313101450>
mock_github_client = <MagicMock name='github_client' id='140201004639840'>
@pytest.mark.asyncio
async def test_pr_unlabeled_autoapprove(webhook_handler, mock_github_client):
"""Test removing autoapprove label dismisses approvals."""
payload = load_fixture("pr_unlabeled_autoapprove")
mock_github_client.find_bot_reviews.return_value = [111, 222] # Mock review IDs
result = await webhook_handler.handle_event("pull_request", payload)
assert result["status"] == "success"
assert "dismissed" in result["message"].lower()
mock_github_client.find_bot_reviews.assert_called_once()
> assert mock_github_client.dismiss_approval.call_count == 2
E AssertionError: assert 0 == 2
E + where 0 = <MagicMock name='github_client.dismiss_approval' id='140201004638832'>.call_count
E + where <MagicMock name='github_client.dismiss_approval' id='140201004638832'> = <MagicMock name='github_client' id='140201004639840'>.dismiss_approval
tests/test_webhook_handler.py:155: AssertionError
----------------------------- Captured stdout call -----------------------------
[2m2026-05-16T20:09:43.021039Z[0m [[32m[1minfo [0m] [1mReceived pull_request event [0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'event_type': 'pull_request', 'action': 'unlabeled'}[0m
[2m2026-05-16T20:09:43.021653Z[0m [[32m[1minfo [0m] [1mNo stampbot.toml found in octocat/hello-world, using defaults[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'org_repo': None}[0m
[2m2026-05-16T20:09:43.022169Z[0m [[32m[1minfo [0m] [1mApproval label autoapprove removed from PR #42[0m [36m_logger[0m=[35m<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>[0m [36m_name[0m=[35minfo[0m [36mextra[0m=[35m{'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}[0m
------------------------------ Captured log call -------------------------------
INFO stampbot.webhook_handler:webhook_handler.py:105 {'extra': {'event_type': 'pull_request', 'action': 'unlabeled'}, 'event': 'Received pull_request event', 'level': 'info', 'timestamp': '2026-05-16T20:09:43.021039Z'}
INFO stampbot.webhook_handler:webhook_handler.py:593 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'level': 'info', 'timestamp': '2026-05-16T20:09:43.021653Z'}
INFO stampbot.webhook_handler:webhook_handler.py:320 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'Approval label autoapprove removed from PR #42', 'level': 'info', 'timestamp': '2026-05-16T20:09:43.022169Z'}
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_unlabeled_autoapprove - AssertionError: assert 0 == 2
+ where 0 = <MagicMock name='github_client.dismiss_approval' id='140201004638832'>.call_count
+ where <MagicMock name='github_client.dismiss_approval' id='140201004638832'> = <MagicMock name='github_client' id='140201004639840'>.dismiss_approval
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 169 passed, 3 deselected in 1.15s