forked from HAL9000/cleveragents-core
517 lines
19 KiB
Python
517 lines
19 KiB
Python
"""Step definitions for Load Context CLI BDD tests."""
|
|
|
|
import json
|
|
import subprocess
|
|
from pathlib import Path
|
|
|
|
from behave import given, then, when
|
|
from behave.runner import Context
|
|
|
|
|
|
@given("I have a test configuration file")
|
|
def step_test_config_file(context: Context) -> None:
|
|
"""Create a simple test configuration file."""
|
|
config_content = """
|
|
agents:
|
|
echo_agent:
|
|
type: llm
|
|
config:
|
|
provider: openai
|
|
model: gpt-3.5-turbo
|
|
|
|
routes:
|
|
main:
|
|
type: stream
|
|
stream_type: cold
|
|
operators:
|
|
- type: map
|
|
params:
|
|
agent: echo_agent
|
|
publications:
|
|
- __output__
|
|
|
|
merges:
|
|
- sources: [__input__]
|
|
target: main
|
|
"""
|
|
context.config_file = Path(context.temp_dir) / "test_config.yaml"
|
|
context.config_file.write_text(config_content)
|
|
|
|
|
|
@given("I have a context JSON file with sample data")
|
|
def step_context_json_sample(context: Context) -> None:
|
|
"""Create a context JSON file with sample data."""
|
|
context_data = {
|
|
"context_name": "sample_context",
|
|
"messages": [
|
|
{
|
|
"role": "user",
|
|
"content": "Hello, this is a test",
|
|
"timestamp": "2025-01-01T00:00:00",
|
|
"metadata": {},
|
|
}
|
|
],
|
|
"metadata": {"created_at": "2025-01-01T00:00:00"},
|
|
"state": {},
|
|
"global_context": {"test_key": "test_value", "session_id": "12345"},
|
|
}
|
|
|
|
context.context_json_file = Path(context.temp_dir) / "sample_context.json"
|
|
context.context_json_file.write_text(json.dumps(context_data, indent=2))
|
|
|
|
|
|
@given('I have a target context name "{name}"')
|
|
def step_target_context_name(context: Context, name: str) -> None:
|
|
"""Set a target context name."""
|
|
context.target_context_name = name
|
|
|
|
|
|
@given("I have a context JSON file with messages and global context")
|
|
def step_context_json_with_messages(context: Context) -> None:
|
|
"""Create a context JSON file with messages and global context."""
|
|
step_context_json_sample(context) # Reuse the sample data
|
|
|
|
|
|
@given("I have a context JSON file with specific global context values")
|
|
def step_context_json_with_global_context(context: Context) -> None:
|
|
"""Create a context JSON file with specific global context values."""
|
|
context_data = {
|
|
"context_name": "global_test",
|
|
"messages": [],
|
|
"metadata": {},
|
|
"state": {},
|
|
"global_context": {"special_key": "special_value", "config_setting": "enabled"},
|
|
}
|
|
|
|
context.context_json_file = Path(context.temp_dir) / "global_context.json"
|
|
context.context_json_file.write_text(json.dumps(context_data, indent=2))
|
|
|
|
|
|
@given("I specify a non-existent context file path")
|
|
def step_nonexistent_context_file(context: Context) -> None:
|
|
"""Set a path to a non-existent context file."""
|
|
context.context_json_file = Path(context.temp_dir) / "nonexistent.json"
|
|
|
|
|
|
@given("I have a malformed JSON context file")
|
|
def step_malformed_json_file(context: Context) -> None:
|
|
"""Create a malformed JSON file."""
|
|
context.context_json_file = Path(context.temp_dir) / "malformed.json"
|
|
context.context_json_file.write_text("{ invalid json content")
|
|
|
|
|
|
@given('I have an existing named context "{name}"')
|
|
def step_existing_named_context(context: Context, name: str) -> None:
|
|
"""Create an existing named context with some data."""
|
|
from cleveragents.context_manager import ContextManager
|
|
|
|
context.target_context_name = name
|
|
ctx_manager = ContextManager(name, context.context_dir)
|
|
ctx_manager.add_message("user", "original message")
|
|
ctx_manager.save_global_context({"old_key": "old_value"})
|
|
ctx_manager.save()
|
|
|
|
|
|
@given("the existing context has different data")
|
|
def step_existing_context_different_data(context: Context) -> None:
|
|
"""The existing context already has different data (set up in previous step)."""
|
|
pass # Already handled in the previous step
|
|
|
|
|
|
@given("I have a new context JSON file")
|
|
def step_new_context_json_file(context: Context) -> None:
|
|
"""Create a new context JSON file with different data."""
|
|
context_data = {
|
|
"context_name": "new_context",
|
|
"messages": [
|
|
{
|
|
"role": "user",
|
|
"content": "New message",
|
|
"timestamp": "2025-01-02T00:00:00",
|
|
"metadata": {},
|
|
}
|
|
],
|
|
"metadata": {},
|
|
"state": {},
|
|
"global_context": {"new_key": "new_value"},
|
|
}
|
|
|
|
context.context_json_file = Path(context.temp_dir) / "new_context.json"
|
|
context.context_json_file.write_text(json.dumps(context_data, indent=2))
|
|
|
|
|
|
@given("I have a context JSON file with messages, state, and metadata")
|
|
def step_context_json_full(context: Context) -> None:
|
|
"""Create a context JSON file with all components."""
|
|
context_data = {
|
|
"context_name": "full_context",
|
|
"messages": [
|
|
{
|
|
"role": "user",
|
|
"content": "Full context message",
|
|
"timestamp": "2025-01-01T00:00:00",
|
|
"metadata": {"msg_metadata": "value"},
|
|
}
|
|
],
|
|
"metadata": {"created_at": "2025-01-01T00:00:00", "message_count": 1},
|
|
"state": {"state_key": "state_value"},
|
|
"global_context": {"global_key": "global_value"},
|
|
}
|
|
|
|
context.context_json_file = Path(context.temp_dir) / "full_context.json"
|
|
context.context_json_file.write_text(json.dumps(context_data, indent=2))
|
|
|
|
|
|
@given("I have a context JSON file")
|
|
def step_simple_context_json(context: Context) -> None:
|
|
"""Create a simple context JSON file."""
|
|
step_context_json_sample(context)
|
|
|
|
|
|
@given('I have an existing context "{name}"')
|
|
def step_create_export_context(context: Context, name: str) -> None:
|
|
"""Create an existing context for export testing."""
|
|
from cleveragents.context_manager import ContextManager
|
|
|
|
context.export_context_name = name
|
|
ctx_manager = ContextManager(name, context.context_dir)
|
|
ctx_manager.add_message("user", "Export test message")
|
|
ctx_manager.save_global_context({"export_key": "export_value"})
|
|
ctx_manager.save()
|
|
# Set context_manager attribute for export step
|
|
context.context_manager = ctx_manager
|
|
|
|
|
|
@when("I run the CLI with --load-context pointing to the JSON file")
|
|
def step_run_cli_with_load_context(context: Context) -> None:
|
|
"""Run the CLI with --load-context flag."""
|
|
cmd = [
|
|
"python",
|
|
"-m",
|
|
"cleveragents",
|
|
"run",
|
|
"-c",
|
|
str(context.config_file),
|
|
"--load-context",
|
|
str(context.context_json_file),
|
|
"--unsafe",
|
|
"--allow-rxpy-in-run-mode",
|
|
"-p",
|
|
"test prompt",
|
|
]
|
|
|
|
result = subprocess.run(cmd, capture_output=True, text=True)
|
|
context.cli_result = result
|
|
context.cli_returncode = result.returncode
|
|
context.cli_stdout = result.stdout
|
|
context.cli_stderr = result.stderr
|
|
context.cli_stdout = result.stdout
|
|
context.cli_stderr = result.stderr
|
|
|
|
|
|
@when("I run the CLI with --load-context")
|
|
def step_run_cli_with_load_context_simple(context: Context) -> None:
|
|
"""Run the CLI with --load-context flag (simple form)."""
|
|
step_run_cli_with_load_context(context)
|
|
|
|
|
|
@when("I run the CLI with both --load-context and --context flags")
|
|
def step_run_cli_with_both_flags(context: Context) -> None:
|
|
"""Run the CLI with both --load-context and --context flags."""
|
|
cmd = [
|
|
"python",
|
|
"-m",
|
|
"cleveragents",
|
|
"run",
|
|
"-c",
|
|
str(context.config_file),
|
|
"--load-context",
|
|
str(context.context_json_file),
|
|
"--context",
|
|
context.target_context_name,
|
|
"--context-dir",
|
|
str(context.context_dir),
|
|
"--unsafe",
|
|
"--allow-rxpy-in-run-mode",
|
|
"-p",
|
|
"test prompt",
|
|
]
|
|
|
|
result = subprocess.run(cmd, capture_output=True, text=True)
|
|
context.cli_result = result
|
|
context.cli_returncode = result.returncode
|
|
context.cli_stdout = result.stdout
|
|
context.cli_stderr = result.stderr
|
|
context.cli_stdout = result.stdout
|
|
context.cli_stderr = result.stderr
|
|
|
|
|
|
@when("I start an interactive session with --load-context")
|
|
def step_start_interactive_with_load_context(context: Context) -> None:
|
|
"""Start an interactive session with --load-context (mocked for testing)."""
|
|
# For BDD testing, we'll just verify the CLI accepts the flag
|
|
# Actual interactive testing would require input simulation
|
|
cmd = [
|
|
"python",
|
|
"-m",
|
|
"cleveragents",
|
|
"interactive",
|
|
"-c",
|
|
str(context.config_file),
|
|
"--load-context",
|
|
str(context.context_json_file),
|
|
"--help", # Using help to avoid actual interactive session
|
|
]
|
|
|
|
result = subprocess.run(cmd, capture_output=True, text=True)
|
|
context.cli_result = result
|
|
context.cli_returncode = result.returncode
|
|
context.cli_stdout = result.stdout
|
|
context.cli_stderr = result.stderr
|
|
context.cli_stdout = result.stdout
|
|
context.cli_stderr = result.stderr
|
|
|
|
|
|
@when("I try to run the CLI with --load-context")
|
|
def step_try_run_cli_with_load_context(context: Context) -> None:
|
|
"""Try to run the CLI with --load-context (expecting failure)."""
|
|
step_run_cli_with_load_context(context)
|
|
|
|
|
|
@when('I run the CLI with --load-context and --context "{name}"')
|
|
def step_run_cli_with_both_flags_named(context: Context, name: str) -> None:
|
|
"""Run the CLI with both flags and a specific context name."""
|
|
context.target_context_name = name
|
|
step_run_cli_with_both_flags(context)
|
|
|
|
|
|
@when("I run the CLI with only --load-context (no --context)")
|
|
def step_run_cli_only_load_context(context: Context) -> None:
|
|
"""Run the CLI with only --load-context."""
|
|
step_run_cli_with_load_context(context)
|
|
|
|
|
|
@when("I delete the original context")
|
|
def step_delete_original_context(context: Context) -> None:
|
|
"""Delete the original context."""
|
|
from cleveragents.context_manager import ContextManager
|
|
|
|
ctx_manager = ContextManager(context.export_context_name, context.context_dir)
|
|
ctx_manager.delete()
|
|
|
|
|
|
@when('I load the exported file into a new context "{name}"')
|
|
def step_load_exported_file(context: Context, name: str) -> None:
|
|
"""Load the exported file into a new context."""
|
|
context.target_context_name = name
|
|
context.context_json_file = context.export_file
|
|
step_run_cli_with_both_flags(context)
|
|
|
|
|
|
@then("the global context should be applied to the app")
|
|
def step_global_context_applied(context: Context) -> None:
|
|
"""Verify the global context was applied (implicit in successful execution)."""
|
|
# The fact that the command succeeded means the context was loaded
|
|
pass
|
|
|
|
|
|
@then("the context should not be persisted after the run")
|
|
def step_context_not_persisted(context: Context) -> None:
|
|
"""Verify no context directory was created for transient loading."""
|
|
# Check that no persistent context directory exists in default location
|
|
home_dir = Path.home()
|
|
default_context_dir = home_dir / ".cleveragents" / "context"
|
|
|
|
# Look for any temp contexts that might have been created
|
|
if default_context_dir.exists():
|
|
temp_contexts = list(default_context_dir.glob("_temp_*"))
|
|
assert len(temp_contexts) == 0, f"Found temp contexts that weren't cleaned up: {temp_contexts}"
|
|
|
|
|
|
@then("the JSON context should be imported into the named context")
|
|
def step_json_imported_into_named_context(context: Context) -> None:
|
|
"""Verify the JSON context was imported into the named context."""
|
|
from cleveragents.context_manager import ContextManager
|
|
|
|
ctx_manager = ContextManager(context.target_context_name, context.context_dir)
|
|
assert ctx_manager.exists(), "Named context was not created"
|
|
|
|
global_ctx = ctx_manager.get_global_context()
|
|
assert "test_key" in global_ctx or "new_key" in global_ctx, "Global context not loaded"
|
|
|
|
|
|
@then("the named context should be persisted")
|
|
def step_named_context_persisted(context: Context) -> None:
|
|
"""Verify the named context directory exists."""
|
|
context_path = context.context_dir / context.target_context_name
|
|
assert context_path.exists(), f"Context directory not created: {context_path}"
|
|
assert (context_path / "messages.json").exists(), "Messages file not created"
|
|
|
|
|
|
@then("changes during the run should be saved to the named context")
|
|
def step_changes_saved_to_context(context: Context) -> None:
|
|
"""Verify changes were saved (new messages added during run)."""
|
|
from cleveragents.context_manager import ContextManager
|
|
|
|
ctx_manager = ContextManager(context.target_context_name, context.context_dir)
|
|
messages = ctx_manager.get_conversation_history()
|
|
# Should have at least the original message plus the new one from the run
|
|
assert len(messages) > 0, "No messages saved to context"
|
|
|
|
|
|
@then("the loaded context should be available in the session")
|
|
def step_context_available_in_session(context: Context) -> None:
|
|
"""Verify context would be available (implicit in successful start)."""
|
|
pass
|
|
|
|
|
|
@then("the context should be transient (not persisted after exit)")
|
|
def step_context_transient(context: Context) -> None:
|
|
"""Verify context is transient."""
|
|
step_context_not_persisted(context)
|
|
|
|
|
|
@then("the application should have access to the global context values")
|
|
def step_app_has_global_context(context: Context) -> None:
|
|
"""Verify app has access to global context (implicit in successful execution)."""
|
|
pass
|
|
|
|
|
|
@then("the global context keys should be available during execution")
|
|
def step_global_context_keys_available(context: Context) -> None:
|
|
"""Verify global context keys are available."""
|
|
pass
|
|
|
|
|
|
@then("the command should fail with an appropriate error")
|
|
def step_command_fails_with_error(context: Context) -> None:
|
|
"""Verify the command failed."""
|
|
assert context.cli_returncode != 0, "Command should have failed but succeeded"
|
|
|
|
|
|
@then("the error should indicate the file was not found")
|
|
def step_error_file_not_found(context: Context) -> None:
|
|
"""Verify error message indicates file not found."""
|
|
error_message = context.cli_stderr.lower()
|
|
assert (
|
|
"does not exist" in error_message or "no such file" in error_message or "not found" in error_message
|
|
), f"Expected file not found error, got: {context.cli_stderr}"
|
|
|
|
|
|
@then("the command should fail with a JSON parsing error")
|
|
def step_command_fails_json_error(context: Context) -> None:
|
|
"""Verify command failed with JSON parsing error."""
|
|
assert context.cli_returncode != 0, "Command should have failed"
|
|
# The error might be in stderr or might cause a Python exception
|
|
error_output = context.cli_stderr.lower()
|
|
assert (
|
|
"json" in error_output
|
|
or "invalid" in error_output
|
|
or "decode" in error_output
|
|
or "expecting" in error_output
|
|
or "property name" in error_output
|
|
), f"Expected JSON parsing error, got: {context.cli_stderr}"
|
|
|
|
|
|
@then("the named context should be replaced with the new data")
|
|
def step_context_replaced_with_new_data(context: Context) -> None:
|
|
"""Verify the named context was replaced with new data."""
|
|
from cleveragents.context_manager import ContextManager
|
|
|
|
ctx_manager = ContextManager(context.target_context_name, context.context_dir)
|
|
global_ctx = ctx_manager.get_global_context()
|
|
assert "new_key" in global_ctx, "New global context not found"
|
|
|
|
|
|
@then("the old data should no longer be present")
|
|
def step_old_data_not_present(context: Context) -> None:
|
|
"""Verify old data is no longer present."""
|
|
from cleveragents.context_manager import ContextManager
|
|
|
|
ctx_manager = ContextManager(context.target_context_name, context.context_dir)
|
|
global_ctx = ctx_manager.get_global_context()
|
|
# The old_key should not be present if context was replaced
|
|
# Note: Due to import behavior, it might still have some residual data
|
|
# but the new_key should definitely be there
|
|
assert "new_key" in global_ctx, "Context was not replaced properly"
|
|
|
|
|
|
@then("all context components should be imported")
|
|
def step_all_components_imported(context: Context) -> None:
|
|
"""Verify all context components were imported."""
|
|
from cleveragents.context_manager import ContextManager
|
|
|
|
ctx_manager = ContextManager(context.target_context_name, context.context_dir)
|
|
assert ctx_manager.exists(), "Context not created"
|
|
|
|
|
|
@then("the messages should be accessible")
|
|
def step_messages_accessible(context: Context) -> None:
|
|
"""Verify messages are accessible."""
|
|
from cleveragents.context_manager import ContextManager
|
|
|
|
ctx_manager = ContextManager(context.target_context_name, context.context_dir)
|
|
messages = ctx_manager.get_conversation_history()
|
|
assert len(messages) > 0, "No messages found"
|
|
|
|
|
|
@then("the state should be accessible")
|
|
def step_state_accessible(context: Context) -> None:
|
|
"""Verify state is accessible."""
|
|
from cleveragents.context_manager import ContextManager
|
|
|
|
ctx_manager = ContextManager(context.target_context_name, context.context_dir)
|
|
assert ctx_manager.state is not None, "State not accessible"
|
|
|
|
|
|
@then("the metadata should be preserved")
|
|
def step_metadata_preserved(context: Context) -> None:
|
|
"""Verify metadata is preserved."""
|
|
from cleveragents.context_manager import ContextManager
|
|
|
|
ctx_manager = ContextManager(context.target_context_name, context.context_dir)
|
|
assert ctx_manager.metadata is not None, "Metadata not preserved"
|
|
|
|
|
|
@then("the command should complete successfully")
|
|
def step_command_completes_successfully(context: Context) -> None:
|
|
"""Verify command completed successfully."""
|
|
assert context.cli_returncode == 0, (
|
|
f"Command failed with exit code {context.cli_returncode}. " f"Output: {getattr(context, 'cli_stdout', '')}"
|
|
)
|
|
|
|
|
|
@then("no context directory should be created")
|
|
def step_no_context_directory_created(context: Context) -> None:
|
|
"""Verify no context directory was created."""
|
|
step_context_not_persisted(context)
|
|
|
|
|
|
@then("changes should not persist after the run")
|
|
def step_changes_not_persist(context: Context) -> None:
|
|
"""Verify changes don't persist."""
|
|
pass # Same as transient behavior
|
|
|
|
|
|
@then("the new context should match the original data")
|
|
def step_new_context_matches_original(context: Context) -> None:
|
|
"""Verify new context matches original data."""
|
|
from cleveragents.context_manager import ContextManager
|
|
|
|
ctx_manager = ContextManager(context.target_context_name, context.context_dir)
|
|
messages = ctx_manager.get_conversation_history()
|
|
# Should have the exported message
|
|
assert any(
|
|
"Export test message" in msg.get("content", "") for msg in messages
|
|
), "Original message not found in imported context"
|
|
|
|
|
|
@then("all fields should be preserved")
|
|
def step_all_fields_preserved(context: Context) -> None:
|
|
"""Verify all fields are preserved."""
|
|
from cleveragents.context_manager import ContextManager
|
|
|
|
ctx_manager = ContextManager(context.target_context_name, context.context_dir)
|
|
global_ctx = ctx_manager.get_global_context()
|
|
assert "export_key" in global_ctx, "Global context not preserved"
|