complete restructure
This commit is contained in:
@@ -0,0 +1,176 @@
|
||||
"""Tests for the prompts module (prompt constants, layout parsing, user info)."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import UTC, datetime
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
import pytest
|
||||
|
||||
from vibe_bot.config import (
|
||||
IMAGE_GEN_SIZE_LANDSCAPE,
|
||||
IMAGE_GEN_SIZE_PORTRAIT,
|
||||
IMAGE_GEN_SIZE_SQUARE,
|
||||
)
|
||||
from vibe_bot.prompts import (
|
||||
IMAGE_PROMPT_SYSTEM_PROMPT_TEMPLATE,
|
||||
LAYOUT_SIZES,
|
||||
RESPONSE_LENGTH_HINT,
|
||||
build_system_prompt,
|
||||
get_user_info,
|
||||
parse_image_layout,
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_ctx() -> MagicMock:
|
||||
"""A minimal Discord user for get_user_info."""
|
||||
author = MagicMock()
|
||||
author.name = "testuser"
|
||||
author.id = "12345"
|
||||
author.global_name = "Test User"
|
||||
author.nick = "tester"
|
||||
author.top_role.name = "@everyone"
|
||||
author.activities = []
|
||||
author.joined_at = None
|
||||
author.created_at = None
|
||||
return author
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_author_with_member_data() -> MagicMock:
|
||||
"""A Discord user with full member data (role + activity + timestamps)."""
|
||||
author = MagicMock()
|
||||
author.name = "testuser"
|
||||
author.id = "12345"
|
||||
author.global_name = "Test User"
|
||||
author.nick = "tester"
|
||||
author.top_role.name = "Admin"
|
||||
activity = MagicMock()
|
||||
activity.name = "Chess"
|
||||
author.activities = [activity]
|
||||
author.joined_at = datetime(2024, 1, 15, tzinfo=UTC)
|
||||
author.created_at = datetime(2023, 6, 1, tzinfo=UTC)
|
||||
return author
|
||||
|
||||
|
||||
def test_build_system_prompt_contains_personality_hint_and_user_info() -> None:
|
||||
"""build_system_prompt assembles personality + length hint + user info block."""
|
||||
result = build_system_prompt("you are a butler", "Username: alice")
|
||||
assert result.startswith("you are a butler")
|
||||
assert RESPONSE_LENGTH_HINT in result
|
||||
assert "User Information:\nUsername: alice" in result
|
||||
|
||||
|
||||
def test_layout_sizes_map_to_config() -> None:
|
||||
"""LAYOUT_SIZES maps each layout to its configured canvas size."""
|
||||
assert LAYOUT_SIZES["portrait"] == IMAGE_GEN_SIZE_PORTRAIT
|
||||
assert LAYOUT_SIZES["landscape"] == IMAGE_GEN_SIZE_LANDSCAPE
|
||||
assert LAYOUT_SIZES["square"] == IMAGE_GEN_SIZE_SQUARE
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("response", "expected"),
|
||||
[
|
||||
("portrait", "portrait"),
|
||||
("landscape", "landscape"),
|
||||
("square", "square"),
|
||||
(" Portrait ", "portrait"),
|
||||
("LANDSCAPE", "landscape"),
|
||||
("square.", "square"),
|
||||
("I would use portrait.", "portrait"),
|
||||
("This scene is best as landscape.", "landscape"),
|
||||
("A square composition works here.", "square"),
|
||||
],
|
||||
)
|
||||
def test_parse_image_layout_valid(response: str, expected: str) -> None:
|
||||
"""Valid LLM layout responses parse to the right layout."""
|
||||
assert parse_image_layout(response) == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"response",
|
||||
[
|
||||
"",
|
||||
" ",
|
||||
"banana",
|
||||
"1024x1024",
|
||||
"tall and wide",
|
||||
"squareness", # word boundary should prevent a match on "square"
|
||||
],
|
||||
)
|
||||
def test_parse_image_layout_defaults_to_square(response: str) -> None:
|
||||
"""Empty or malformed responses fall back to square."""
|
||||
assert parse_image_layout(response) == "square"
|
||||
|
||||
|
||||
def test_image_prompt_system_prompt_covers_key_details() -> None:
|
||||
"""The prompt-rewrite system prompt forces explicit detail on all aspects."""
|
||||
prompt = IMAGE_PROMPT_SYSTEM_PROMPT_TEMPLATE.format(layout="square")
|
||||
lowered = prompt.lower()
|
||||
assert "square" in prompt
|
||||
assert "exact text" in lowered
|
||||
assert "composition" in lowered
|
||||
assert "style" in lowered
|
||||
assert "canada goose" in lowered
|
||||
assert "only the image generation prompt" in lowered
|
||||
assert "exactly as written" in lowered
|
||||
assert "fountain pen wearing pants" in lowered
|
||||
assert "centaur" in lowered
|
||||
assert "not a person riding a horse" in lowered
|
||||
|
||||
|
||||
def test_get_user_info_minimal(mock_ctx: MagicMock) -> None:
|
||||
"""get_user_info with minimal member data includes the core identity lines."""
|
||||
result = get_user_info(mock_ctx)
|
||||
assert "Username: testuser" in result
|
||||
assert "User ID: 12345" in result
|
||||
assert "Global Name: Test User" in result
|
||||
assert "Nickname: tester" in result
|
||||
|
||||
|
||||
def test_get_user_info_with_member_data(
|
||||
mock_author_with_member_data: MagicMock,
|
||||
) -> None:
|
||||
"""get_user_info with full member data includes roles, activity, timestamps."""
|
||||
result = get_user_info(mock_author_with_member_data)
|
||||
assert "Global Name: Test User" in result
|
||||
assert "Nickname: tester" in result
|
||||
assert "Username: testuser" in result
|
||||
assert "User ID: 12345" in result
|
||||
assert "Top Role: Admin" in result
|
||||
assert "Activities: Chess" in result
|
||||
assert "Joined: 2024-01-15" in result
|
||||
assert "Account Created: 2023-06-01" in result
|
||||
|
||||
|
||||
def test_get_user_info_no_global_name(mock_ctx: MagicMock) -> None:
|
||||
"""Optional fields are omitted when they are empty."""
|
||||
mock_ctx.global_name = None
|
||||
mock_ctx.nick = None
|
||||
mock_ctx.top_role.name = "@everyone"
|
||||
mock_ctx.activities = []
|
||||
|
||||
result = get_user_info(mock_ctx)
|
||||
|
||||
assert "Global Name:" not in result
|
||||
assert "Nickname:" not in result
|
||||
assert "Top Role:" not in result
|
||||
assert "Activities:" not in result
|
||||
assert "Username: testuser" in result
|
||||
assert "User ID: 12345" in result
|
||||
|
||||
|
||||
def test_get_user_info_with_top_role_not_everyone(
|
||||
mock_author_with_member_data: MagicMock,
|
||||
) -> None:
|
||||
"""Top role is included when it is not @everyone."""
|
||||
result = get_user_info(mock_author_with_member_data)
|
||||
assert "Top Role: Admin" in result
|
||||
|
||||
|
||||
def test_get_user_info_no_activities(mock_ctx: MagicMock) -> None:
|
||||
"""The activities line is omitted when there are none."""
|
||||
mock_ctx.activities = []
|
||||
result = get_user_info(mock_ctx)
|
||||
assert "Activities:" not in result
|
||||
Reference in New Issue
Block a user