Test updates, still not working

This commit is contained in:
Cyber MacGeddon 2025-07-21 12:47:09 +01:00
parent e434e03cfa
commit 0d8b1aff57
3 changed files with 957 additions and 0 deletions

View file

@ -0,0 +1,202 @@
"""
Simplified integration tests for Template Service
These tests verify the basic functionality of the template service
without the full message queue infrastructure.
"""
import pytest
import json
from unittest.mock import AsyncMock, MagicMock
from trustgraph.schema import PromptRequest, PromptResponse
from trustgraph.template.prompt_manager import PromptManager
@pytest.mark.integration
class TestTemplateServiceSimple:
"""Simplified integration tests for Template Service components"""
@pytest.fixture
def sample_config(self):
"""Sample configuration for testing"""
return {
"system": json.dumps("You are a helpful assistant."),
"template-index": json.dumps(["greeting", "json_test"]),
"template.greeting": json.dumps({
"prompt": "Hello {{ name }}, welcome to {{ system_name }}!",
"response-type": "text"
}),
"template.json_test": json.dumps({
"prompt": "Generate profile for {{ username }}",
"response-type": "json",
"schema": {
"type": "object",
"properties": {
"name": {"type": "string"},
"role": {"type": "string"}
},
"required": ["name", "role"]
}
})
}
@pytest.fixture
def prompt_manager(self, sample_config):
"""Create a configured PromptManager"""
pm = PromptManager()
pm.load_config(sample_config)
pm.terms["system_name"] = "TrustGraph"
return pm
@pytest.mark.asyncio
async def test_prompt_manager_text_invocation(self, prompt_manager):
"""Test PromptManager text response invocation"""
# Mock LLM function
async def mock_llm(system, prompt):
assert system == "You are a helpful assistant."
assert "Hello Alice, welcome to TrustGraph!" in prompt
return "Welcome message processed!"
result = await prompt_manager.invoke("greeting", {"name": "Alice"}, mock_llm)
assert result == "Welcome message processed!"
@pytest.mark.asyncio
async def test_prompt_manager_json_invocation(self, prompt_manager):
"""Test PromptManager JSON response invocation"""
# Mock LLM function
async def mock_llm(system, prompt):
assert "Generate profile for johndoe" in prompt
return '{"name": "John Doe", "role": "user"}'
result = await prompt_manager.invoke("json_test", {"username": "johndoe"}, mock_llm)
assert isinstance(result, dict)
assert result["name"] == "John Doe"
assert result["role"] == "user"
@pytest.mark.asyncio
async def test_prompt_manager_json_validation_error(self, prompt_manager):
"""Test JSON schema validation failure"""
# Mock LLM function that returns invalid JSON
async def mock_llm(system, prompt):
return '{"name": "John Doe"}' # Missing required "role"
with pytest.raises(RuntimeError) as exc_info:
await prompt_manager.invoke("json_test", {"username": "johndoe"}, mock_llm)
assert "Schema validation fail" in str(exc_info.value)
@pytest.mark.asyncio
async def test_prompt_manager_json_parse_error(self, prompt_manager):
"""Test JSON parsing failure"""
# Mock LLM function that returns non-JSON
async def mock_llm(system, prompt):
return "This is not JSON at all"
with pytest.raises(RuntimeError) as exc_info:
await prompt_manager.invoke("json_test", {"username": "johndoe"}, mock_llm)
assert "JSON parse fail" in str(exc_info.value)
@pytest.mark.asyncio
async def test_prompt_manager_unknown_prompt(self, prompt_manager):
"""Test unknown prompt ID handling"""
async def mock_llm(system, prompt):
return "Response"
with pytest.raises(RuntimeError) as exc_info:
await prompt_manager.invoke("unknown_prompt", {}, mock_llm)
assert "ID invalid" in str(exc_info.value)
@pytest.mark.asyncio
async def test_prompt_manager_term_merging(self, prompt_manager):
"""Test proper term merging (global + prompt + input)"""
# Add prompt-specific terms
prompt_manager.prompts["greeting"].terms = {"greeting_prefix": "Hi"}
async def mock_llm(system, prompt):
# Should have global term (system_name), input term (name), and any prompt terms
assert "TrustGraph" in prompt # Global term
assert "Bob" in prompt # Input term
return "Merged correctly"
result = await prompt_manager.invoke("greeting", {"name": "Bob"}, mock_llm)
assert result == "Merged correctly"
def test_prompt_manager_template_rendering(self, prompt_manager):
"""Test direct template rendering"""
result = prompt_manager.render("greeting", {"name": "Charlie"})
assert "Hello Charlie, welcome to TrustGraph!" == result.strip()
def test_prompt_manager_configuration_loading(self):
"""Test configuration loading with various formats"""
pm = PromptManager()
# Test empty configuration
pm.load_config({})
assert pm.config.system_template == "Be helpful."
assert len(pm.prompts) == 0
# Test configuration with single prompt
config = {
"system": json.dumps("Test system"),
"template-index": json.dumps(["test"]),
"template.test": json.dumps({
"prompt": "Test {{ value }}",
"response-type": "text"
})
}
pm.load_config(config)
assert pm.config.system_template == "Test system"
assert "test" in pm.prompts
assert pm.prompts["test"].response_type == "text"
@pytest.mark.asyncio
async def test_prompt_manager_json_with_markdown(self, prompt_manager):
"""Test JSON extraction from markdown code blocks"""
async def mock_llm(system, prompt):
return '''
Here's the profile:
```json
{"name": "Jane Smith", "role": "admin"}
```
'''
result = await prompt_manager.invoke("json_test", {"username": "jane"}, mock_llm)
assert isinstance(result, dict)
assert result["name"] == "Jane Smith"
assert result["role"] == "admin"
def test_prompt_manager_error_handling_in_templates(self, prompt_manager):
"""Test error handling in template rendering"""
# Test with missing variable
with pytest.raises(Exception): # ibis should raise on undefined variable
prompt_manager.render("greeting", {}) # Missing 'name'
@pytest.mark.asyncio
async def test_concurrent_prompt_invocations(self, prompt_manager):
"""Test concurrent invocations"""
async def mock_llm(system, prompt):
# Extract name from prompt for response
if "Alice" in prompt:
return "Alice response"
elif "Bob" in prompt:
return "Bob response"
else:
return "Default response"
# Run concurrent invocations
import asyncio
results = await asyncio.gather(
prompt_manager.invoke("greeting", {"name": "Alice"}, mock_llm),
prompt_manager.invoke("greeting", {"name": "Bob"}, mock_llm),
)
assert "Alice response" in results
assert "Bob response" in results

