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