Files
zk-data-agent/tests/test_prompt_constants.py
Abdelrahman Abdallah aacf0a212a Created src/prompt_constants.py (550+ lines) porting all constants from npm src/constants/:
┌──────────────────┬───────────────────────────┬───────────────────────────────────────────────┐
│ Category         │ npm Source                │ Items                                         │
├──────────────────┼───────────────────────────┼───────────────────────────────────────────────┤
│ Product metadata │ product.ts                │ URLs, base URLs                               │
├──────────────────┼───────────────────────────┼───────────────────────────────────────────────┤
│ System prefixes  │ system.ts                 │ 3 prompt prefixes                             │
├──────────────────┼───────────────────────────┼───────────────────────────────────────────────┤
│ Cyber risk       │ cyberRiskInstruction.ts   │ Safety instruction                            │
├──────────────────┼───────────────────────────┼───────────────────────────────────────────────┤
│ API limits       │ apiLimits.ts              │ 10 image/PDF/media limits                     │
├──────────────────┼───────────────────────────┼───────────────────────────────────────────────┤
│ Tool limits      │ toolLimits.ts             │ 6 result size constants                       │
├──────────────────┼───────────────────────────┼───────────────────────────────────────────────┤
│ Spinner verbs    │ spinnerVerbs.ts           │ 187 whimsical gerunds                         │
├──────────────────┼───────────────────────────┼───────────────────────────────────────────────┤
│ Completion verbs │ turnCompletionVerbs.ts    │ 8 past-tense verbs                            │
├──────────────────┼───────────────────────────┼───────────────────────────────────────────────┤
│ Figures/symbols  │ figures.ts                │ 25 Unicode UI symbols                         │
├──────────────────┼───────────────────────────┼───────────────────────────────────────────────┤
│ XML tags         │ xml.ts                    │ 30+ tag constants                             │
├──────────────────┼───────────────────────────┼───────────────────────────────────────────────┤
│ Messages         │ messages.ts               │ NO_CONTENT_MESSAGE                            │
├──────────────────┼───────────────────────────┼───────────────────────────────────────────────┤
│ Date utilities   │ common.ts                 │ 4 functions                                   │
├──────────────────┼───────────────────────────┼───────────────────────────────────────────────┤
│ Section caching  │ systemPromptSections.ts   │ Memoized/volatile sections                    │
├──────────────────┼───────────────────────────┼───────────────────────────────────────────────┤
│ Output styles    │ outputStyles.ts           │ 3 built-in configs                            │
├──────────────────┼───────────────────────────┼───────────────────────────────────────────────┤
│ Prompt helpers   │ prompts.ts                │ Knowledge cutoff, language, scratchpad, hooks │
└──────────────────┴───────────────────────────┴───────────────────────────────────────────────┘

91 new tests in tests/test_prompt_constants.py. All 17 SQL todos done.
2026-04-08 00:02:53 +02:00

644 lines
20 KiB
Python

