187 lines
5.2 KiB
Python
187 lines
5.2 KiB
Python
"""No-content logging tests.
|
|
|
|
A recognizable secret string is seeded into message content, image prompts,
|
|
and bot personalities; the logs captured at DEBUG must never contain it,
|
|
while metadata (bot name, user id, message id, counts) must.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import asyncio
|
|
import base64
|
|
import logging
|
|
from typing import Any
|
|
from unittest.mock import AsyncMock, MagicMock, patch
|
|
|
|
import pytest
|
|
|
|
from vibe_bot.services.chat_service import ChatService
|
|
from vibe_bot.services.image_service import ImageService
|
|
from vibe_bot.tests._helpers import invoke
|
|
|
|
SECRET = "SECRET-DO-NOT-LOG-12345"
|
|
|
|
|
|
def _registry() -> MagicMock:
|
|
"""A mock ToolRegistry."""
|
|
reg = MagicMock()
|
|
reg.to_openai_tools.return_value = []
|
|
reg.execute.return_value = "tool result"
|
|
return reg
|
|
|
|
|
|
def _assert_no_secret(
|
|
caplog: pytest.LogCaptureFixture,
|
|
*metadata: str,
|
|
) -> None:
|
|
"""The secret must be absent from every record; the metadata must be present."""
|
|
for record in caplog.records:
|
|
assert (
|
|
SECRET not in record.getMessage()
|
|
), f"Secret leaked in log record: {record.getMessage()!r}"
|
|
assert SECRET not in caplog.text
|
|
for token in metadata:
|
|
assert token in caplog.text, f"Expected metadata {token!r} missing from logs"
|
|
|
|
|
|
def test_chat_service_logs_metadata_not_content(
|
|
caplog: pytest.LogCaptureFixture,
|
|
chat_db: Any,
|
|
mock_ctx: MagicMock,
|
|
) -> None:
|
|
"""A full chat turn (RAG + persist + reply) never logs the message content."""
|
|
caplog.set_level(logging.DEBUG)
|
|
svc = ChatService(chat_db, _registry())
|
|
|
|
with patch(
|
|
"vibe_bot.llm_client.chat_completion_with_tools",
|
|
new=AsyncMock(return_value="A perfectly fine response."),
|
|
):
|
|
asyncio.run(
|
|
svc.handle(
|
|
mock_ctx,
|
|
bot_name="alfred",
|
|
message=f"please remember {SECRET} forever",
|
|
system_prompt="you are a butler",
|
|
response_prefix="alfred response",
|
|
)
|
|
)
|
|
|
|
_assert_no_secret(caplog, "alfred", "12345")
|
|
|
|
|
|
def test_add_message_logs_metadata_not_content(
|
|
caplog: pytest.LogCaptureFixture,
|
|
chat_db: Any,
|
|
) -> None:
|
|
"""add_message (with embedding) never logs the stored content."""
|
|
caplog.set_level(logging.DEBUG)
|
|
|
|
assert chat_db.add_message(
|
|
message_id="msg-1",
|
|
user_id="12345",
|
|
username="testuser",
|
|
content=f"User: tell me about {SECRET}",
|
|
bot_name="alfred",
|
|
channel_id="channel-1",
|
|
guild_id="guild-1",
|
|
)
|
|
assert chat_db.add_message(
|
|
message_id="msg-1_response",
|
|
user_id="bot-123",
|
|
username="test-bot",
|
|
content=f"the bot knew {SECRET}",
|
|
bot_name="alfred",
|
|
channel_id="channel-1",
|
|
guild_id="guild-1",
|
|
role="assistant",
|
|
embed=False,
|
|
)
|
|
|
|
_assert_no_secret(caplog, "msg-1", "12345")
|
|
|
|
|
|
def test_history_lookups_log_metadata_not_content(
|
|
caplog: pytest.LogCaptureFixture,
|
|
chat_db: Any,
|
|
) -> None:
|
|
"""get_user_history / get_bot_history never log message or response content."""
|
|
caplog.set_level(logging.DEBUG)
|
|
chat_db.add_message(
|
|
message_id="msg-2",
|
|
user_id="12345",
|
|
username="testuser",
|
|
content=f"User: what is {SECRET}",
|
|
bot_name="alfred",
|
|
channel_id="channel-1",
|
|
guild_id="guild-1",
|
|
)
|
|
chat_db.add_message(
|
|
message_id="msg-2_response",
|
|
user_id="bot-123",
|
|
username="test-bot",
|
|
content=f"it is {SECRET}, clearly",
|
|
bot_name="alfred",
|
|
channel_id="channel-1",
|
|
guild_id="guild-1",
|
|
role="assistant",
|
|
embed=False,
|
|
)
|
|
|
|
user_history = chat_db.get_user_history("12345", limit=5)
|
|
bot_history = chat_db.get_bot_history("alfred", limit=5)
|
|
|
|
assert len(user_history) == 1
|
|
assert len(bot_history) == 1
|
|
_assert_no_secret(caplog, "msg-2", "alfred")
|
|
|
|
|
|
def test_doodlebob_logs_metadata_not_content(
|
|
caplog: pytest.LogCaptureFixture,
|
|
mock_ctx: MagicMock,
|
|
) -> None:
|
|
"""A doodlebob generation never logs the prompt or the derived image prompt."""
|
|
caplog.set_level(logging.DEBUG)
|
|
db = MagicMock()
|
|
db.get_image_generation_time_estimate.return_value = 12.0
|
|
svc = ImageService(db, MagicMock())
|
|
|
|
with (
|
|
patch(
|
|
"vibe_bot.llm_client.chat_completion_instruct",
|
|
new=AsyncMock(
|
|
side_effect=[
|
|
"portrait",
|
|
f"a very detailed painting of {SECRET}",
|
|
"pass",
|
|
]
|
|
),
|
|
),
|
|
patch(
|
|
"vibe_bot.llm_client.image_generation",
|
|
new=AsyncMock(return_value=base64.b64encode(b"img").decode()),
|
|
),
|
|
):
|
|
asyncio.run(svc.generate(mock_ctx, message=f"draw {SECRET} on the moon"))
|
|
|
|
_assert_no_secret(caplog, "Doodlebob", "12345")
|
|
|
|
|
|
def test_custom_bot_creation_logs_metadata_not_personality(
|
|
caplog: pytest.LogCaptureFixture,
|
|
bot: Any,
|
|
mock_ctx: MagicMock,
|
|
) -> None:
|
|
"""!custom-bot never logs the personality text."""
|
|
caplog.set_level(logging.DEBUG)
|
|
|
|
invoke(
|
|
bot,
|
|
"custom-bot",
|
|
mock_ctx,
|
|
"secretbot",
|
|
personality=f"a butler who knows {SECRET}",
|
|
)
|
|
|
|
_assert_no_secret(caplog, "secretbot", "12345")
|