View file

@ -0,0 +1,327 @@
"""
Unit tests for PromptManager
These tests verify the functionality of the PromptManager class,
including template rendering, term merging, JSON validation, and error handling.
"""
import pytest
import json
from unittest.mock import AsyncMock, MagicMock, patch
from trustgraph.template.prompt_manager import PromptManager, PromptConfiguration, Prompt
@pytest.mark.unit
class TestPromptManager:
"""Unit tests for PromptManager template functionality"""
@pytest.fixture
def sample_config(self):
"""Sample configuration dict for PromptManager"""
return {
"system": json.dumps("You are a helpful assistant."),
"template-index": json.dumps(["simple_text", "json_response", "complex_template"]),
"template.simple_text": json.dumps({
"prompt": "Hello {{ name }}, welcome to {{ system_name }}!",
"response-type": "text"
}),
"template.json_response": json.dumps({
"prompt": "Generate a user profile for {{ username }}",
"response-type": "json",
"schema": {
"type": "object",
"properties": {
"name": {"type": "string"},
"age": {"type": "number"}
},
"required": ["name", "age"]
}
}),
"template.complex_template": json.dumps({
"prompt": """
{% for item in items %}
- {{ item.name }}: {{ item.value }}
{% endfor %}
Total: {{ items|length }}
""",
"response-type": "text"
})
}
@pytest.fixture
def prompt_manager(self, sample_config):
"""Create a PromptManager with sample configuration"""
pm = PromptManager()
pm.load_config(sample_config)
# Add global terms manually since load_config doesn't handle them
pm.terms["system_name"] = "TrustGraph"
pm.terms["version"] = "1.0"
return pm
def test_prompt_manager_initialization(self, prompt_manager, sample_config):
"""Test PromptManager initialization with configuration"""
assert prompt_manager.config.system_template == "You are a helpful assistant."
assert len(prompt_manager.prompts) == 3
assert "simple_text" in prompt_manager.prompts
def test_simple_text_template_rendering(self, prompt_manager):
"""Test basic template rendering with text response"""
terms = {"name": "Alice"}
rendered = prompt_manager.render("simple_text", terms)
assert rendered == "Hello Alice, welcome to TrustGraph!"
def test_global_terms_merging(self, prompt_manager):
"""Test that global terms are properly merged"""
terms = {"name": "Bob"}
# Global terms should be available in template
rendered = prompt_manager.render("simple_text", terms)
assert "TrustGraph" in rendered # From global terms
assert "Bob" in rendered # From input terms
def test_term_override_priority(self, prompt_manager):
"""Test term override priority: input > prompt > global"""
# Add a test prompt with overlapping terms
test_config = {
"template.test": json.dumps({
"prompt": "Value is: {{ value }}",
"response-type": "text"
})
}
prompt_manager.load_config({**prompt_manager.config.__dict__, **test_config})
prompt_manager.terms["value"] = "global"
prompt_manager.prompts["test"].terms["value"] = "prompt"
# Test with no input override
rendered = prompt_manager.render("test", {})
assert rendered == "Value is: prompt" # Prompt terms override global
# Test with input override
rendered = prompt_manager.render("test", {"value": "input"})
assert rendered == "Value is: input" # Input terms override all
def test_complex_template_rendering(self, prompt_manager):
"""Test complex template with loops and filters"""
terms = {
"items": [
{"name": "Item1", "value": 10},
{"name": "Item2", "value": 20},
{"name": "Item3", "value": 30}
]
}
rendered = prompt_manager.render("complex_template", terms)
assert "Item1: 10" in rendered
assert "Item2: 20" in rendered
assert "Item3: 30" in rendered
assert "Total: 3" in rendered
@pytest.mark.asyncio
async def test_invoke_text_response(self, prompt_manager):
"""Test invoking a prompt with text response"""
mock_llm = AsyncMock()
mock_llm.return_value = "Welcome Alice to TrustGraph!"
result = await prompt_manager.invoke(
"simple_text",
{"name": "Alice"},
mock_llm
)
assert result == "Welcome Alice to TrustGraph!"
# Verify LLM was called with correct prompts
mock_llm.assert_called_once()
call_args = mock_llm.call_args[1]
assert call_args["system"] == "You are a helpful assistant."
assert "Hello Alice, welcome to TrustGraph!" in call_args["prompt"]
@pytest.mark.asyncio
async def test_invoke_json_response_valid(self, prompt_manager):
"""Test invoking a prompt with valid JSON response"""
mock_llm = AsyncMock()
mock_llm.return_value = '{"name": "John Doe", "age": 30}'
result = await prompt_manager.invoke(
"json_response",
{"username": "johndoe"},
mock_llm
)
assert isinstance(result, dict)
assert result["name"] == "John Doe"
assert result["age"] == 30
@pytest.mark.asyncio
async def test_invoke_json_response_with_markdown(self, prompt_manager):
"""Test JSON extraction from markdown code blocks"""
mock_llm = AsyncMock()
mock_llm.return_value = """
Here is the user profile:
```json
{
"name": "Jane Smith",
"age": 25
}
```
This is a valid profile.
"""
result = await prompt_manager.invoke(
"json_response",
{"username": "janesmith"},
mock_llm
)
assert isinstance(result, dict)
assert result["name"] == "Jane Smith"
assert result["age"] == 25
@pytest.mark.asyncio
async def test_invoke_json_validation_failure(self, prompt_manager):
"""Test JSON schema validation failure"""
mock_llm = AsyncMock()
# Missing required 'age' field
mock_llm.return_value = '{"name": "Invalid User"}'
with pytest.raises(ValueError) as exc_info:
await prompt_manager.invoke(
"json_response",
{"username": "invalid"},
mock_llm
)
assert "JSON schema validation failed" in str(exc_info.value)
@pytest.mark.asyncio
async def test_invoke_json_parse_failure(self, prompt_manager):
"""Test invalid JSON parsing"""
mock_llm = AsyncMock()
mock_llm.return_value = "This is not JSON at all"
with pytest.raises(ValueError) as exc_info:
await prompt_manager.invoke(
"json_response",
{"username": "test"},
mock_llm
)
assert "No JSON found in response" in str(exc_info.value)
@pytest.mark.asyncio
async def test_invoke_unknown_prompt(self, prompt_manager):
"""Test invoking an unknown prompt ID"""
mock_llm = AsyncMock()
with pytest.raises(KeyError):
await prompt_manager.invoke(
"nonexistent_prompt",
{},
mock_llm
)
def test_template_rendering_with_undefined_variable(self, prompt_manager):
"""Test template rendering with undefined variables"""
terms = {} # Missing 'name' variable
# This should raise an error or use empty string depending on config
with pytest.raises(Exception): # ibis/Jinja2 will raise on undefined
prompt_manager.render("simple_text", terms)
@pytest.mark.asyncio
async def test_json_response_without_schema(self):
"""Test JSON response without schema validation"""
pm = PromptManager()
config = {
"system": json.dumps("Test"),
"template-index": json.dumps(["no_schema"]),
"template.no_schema": json.dumps({
"prompt": "Generate any JSON",
"response-type": "json"
# No schema defined
})
}
pm.load_config(config)
mock_llm = AsyncMock()
mock_llm.return_value = '{"any": "json", "is": "valid"}'
result = await pm.invoke("no_schema", {}, mock_llm)
assert result == {"any": "json", "is": "valid"}
def test_prompt_configuration_validation(self):
"""Test PromptConfiguration validation"""
# Valid configuration
config = PromptConfiguration(
system_template="Test system",
prompts={
"test": Prompt(
template="Hello {{ name }}",
response_type="text"
)
}
)
assert config.system_template == "Test system"
assert len(config.prompts) == 1
def test_nested_template_includes(self, prompt_manager):
"""Test templates with nested variable references"""
# Add global terms
prompt_manager.terms["company"] = "TrustGraph"
prompt_manager.terms["year"] = 2024
# Add a nested template
config = {
"template.nested": json.dumps({
"prompt": "{{ greeting }} from {{ company }} in {{ year }}!",
"response-type": "text"
})
}
prompt_manager.load_config({**prompt_manager.config.__dict__, **config})
prompt_manager.prompts["nested"].terms = {"greeting": "Welcome"}
rendered = prompt_manager.render("nested", {"user": "Alice"})
# Should contain company and year from global terms
assert "TrustGraph" in rendered
assert "2024" in rendered
@pytest.mark.asyncio
async def test_concurrent_invocations(self, prompt_manager):
"""Test concurrent prompt invocations"""
mock_llm = AsyncMock()
mock_llm.side_effect = [
"Response for Alice",
"Response for Bob",
"Response for Charlie"
]
# Simulate concurrent invocations
import asyncio
results = await asyncio.gather(
prompt_manager.invoke("simple_text", {"name": "Alice"}, mock_llm),
prompt_manager.invoke("simple_text", {"name": "Bob"}, mock_llm),
prompt_manager.invoke("simple_text", {"name": "Charlie"}, mock_llm)
)
assert len(results) == 3
assert "Alice" in results[0]
assert "Bob" in results[1]
assert "Charlie" in results[2]
def test_empty_configuration(self):
"""Test PromptManager with minimal configuration"""
pm = PromptManager()
pm.load_config({}) # Empty config
assert pm.config.system_template == "Be helpful." # Default system
assert pm.terms == {} # Default empty terms
assert len(pm.prompts) == 0