"""Tests for prompt_constants module.
Validates that all constants ported from npm src/constants/ are present,
correctly typed, and that helper functions behave as expected.
"""
from __future__ import annotations
import os
import platform
from unittest.mock import patch
import pytest
from src.prompt_constants import (
# Product metadata
PRODUCT_URL,
CLAUDE_AI_BASE_URL,
# System prompt prefixes
DEFAULT_SYSPROMPT_PREFIX,
AGENT_SDK_CLAUDE_CODE_PRESET_PREFIX,
AGENT_SDK_PREFIX,
CLI_SYSPROMPT_PREFIXES,
# Cyber risk
CYBER_RISK_INSTRUCTION,
# API limits
API_IMAGE_MAX_BASE64_SIZE,
IMAGE_TARGET_RAW_SIZE,
IMAGE_MAX_WIDTH,
IMAGE_MAX_HEIGHT,
PDF_TARGET_RAW_SIZE,
API_PDF_MAX_PAGES,
PDF_EXTRACT_SIZE_THRESHOLD,
PDF_MAX_EXTRACT_SIZE,
PDF_MAX_PAGES_PER_READ,
PDF_AT_MENTION_INLINE_THRESHOLD,
API_MAX_MEDIA_PER_REQUEST,
# Tool limits
DEFAULT_MAX_RESULT_SIZE_CHARS,
MAX_TOOL_RESULT_TOKENS,
BYTES_PER_TOKEN,
MAX_TOOL_RESULT_BYTES,
MAX_TOOL_RESULTS_PER_MESSAGE_CHARS,
TOOL_SUMMARY_MAX_LENGTH,
# Spinner verbs
SPINNER_VERBS,
# Turn completion verbs
TURN_COMPLETION_VERBS,
# Figures
BLACK_CIRCLE,
BULLET_OPERATOR,
TEARDROP_ASTERISK,
UP_ARROW,
DOWN_ARROW,
LIGHTNING_BOLT,
EFFORT_LOW,
EFFORT_MEDIUM,
EFFORT_HIGH,
EFFORT_MAX,
PLAY_ICON,
PAUSE_ICON,
REFRESH_ARROW,
CHANNEL_ARROW,
INJECTED_ARROW,
FORK_GLYPH,
DIAMOND_OPEN,
DIAMOND_FILLED,
REFERENCE_MARK,
FLAG_ICON,
BLOCKQUOTE_BAR,
HEAVY_HORIZONTAL,
BRIDGE_SPINNER_FRAMES,
BRIDGE_READY_INDICATOR,
BRIDGE_FAILED_INDICATOR,
# XML tags
COMMAND_NAME_TAG,
COMMAND_MESSAGE_TAG,
COMMAND_ARGS_TAG,
BASH_INPUT_TAG,
BASH_STDOUT_TAG,
BASH_STDERR_TAG,
LOCAL_COMMAND_STDOUT_TAG,
LOCAL_COMMAND_STDERR_TAG,
LOCAL_COMMAND_CAVEAT_TAG,
TERMINAL_OUTPUT_TAGS,
TICK_TAG,
TASK_NOTIFICATION_TAG,
TASK_ID_TAG,
TOOL_USE_ID_TAG,
TASK_TYPE_TAG,
OUTPUT_FILE_TAG,
STATUS_TAG,
SUMMARY_TAG,
REASON_TAG,
WORKTREE_TAG,
WORKTREE_PATH_TAG,
WORKTREE_BRANCH_TAG,
ULTRAPLAN_TAG,
REMOTE_REVIEW_TAG,
REMOTE_REVIEW_PROGRESS_TAG,
TEAMMATE_MESSAGE_TAG,
CHANNEL_MESSAGE_TAG,
CHANNEL_TAG,
CROSS_SESSION_MESSAGE_TAG,
FORK_BOILERPLATE_TAG,
FORK_DIRECTIVE_PREFIX,
COMMON_HELP_ARGS,
COMMON_INFO_ARGS,
# Messages
NO_CONTENT_MESSAGE,
# Date utilities
get_local_iso_date,
get_session_start_date,
reset_session_start_date,
get_local_month_year,
# System prompt section caching
SystemPromptSection,
system_prompt_section,
dangerous_uncached_system_prompt_section,
resolve_system_prompt_sections,
clear_system_prompt_sections,
# Output styles
DEFAULT_OUTPUT_STYLE_NAME,
OutputStyleConfig,
OUTPUT_STYLE_CONFIGS,
# Knowledge cutoff
FRONTIER_MODEL_NAME,
get_knowledge_cutoff,
CLAUDE_MODEL_IDS,
# Prompt sections
HOOKS_SECTION,
SYSTEM_REMINDERS_SECTION,
SUMMARIZE_TOOL_RESULTS_SECTION,
DEFAULT_AGENT_PROMPT,
SYSTEM_PROMPT_DYNAMIC_BOUNDARY,
get_language_section,
get_output_style_section,
get_scratchpad_instructions,
# Error IDs
E_TOOL_USE_SUMMARY_GENERATION_FAILED,
)
# =========================================================================
# Product metadata
# =========================================================================
class TestProductMetadata:
def test_product_url(self):
assert PRODUCT_URL == "https://claude.com/claude-code"
def test_claude_ai_base_url(self):
assert CLAUDE_AI_BASE_URL == "https://claude.ai"
# =========================================================================
# System prompt prefixes
# =========================================================================
class TestSystemPromptPrefixes:
def test_default_prefix_content(self):
assert "Claude Code" in DEFAULT_SYSPROMPT_PREFIX
assert "Anthropic" in DEFAULT_SYSPROMPT_PREFIX
def test_agent_sdk_prefix_content(self):
assert "Agent SDK" in AGENT_SDK_PREFIX
def test_cli_sysprompt_prefixes_is_frozenset(self):
assert isinstance(CLI_SYSPROMPT_PREFIXES, frozenset)
assert len(CLI_SYSPROMPT_PREFIXES) == 3
def test_all_prefixes_in_set(self):
assert DEFAULT_SYSPROMPT_PREFIX in CLI_SYSPROMPT_PREFIXES
assert AGENT_SDK_CLAUDE_CODE_PRESET_PREFIX in CLI_SYSPROMPT_PREFIXES
assert AGENT_SDK_PREFIX in CLI_SYSPROMPT_PREFIXES
# =========================================================================
# Cyber risk
# =========================================================================
class TestCyberRisk:
def test_instruction_mentions_ctf(self):
assert "CTF" in CYBER_RISK_INSTRUCTION
def test_instruction_mentions_dos(self):
assert "DoS" in CYBER_RISK_INSTRUCTION
# =========================================================================
# API limits
# =========================================================================
class TestAPILimits:
def test_image_base64_size(self):
assert API_IMAGE_MAX_BASE64_SIZE == 5 * 1024 * 1024
def test_image_target_raw_size(self):
assert IMAGE_TARGET_RAW_SIZE == (API_IMAGE_MAX_BASE64_SIZE * 3) // 4
def test_image_dimensions(self):
assert IMAGE_MAX_WIDTH == 2000
assert IMAGE_MAX_HEIGHT == 2000
def test_pdf_target_raw_size(self):
assert PDF_TARGET_RAW_SIZE == 20 * 1024 * 1024
def test_pdf_max_pages(self):
assert API_PDF_MAX_PAGES == 100
def test_pdf_extract_threshold(self):
assert PDF_EXTRACT_SIZE_THRESHOLD == 3 * 1024 * 1024
def test_pdf_max_extract_size(self):
assert PDF_MAX_EXTRACT_SIZE == 100 * 1024 * 1024
def test_pdf_pages_per_read(self):
assert PDF_MAX_PAGES_PER_READ == 20
def test_pdf_inline_threshold(self):
assert PDF_AT_MENTION_INLINE_THRESHOLD == 10
def test_media_per_request(self):
assert API_MAX_MEDIA_PER_REQUEST == 100
# =========================================================================
# Tool limits
# =========================================================================
class TestToolLimits:
def test_default_max_result_size(self):
assert DEFAULT_MAX_RESULT_SIZE_CHARS == 50_000
def test_max_tool_result_tokens(self):
assert MAX_TOOL_RESULT_TOKENS == 100_000
def test_bytes_per_token(self):
assert BYTES_PER_TOKEN == 4
def test_max_tool_result_bytes_derived(self):
assert MAX_TOOL_RESULT_BYTES == MAX_TOOL_RESULT_TOKENS * BYTES_PER_TOKEN
assert MAX_TOOL_RESULT_BYTES == 400_000
def test_max_per_message_chars(self):
assert MAX_TOOL_RESULTS_PER_MESSAGE_CHARS == 200_000
def test_tool_summary_max_length(self):
assert TOOL_SUMMARY_MAX_LENGTH == 50
# =========================================================================
# Spinner verbs
# =========================================================================
class TestSpinnerVerbs:
def test_is_tuple(self):
assert isinstance(SPINNER_VERBS, tuple)
def test_count(self):
assert len(SPINNER_VERBS) == 187
def test_first_verb(self):
assert SPINNER_VERBS[0] == "Accomplishing"
def test_last_verb(self):
assert SPINNER_VERBS[-1] == "Zigzagging"
def test_all_strings(self):
for verb in SPINNER_VERBS:
assert isinstance(verb, str)
def test_contains_clauding(self):
assert "Clauding" in SPINNER_VERBS
def test_contains_thinking(self):
assert "Thinking" in SPINNER_VERBS
def test_no_duplicates(self):
assert len(SPINNER_VERBS) == len(set(SPINNER_VERBS))
# =========================================================================
# Turn completion verbs
# =========================================================================
class TestTurnCompletionVerbs:
def test_is_tuple(self):
assert isinstance(TURN_COMPLETION_VERBS, tuple)
def test_count(self):
assert len(TURN_COMPLETION_VERBS) == 8
def test_contains_worked(self):
assert "Worked" in TURN_COMPLETION_VERBS
def test_contains_baked(self):
assert "Baked" in TURN_COMPLETION_VERBS
def test_all_past_tense(self):
# All end in 'd' (past tense)
for verb in TURN_COMPLETION_VERBS:
assert verb[-1] == "d", f"{verb} doesn't end with 'd'"
# =========================================================================
# Figures / UI symbols
# =========================================================================
class TestFigures:
def test_black_circle_is_string(self):
assert isinstance(BLACK_CIRCLE, str)
assert len(BLACK_CIRCLE) == 1
def test_effort_symbols_are_distinct(self):
symbols = {EFFORT_LOW, EFFORT_MEDIUM, EFFORT_HIGH, EFFORT_MAX}
assert len(symbols) == 4
def test_arrows(self):
assert UP_ARROW == "\u2191"
assert DOWN_ARROW == "\u2193"
def test_bridge_spinner_frames(self):
assert isinstance(BRIDGE_SPINNER_FRAMES, tuple)
assert len(BRIDGE_SPINNER_FRAMES) == 4
def test_play_pause_icons(self):
assert PLAY_ICON == "\u25b6"
assert PAUSE_ICON == "\u23f8"
def test_diamond_symbols(self):
assert DIAMOND_OPEN == "\u25c7"
assert DIAMOND_FILLED == "\u25c6"
# =========================================================================
# XML tag constants
# =========================================================================
class TestXMLTags:
def test_command_tags(self):
assert COMMAND_NAME_TAG == "command-name"
assert COMMAND_MESSAGE_TAG == "command-message"
assert COMMAND_ARGS_TAG == "command-args"
def test_bash_tags(self):
assert BASH_INPUT_TAG == "bash-input"
assert BASH_STDOUT_TAG == "bash-stdout"
assert BASH_STDERR_TAG == "bash-stderr"
def test_terminal_output_tags_tuple(self):
assert isinstance(TERMINAL_OUTPUT_TAGS, tuple)
assert len(TERMINAL_OUTPUT_TAGS) == 6
assert BASH_INPUT_TAG in TERMINAL_OUTPUT_TAGS
assert LOCAL_COMMAND_STDOUT_TAG in TERMINAL_OUTPUT_TAGS
def test_tick_tag(self):
assert TICK_TAG == "tick"
def test_task_tags(self):
assert TASK_NOTIFICATION_TAG == "task-notification"
assert TASK_ID_TAG == "task-id"
assert TOOL_USE_ID_TAG == "tool-use-id"
def test_worktree_tags(self):
assert WORKTREE_TAG == "worktree"
assert WORKTREE_PATH_TAG == "worktreePath"
def test_fork_tags(self):
assert FORK_BOILERPLATE_TAG == "fork-boilerplate"
assert FORK_DIRECTIVE_PREFIX == "Your directive: "
def test_common_help_args(self):
assert isinstance(COMMON_HELP_ARGS, tuple)
assert "help" in COMMON_HELP_ARGS
assert "-h" in COMMON_HELP_ARGS
assert "--help" in COMMON_HELP_ARGS
def test_common_info_args(self):
assert isinstance(COMMON_INFO_ARGS, tuple)
assert "list" in COMMON_INFO_ARGS
assert "status" in COMMON_INFO_ARGS
assert "?" in COMMON_INFO_ARGS
# =========================================================================
# Message constants
# =========================================================================
class TestMessages:
def test_no_content_message(self):
assert NO_CONTENT_MESSAGE == "(no content)"
# =========================================================================
# Date utilities
# =========================================================================
class TestDateUtilities:
def test_get_local_iso_date_format(self):
d = get_local_iso_date()
parts = d.split("-")
assert len(parts) == 3
assert len(parts[0]) == 4 # year
assert len(parts[1]) == 2 # month
assert len(parts[2]) == 2 # day
def test_get_local_iso_date_override(self):
with patch.dict(os.environ, {"CLAUDE_CODE_OVERRIDE_DATE": "2025-01-15"}):
assert get_local_iso_date() == "2025-01-15"
def test_get_session_start_date_memoised(self):
reset_session_start_date()
d1 = get_session_start_date()
d2 = get_session_start_date()
assert d1 == d2
def test_reset_session_start_date(self):
reset_session_start_date()
d = get_session_start_date()
assert isinstance(d, str)
reset_session_start_date()
# After reset, should still return valid date
d2 = get_session_start_date()
assert isinstance(d2, str)
def test_get_local_month_year_format(self):
result = get_local_month_year()
parts = result.split()
assert len(parts) == 2
assert parts[1].isdigit()
assert len(parts[1]) == 4
def test_get_local_month_year_override(self):
with patch.dict(os.environ, {"CLAUDE_CODE_OVERRIDE_DATE": "2026-02-15"}):
assert get_local_month_year() == "February 2026"
# =========================================================================
# System prompt section caching
# =========================================================================
class TestSystemPromptSections:
def setup_method(self):
clear_system_prompt_sections()
def test_system_prompt_section_creates_cached(self):
s = system_prompt_section("test", lambda: "hello")
assert s.name == "test"
assert s.cache_break is False
def test_dangerous_uncached_creates_volatile(self):
s = dangerous_uncached_system_prompt_section("test", lambda: "hello", "reason")
assert s.name == "test"
assert s.cache_break is True
def test_resolve_caches_sections(self):
call_count = 0
def compute():
nonlocal call_count
call_count += 1
return f"value-{call_count}"
sections = [system_prompt_section("s1", compute)]
r1 = resolve_system_prompt_sections(sections)
r2 = resolve_system_prompt_sections(sections)
assert r1 == ["value-1"]
assert r2 == ["value-1"] # cached
assert call_count == 1
def test_uncached_recomputes(self):
call_count = 0
def compute():
nonlocal call_count
call_count += 1
return f"value-{call_count}"
sections = [dangerous_uncached_system_prompt_section("s2", compute, "test")]
r1 = resolve_system_prompt_sections(sections)
r2 = resolve_system_prompt_sections(sections)
assert r1 == ["value-1"]
assert r2 == ["value-2"] # recomputed
assert call_count == 2
def test_clear_resets_cache(self):
call_count = 0
def compute():
nonlocal call_count
call_count += 1
return f"value-{call_count}"
sections = [system_prompt_section("s3", compute)]
resolve_system_prompt_sections(sections)
clear_system_prompt_sections()
r = resolve_system_prompt_sections(sections)
assert r == ["value-2"]
assert call_count == 2
def test_resolve_handles_none(self):
sections = [system_prompt_section("nil", lambda: None)]
r = resolve_system_prompt_sections(sections)
assert r == [None]
def test_multiple_sections(self):
sections = [
system_prompt_section("a", lambda: "alpha"),
system_prompt_section("b", lambda: "beta"),
system_prompt_section("c", lambda: None),
]
r = resolve_system_prompt_sections(sections)
assert r == ["alpha", "beta", None]
# =========================================================================
# Output styles
# =========================================================================
class TestOutputStyles:
def test_default_style_name(self):
assert DEFAULT_OUTPUT_STYLE_NAME == "default"
def test_default_style_is_none(self):
assert OUTPUT_STYLE_CONFIGS[DEFAULT_OUTPUT_STYLE_NAME] is None
def test_explanatory_exists(self):
style = OUTPUT_STYLE_CONFIGS["Explanatory"]
assert style is not None
assert style.name == "Explanatory"
assert "explains" in style.description
def test_learning_exists(self):
style = OUTPUT_STYLE_CONFIGS["Learning"]
assert style is not None
assert style.name == "Learning"
assert "hands-on" in style.description
def test_output_style_config_frozen(self):
style = OutputStyleConfig(
name="Test", description="desc", prompt="prompt"
)
with pytest.raises(Exception):
style.name = "other" # type: ignore[misc]
# =========================================================================
# Knowledge cutoff
# =========================================================================
class TestKnowledgeCutoff:
def test_frontier_model_name(self):
assert FRONTIER_MODEL_NAME == "Claude Opus 4.6"
def test_opus_46_cutoff(self):
assert get_knowledge_cutoff("claude-opus-4-6-20250601") == "May 2025"
def test_sonnet_46_cutoff(self):
assert get_knowledge_cutoff("claude-sonnet-4-6-20250801") == "August 2025"
def test_opus_45_cutoff(self):
assert get_knowledge_cutoff("claude-opus-4-5-20250601") == "May 2025"
def test_haiku_cutoff(self):
assert get_knowledge_cutoff("claude-haiku-4-20250201") == "February 2025"
def test_sonnet_4_cutoff(self):
assert get_knowledge_cutoff("claude-sonnet-4-20250114") == "January 2025"
def test_unknown_model_returns_none(self):
assert get_knowledge_cutoff("gpt-4-turbo") is None
def test_claude_model_ids(self):
assert "opus" in CLAUDE_MODEL_IDS
assert "sonnet" in CLAUDE_MODEL_IDS
assert "haiku" in CLAUDE_MODEL_IDS
# =========================================================================
# Prompt section helpers
# =========================================================================
class TestPromptSectionHelpers:
def test_hooks_section_content(self):
assert "hooks" in HOOKS_SECTION
assert "user-prompt-submit-hook" in HOOKS_SECTION
def test_system_reminders_section(self):
assert "system-reminder" in SYSTEM_REMINDERS_SECTION
def test_summarize_tool_results(self):
assert "tool results" in SUMMARIZE_TOOL_RESULTS_SECTION
def test_default_agent_prompt(self):
assert "agent for Claude Code" in DEFAULT_AGENT_PROMPT
def test_dynamic_boundary(self):
assert SYSTEM_PROMPT_DYNAMIC_BOUNDARY == "__SYSTEM_PROMPT_DYNAMIC_BOUNDARY__"
def test_language_section_none_when_no_preference(self):
assert get_language_section(None) is None
assert get_language_section("") is None
def test_language_section_with_preference(self):
result = get_language_section("Spanish")
assert result is not None
assert "Spanish" in result
assert "# Language" in result
def test_output_style_section_none_when_no_config(self):
assert get_output_style_section(None) is None
def test_output_style_section_with_config(self):
config = OutputStyleConfig(
name="TestStyle",
description="A test style",
prompt="Be concise.",
)
result = get_output_style_section(config)
assert result is not None
assert "# Output Style: TestStyle" in result
assert "Be concise." in result
def test_scratchpad_none_when_no_dir(self):
assert get_scratchpad_instructions(None) is None
assert get_scratchpad_instructions("") is None
def test_scratchpad_with_dir(self):
result = get_scratchpad_instructions("/tmp/session-123")
assert result is not None
assert "/tmp/session-123" in result
assert "# Scratchpad Directory" in result
assert "temporary files" in result
# =========================================================================
# Error IDs
# =========================================================================
class TestErrorIDs:
def test_tool_use_summary_error(self):
assert E_TOOL_USE_SUMMARY_GENERATION_FAILED == 344