complete restructure
This commit is contained in:
@@ -0,0 +1,186 @@
|
||||
"""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")
|
||||
Reference in New Issue
Block a user