View file

@ -0,0 +1,428 @@
"""
Edge case and error handling tests for PromptManager
These tests focus on boundary conditions, error scenarios, and
unusual but valid use cases for the PromptManager.
"""
import pytest
import json
import asyncio
from unittest.mock import AsyncMock
from trustgraph.template.prompt_manager import PromptManager, PromptConfiguration, Prompt
@pytest.mark.unit
class TestPromptManagerEdgeCases:
"""Edge case tests for PromptManager"""
@pytest.mark.asyncio
async def test_very_large_json_response(self):
"""Test handling of very large JSON responses"""
pm = PromptManager()
config = {
"system": json.dumps("Test"),
"template-index": json.dumps(["large_json"]),
"template.large_json": json.dumps({
"prompt": "Generate large dataset",
"response-type": "json"
})
}
pm.load_config(config)
# Create a large JSON structure
large_data = {
f"item_{i}": {
"name": f"Item {i}",
"data": list(range(100)),
"nested": {
"level1": {
"level2": f"Deep value {i}"
}
}
}
for i in range(100)
}
mock_llm = AsyncMock()
mock_llm.return_value = json.dumps(large_data)
result = await pm.invoke("large_json", {}, mock_llm)
assert isinstance(result, dict)
assert len(result) == 100
assert "item_50" in result
@pytest.mark.asyncio
async def test_unicode_and_special_characters(self):
"""Test handling of unicode and special characters"""
pm = PromptManager()
config = {
"system": json.dumps("Test"),
"template-index": json.dumps(["unicode"]),
"template.unicode": json.dumps({
"prompt": "Process text: {{ text }}",
"response-type": "text"
})
}
pm.load_config(config)
special_text = "Hello 世界! 🌍 Привет мир! مرحبا بالعالم"
mock_llm = AsyncMock()
mock_llm.return_value = f"Processed: {special_text}"
result = await pm.invoke("unicode", {"text": special_text}, mock_llm)
assert special_text in result
assert "🌍" in result
assert "世界" in result
@pytest.mark.asyncio
async def test_nested_json_in_text_response(self):
"""Test text response containing JSON-like structures"""
pm = PromptManager()
config = {
"system": json.dumps("Test"),
"template-index": json.dumps(["text_with_json"]),
"template.text_with_json": json.dumps({
"prompt": "Explain this data",
"response-type": "text" # Text response, not JSON
})
}
pm.load_config(config)
mock_llm = AsyncMock()
mock_llm.return_value = """
The data structure is:
{
"key": "value",
"nested": {
"array": [1, 2, 3]
}
}
This represents a nested object.
"""
result = await pm.invoke("text_with_json", {}, mock_llm)
assert isinstance(result, str) # Should remain as text
assert '"key": "value"' in result
@pytest.mark.asyncio
async def test_multiple_json_blocks_in_response(self):
"""Test response with multiple JSON blocks"""
pm = PromptManager()
config = {
"system": json.dumps("Test"),
"template-index": json.dumps(["multi_json"]),
"template.multi_json": json.dumps({
"prompt": "Generate examples",
"response-type": "json"
})
}
pm.load_config(config)
mock_llm = AsyncMock()
mock_llm.return_value = """
Here's the first example:
```json
{"first": true, "value": 1}
```
And here's another:
```json
{"second": true, "value": 2}
```
"""
# Should extract the first valid JSON block
result = await pm.invoke("multi_json", {}, mock_llm)
assert result == {"first": True, "value": 1}
@pytest.mark.asyncio
async def test_json_with_comments(self):
"""Test JSON response with comment-like content"""
pm = PromptManager()
config = {
"system": json.dumps("Test"),
"template-index": json.dumps(["json_comments"]),
"template.json_comments": json.dumps({
"prompt": "Generate config",
"response-type": "json"
})
}
pm.load_config(config)
mock_llm = AsyncMock()
# JSON with comment-like content that should be extracted
mock_llm.return_value = """
// This is a configuration file
{
"setting": "value", // Important setting
"number": 42
}
/* End of config */
"""
# Standard JSON parser won't handle comments
with pytest.raises(ValueError):
await pm.invoke("json_comments", {}, mock_llm)
def test_template_with_raw_blocks(self):
"""Test template with raw blocks that shouldn't be processed"""
pm = PromptManager()
config = {
"system": json.dumps("Test"),
"template-index": json.dumps(["raw_template"]),
"template.raw_template": json.dumps({
"prompt": """
Normal: {{ variable }}
{% raw %}
Raw block: {{ this_should_not_be_processed }}
{% endraw %}
Normal again: {{ another }}
""",
"response-type": "text"
})
}
pm.load_config(config)
result = pm.render(
"raw_template",
{"variable": "processed", "another": "also processed"}
)
assert "processed" in result
assert "{{ this_should_not_be_processed }}" in result # Should remain as-is
assert "also processed" in result
@pytest.mark.asyncio
async def test_empty_json_response_variations(self):
"""Test various empty JSON response formats"""
pm = PromptManager()
config = {
"system": json.dumps("Test"),
"template-index": json.dumps(["empty_json"]),
"template.empty_json": json.dumps({
"prompt": "Generate empty data",
"response-type": "json"
})
}
pm.load_config(config)
empty_variations = [
"{}",
"[]",
"null",
'""',
"0",
"false"
]
for empty_value in empty_variations:
mock_llm = AsyncMock()
mock_llm.return_value = empty_value
result = await pm.invoke("empty_json", {}, mock_llm)
assert result == json.loads(empty_value)
@pytest.mark.asyncio
async def test_malformed_json_recovery(self):
"""Test recovery from slightly malformed JSON"""
pm = PromptManager()
config = {
"system": json.dumps("Test"),
"template-index": json.dumps(["malformed"]),
"template.malformed": json.dumps({
"prompt": "Generate data",
"response-type": "json"
})
}
pm.load_config(config)
# Missing closing brace - should fail
mock_llm = AsyncMock()
mock_llm.return_value = '{"key": "value"'
with pytest.raises(RuntimeError) as exc_info:
await pm.invoke("malformed", {}, mock_llm)
assert "JSON parse fail" in str(exc_info.value)
def test_template_infinite_loop_protection(self):
"""Test protection against infinite template loops"""
pm = PromptManager()
config = {
"system": json.dumps("Test"),
"template-index": json.dumps(["recursive"]),
"template.recursive": json.dumps({
"prompt": "{{ recursive_var }}",
"response-type": "text"
})
}
pm.load_config(config)
pm.prompts["recursive"].terms = {"recursive_var": "This includes {{ recursive_var }}"}
# This should not cause infinite recursion
result = pm.render("recursive", {})
# The exact behavior depends on the template engine
assert isinstance(result, str)
@pytest.mark.asyncio
async def test_extremely_long_template(self):
"""Test handling of extremely long templates"""
# Create a very long template
long_template = "Start\n" + "\n".join([
f"Line {i}: " + "{{ var_" + str(i) + " }}"
for i in range(1000)
]) + "\nEnd"
pm = PromptManager()
config = {
"system": json.dumps("Test"),
"template-index": json.dumps(["long"]),
"template.long": json.dumps({
"prompt": long_template,
"response-type": "text"
})
}
pm.load_config(config)
# Create corresponding variables
variables = {f"var_{i}": f"value_{i}" for i in range(1000)}
mock_llm = AsyncMock()
mock_llm.return_value = "Processed long template"
result = await pm.invoke("long", variables, mock_llm)
assert result == "Processed long template"
# Check that template was rendered correctly
call_args = mock_llm.call_args[1]
rendered = call_args["prompt"]
assert "Line 500: value_500" in rendered
@pytest.mark.asyncio
async def test_json_schema_with_additional_properties(self):
"""Test JSON schema validation with additional properties"""
pm = PromptManager()
config = {
"system": json.dumps("Test"),
"template-index": json.dumps(["strict_schema"]),
"template.strict_schema": json.dumps({
"prompt": "Generate user",
"response-type": "json",
"schema": {
"type": "object",
"properties": {
"name": {"type": "string"}
},
"required": ["name"],
"additionalProperties": False
}
})
}
pm.load_config(config)
mock_llm = AsyncMock()
# Response with extra property
mock_llm.return_value = '{"name": "John", "age": 30}'
# Should fail validation due to additionalProperties: false
with pytest.raises(RuntimeError) as exc_info:
await pm.invoke("strict_schema", {}, mock_llm)
assert "Schema validation fail" in str(exc_info.value)
@pytest.mark.asyncio
async def test_llm_timeout_handling(self):
"""Test handling of LLM timeouts"""
pm = PromptManager()
config = {
"system": json.dumps("Test"),
"template-index": json.dumps(["timeout_test"]),
"template.timeout_test": json.dumps({
"prompt": "Test prompt",
"response-type": "text"
})
}
pm.load_config(config)
mock_llm = AsyncMock()
mock_llm.side_effect = asyncio.TimeoutError("LLM request timed out")
with pytest.raises(asyncio.TimeoutError):
await pm.invoke("timeout_test", {}, mock_llm)
def test_template_with_filters_and_tests(self):
"""Test template with Jinja2 filters and tests"""
pm = PromptManager()
config = {
"system": json.dumps("Test"),
"template-index": json.dumps(["filters"]),
"template.filters": json.dumps({
"prompt": """
{% if items %}
Items ({{ items|length }}):
{% for item in items|sort %}
- {{ item|upper }}
{% endfor %}
{% else %}
No items
{% endif %}
""",
"response-type": "text"
})
}
pm.load_config(config)
# Test with items
result = pm.render(
"filters",
{"items": ["banana", "apple", "cherry"]}
)
assert "Items (3)" in result
assert "- APPLE" in result
assert "- BANANA" in result
assert "- CHERRY" in result
# Test without items
result = pm.render("filters", {"items": []})
assert "No items" in result
@pytest.mark.asyncio
async def test_concurrent_template_modifications(self):
"""Test thread safety of template operations"""
pm = PromptManager()
config = {
"system": json.dumps("Test"),
"template-index": json.dumps(["concurrent"]),
"template.concurrent": json.dumps({
"prompt": "User: {{ user }}",
"response-type": "text"
})
}
pm.load_config(config)
mock_llm = AsyncMock()
mock_llm.side_effect = lambda **kwargs: f"Response for {kwargs['prompt'].split()[1]}"
# Simulate concurrent invocations with different users
import asyncio
tasks = []
for i in range(10):
tasks.append(
pm.invoke("concurrent", {"user": f"User{i}"}, mock_llm)
)
results = await asyncio.gather(*tasks)
# Each result should correspond to its user
for i, result in enumerate(results):
assert f"User{i}" in result