chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
import sys
|
||||
import os
|
||||
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
|
||||
@@ -0,0 +1,77 @@
|
||||
"""Cross-language parity tests.
|
||||
|
||||
Verify that Python shared tool outputs match the structural contracts
|
||||
that TypeScript equivalents also follow. If these tests pass in Python
|
||||
AND the TS tests pass, the implementations are structurally compatible.
|
||||
"""
|
||||
|
||||
from tools import (
|
||||
get_weather_impl,
|
||||
query_data_impl,
|
||||
manage_sales_todos_impl,
|
||||
get_sales_todos_impl,
|
||||
search_flights_impl,
|
||||
schedule_meeting_impl,
|
||||
INITIAL_TODOS,
|
||||
)
|
||||
|
||||
|
||||
def test_weather_field_names_match_typescript():
|
||||
"""WeatherResult fields must be: city, temperature, humidity, wind_speed, feels_like, conditions"""
|
||||
result = get_weather_impl("Tokyo")
|
||||
expected_fields = {
|
||||
"city",
|
||||
"temperature",
|
||||
"humidity",
|
||||
"wind_speed",
|
||||
"feels_like",
|
||||
"conditions",
|
||||
}
|
||||
assert set(result.keys()) == expected_fields
|
||||
|
||||
|
||||
def test_initial_todos_ids_match_typescript():
|
||||
"""INITIAL_TODOS IDs must be st-001, st-002, st-003 (same as TS)"""
|
||||
assert [t["id"] for t in INITIAL_TODOS] == ["st-001", "st-002", "st-003"]
|
||||
|
||||
|
||||
def test_initial_todos_count_matches_typescript():
|
||||
assert len(INITIAL_TODOS) == 3
|
||||
|
||||
|
||||
def test_manage_todos_provides_same_defaults_as_typescript():
|
||||
"""Missing fields default to: stage=prospect, value=0, completed=False"""
|
||||
result = manage_sales_todos_impl([{"title": "Test"}])
|
||||
assert result[0]["stage"] == "prospect"
|
||||
assert result[0]["value"] == 0
|
||||
assert result[0]["completed"] == False
|
||||
assert result[0]["dueDate"] == ""
|
||||
assert result[0]["assignee"] == ""
|
||||
|
||||
|
||||
def test_get_todos_none_returns_initial():
|
||||
result = get_sales_todos_impl(None)
|
||||
assert len(result) == 3
|
||||
|
||||
|
||||
def test_get_todos_empty_returns_empty():
|
||||
result = get_sales_todos_impl([])
|
||||
assert result == []
|
||||
|
||||
|
||||
def test_search_flights_returns_a2ui_operations():
|
||||
result = search_flights_impl([{"airline": "Test"}])
|
||||
assert "a2ui_operations" in result
|
||||
|
||||
|
||||
def test_schedule_meeting_returns_pending():
|
||||
result = schedule_meeting_impl("test")
|
||||
assert result["status"] == "pending_approval"
|
||||
|
||||
|
||||
def test_query_data_returns_list_of_dicts_with_expected_columns():
|
||||
result = query_data_impl("test")
|
||||
assert isinstance(result, list)
|
||||
assert len(result) > 0
|
||||
row = result[0]
|
||||
assert "category" in row and "date" in row # Both columns must be present
|
||||
@@ -0,0 +1,110 @@
|
||||
"""Cross-package equivalence test.
|
||||
|
||||
Verifies that all showcase packages' backend tools produce structurally
|
||||
equivalent outputs when given identical inputs.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
from tools import (
|
||||
get_weather_impl,
|
||||
query_data_impl,
|
||||
manage_sales_todos_impl,
|
||||
get_sales_todos_impl,
|
||||
search_flights_impl,
|
||||
generate_a2ui_impl,
|
||||
schedule_meeting_impl,
|
||||
)
|
||||
|
||||
# These tests verify the SHARED implementations. Since all 17 packages
|
||||
# wrap these same functions, if the shared impls are correct, all
|
||||
# packages produce equivalent outputs.
|
||||
|
||||
|
||||
class TestToolOutputEquivalence:
|
||||
"""All tools return consistent structures regardless of caller."""
|
||||
|
||||
def test_weather_consistent_structure(self):
|
||||
cities = ["Tokyo", "London", "New York", "São Paulo", "Sydney"]
|
||||
for city in cities:
|
||||
result = get_weather_impl(city)
|
||||
assert set(result.keys()) == {
|
||||
"city",
|
||||
"temperature",
|
||||
"humidity",
|
||||
"wind_speed",
|
||||
"feels_like",
|
||||
"conditions",
|
||||
}
|
||||
assert result["city"] == city
|
||||
assert isinstance(result["temperature"], int)
|
||||
|
||||
def test_query_data_consistent_columns(self):
|
||||
for query in ["revenue", "expenses", "all", ""]:
|
||||
result = query_data_impl(query)
|
||||
assert len(result) > 0
|
||||
for row in result:
|
||||
assert "category" in row or "date" in row
|
||||
|
||||
def test_manage_todos_idempotent_structure(self):
|
||||
input_todos = [
|
||||
{"title": "Deal A", "stage": "prospect", "value": 10000},
|
||||
{"title": "Deal B", "stage": "qualified", "value": 50000},
|
||||
]
|
||||
result = manage_sales_todos_impl(input_todos)
|
||||
assert len(result) == 2
|
||||
for todo in result:
|
||||
assert all(
|
||||
k in todo
|
||||
for k in [
|
||||
"id",
|
||||
"title",
|
||||
"stage",
|
||||
"value",
|
||||
"dueDate",
|
||||
"assignee",
|
||||
"completed",
|
||||
]
|
||||
)
|
||||
|
||||
def test_get_todos_none_returns_initial(self):
|
||||
result = get_sales_todos_impl(None)
|
||||
assert len(result) == 3
|
||||
assert all(t["id"].startswith("st-") for t in result)
|
||||
|
||||
def test_search_flights_returns_a2ui_ops(self):
|
||||
flights = [
|
||||
{
|
||||
"airline": "Test",
|
||||
"flightNumber": "T1",
|
||||
"origin": "SFO",
|
||||
"destination": "JFK",
|
||||
"date": "Mon",
|
||||
"departureTime": "08:00",
|
||||
"arrivalTime": "16:00",
|
||||
"duration": "8h",
|
||||
"status": "On Time",
|
||||
"statusColor": "#22c55e",
|
||||
"price": "$300",
|
||||
"currency": "USD",
|
||||
"airlineLogo": "https://example.com/logo.png",
|
||||
}
|
||||
]
|
||||
result = search_flights_impl(flights)
|
||||
assert "a2ui_operations" in result
|
||||
ops = result["a2ui_operations"]
|
||||
assert any(op["type"] == "create_surface" for op in ops)
|
||||
assert any(op["type"] == "update_components" for op in ops)
|
||||
|
||||
def test_generate_a2ui_returns_prompt_and_schema(self):
|
||||
result = generate_a2ui_impl(
|
||||
messages=[{"role": "user", "content": "show dashboard"}]
|
||||
)
|
||||
assert "system_prompt" in result
|
||||
assert "tool_schema" in result
|
||||
assert result["tool_schema"]["name"] == "render_a2ui"
|
||||
|
||||
def test_schedule_meeting_returns_pending(self):
|
||||
result = schedule_meeting_impl("quarterly review", 45)
|
||||
assert result["status"] == "pending_approval"
|
||||
assert result["reason"] == "quarterly review"
|
||||
assert result["duration_minutes"] == 45
|
||||
@@ -0,0 +1,73 @@
|
||||
"""Framework wrapper import tests.
|
||||
|
||||
Verify that each showcase package's Python agent module can be imported
|
||||
and has the expected tools/functions defined. Skips packages whose
|
||||
framework dependencies aren't installed locally.
|
||||
"""
|
||||
|
||||
import importlib
|
||||
import sys
|
||||
import os
|
||||
import pytest
|
||||
|
||||
SHOWCASE_ROOT = os.path.join(os.path.dirname(__file__), "..", "..", "..", "packages")
|
||||
SHARED_PYTHON = os.path.join(os.path.dirname(__file__), "..")
|
||||
|
||||
# Ensure shared tools are importable
|
||||
if SHARED_PYTHON not in sys.path:
|
||||
sys.path.insert(0, SHARED_PYTHON)
|
||||
|
||||
|
||||
def _try_import_agent(package_name, agent_module_path, agent_module_name):
|
||||
"""Try to import a package's agent module. Returns (module, error)."""
|
||||
agent_dir = os.path.join(SHOWCASE_ROOT, package_name, *agent_module_path.split("/"))
|
||||
if agent_dir not in sys.path:
|
||||
sys.path.insert(0, agent_dir)
|
||||
try:
|
||||
# Remove cached module if present
|
||||
if agent_module_name in sys.modules:
|
||||
del sys.modules[agent_module_name]
|
||||
mod = importlib.import_module(agent_module_name)
|
||||
return mod, None
|
||||
except ImportError as e:
|
||||
return None, str(e)
|
||||
finally:
|
||||
if agent_dir in sys.path:
|
||||
sys.path.remove(agent_dir)
|
||||
|
||||
|
||||
# Each entry: (package_name, path_to_agent_dir, module_name, expected_attributes)
|
||||
PACKAGES = [
|
||||
("langgraph-python", "src", "agents.main", ["graph"]),
|
||||
(
|
||||
"langgraph-python",
|
||||
"src",
|
||||
"agents.tools",
|
||||
["query_data", "get_weather", "schedule_meeting"],
|
||||
),
|
||||
("langgraph-fastapi", "src/agents", "src.agent", ["graph"]),
|
||||
("pydantic-ai", "src", "agents.agent", ["agent"]),
|
||||
("crewai-crews", "src", "agents.crew", ["LatestAiDevelopment"]),
|
||||
("google-adk", "src", "agents.main", ["sales_pipeline_agent"]),
|
||||
("agno", "src", "agents.main", ["agent"]),
|
||||
("claude-sdk-python", "src", "agents.agent", ["create_app"]),
|
||||
("ag2", "src", "agents.agent", ["agent"]),
|
||||
("strands", "src", "agents.agent", ["strands_agent", "agui_agent"]),
|
||||
("llamaindex", "src", "agents.agent", ["agent_router"]),
|
||||
("langroid", "src", "agents.agent", ["create_agent"]),
|
||||
("ms-agent-python", "src", "agents.agent", ["create_agent"]),
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"pkg,path,mod_name,attrs", PACKAGES, ids=[p[0] for p in PACKAGES]
|
||||
)
|
||||
def test_agent_import(pkg, path, mod_name, attrs):
|
||||
"""Verify agent module imports and has expected attributes."""
|
||||
mod, err = _try_import_agent(pkg, path, mod_name)
|
||||
if err and ("No module named" in err or "cannot import name" in err):
|
||||
# Framework not installed locally — skip gracefully
|
||||
pytest.skip(f"Framework dependency not installed: {err}")
|
||||
assert mod is not None, f"Import failed for {pkg}: {err}"
|
||||
for attr in attrs:
|
||||
assert hasattr(mod, attr), f"{pkg} agent missing expected attribute: {attr}"
|
||||
@@ -0,0 +1,81 @@
|
||||
import pytest
|
||||
from tools import generate_a2ui_impl, build_a2ui_operations_from_tool_call
|
||||
|
||||
|
||||
def test_returns_system_prompt():
|
||||
result = generate_a2ui_impl(messages=[])
|
||||
assert "system_prompt" in result
|
||||
|
||||
|
||||
def test_returns_tool_schema():
|
||||
result = generate_a2ui_impl(messages=[])
|
||||
assert "tool_schema" in result
|
||||
assert result["tool_schema"]["name"] == "render_a2ui"
|
||||
|
||||
|
||||
def test_returns_tool_choice():
|
||||
result = generate_a2ui_impl(messages=[])
|
||||
assert result["tool_choice"] == "render_a2ui"
|
||||
|
||||
|
||||
def test_build_operations_basic():
|
||||
args = {"surfaceId": "s1", "catalogId": "cat1", "components": [{"id": "root"}]}
|
||||
result = build_a2ui_operations_from_tool_call(args)
|
||||
ops = result["a2ui_operations"]
|
||||
assert len(ops) == 2 # create_surface + update_components
|
||||
|
||||
|
||||
def test_build_operations_with_data():
|
||||
args = {
|
||||
"surfaceId": "s1",
|
||||
"catalogId": "cat1",
|
||||
"components": [{"id": "root"}],
|
||||
"data": {"key": "val"},
|
||||
}
|
||||
result = build_a2ui_operations_from_tool_call(args)
|
||||
ops = result["a2ui_operations"]
|
||||
assert len(ops) == 3 # create_surface + update_components + update_data_model
|
||||
|
||||
|
||||
def test_build_operations_empty_components_warns(caplog):
|
||||
import logging
|
||||
|
||||
with caplog.at_level(logging.WARNING):
|
||||
build_a2ui_operations_from_tool_call(
|
||||
{"surfaceId": "s1", "catalogId": "cat1", "components": []}
|
||||
)
|
||||
assert "empty components" in caplog.text.lower()
|
||||
|
||||
|
||||
def test_context_entries_with_values_appear_in_system_prompt():
|
||||
entries = [
|
||||
{"value": "The user is viewing a sales dashboard."},
|
||||
{"value": "Current quarter is Q2 2026."},
|
||||
]
|
||||
result = generate_a2ui_impl(
|
||||
messages=[{"role": "user", "content": "hello"}], context_entries=entries
|
||||
)
|
||||
assert "sales dashboard" in result["system_prompt"]
|
||||
assert "Q2 2026" in result["system_prompt"]
|
||||
|
||||
|
||||
def test_context_entries_missing_or_empty_values_filtered():
|
||||
entries = [
|
||||
{"value": "Keep this"},
|
||||
{"value": ""},
|
||||
{"other_key": "no value field"},
|
||||
{"value": None},
|
||||
]
|
||||
result = generate_a2ui_impl(messages=[], context_entries=entries)
|
||||
assert "Keep this" in result["system_prompt"]
|
||||
# Empty/missing values should not produce extra content
|
||||
assert "None" not in result["system_prompt"]
|
||||
|
||||
|
||||
def test_messages_pass_through_unchanged():
|
||||
msgs = [
|
||||
{"role": "user", "content": "hello"},
|
||||
{"role": "assistant", "content": "hi"},
|
||||
]
|
||||
result = generate_a2ui_impl(messages=msgs)
|
||||
assert result["messages"] is msgs # exact same reference
|
||||
@@ -0,0 +1,59 @@
|
||||
import pytest
|
||||
from tools import get_weather_impl
|
||||
|
||||
|
||||
def test_returns_all_required_fields():
|
||||
result = get_weather_impl("Tokyo")
|
||||
assert "city" in result
|
||||
assert "temperature" in result
|
||||
assert "humidity" in result
|
||||
assert "wind_speed" in result
|
||||
assert "feels_like" in result
|
||||
assert "conditions" in result
|
||||
|
||||
|
||||
def test_city_name_passed_through():
|
||||
result = get_weather_impl("San Francisco")
|
||||
assert result["city"] == "San Francisco"
|
||||
|
||||
|
||||
def test_deterministic_for_same_city():
|
||||
r1 = get_weather_impl("Tokyo")
|
||||
r2 = get_weather_impl("Tokyo")
|
||||
assert r1["temperature"] == r2["temperature"]
|
||||
assert r1["conditions"] == r2["conditions"]
|
||||
|
||||
|
||||
def test_different_cities_produce_different_results():
|
||||
r1 = get_weather_impl("Tokyo")
|
||||
r2 = get_weather_impl("London")
|
||||
# At least one field should differ (statistically guaranteed with seeded RNG)
|
||||
assert r1 != r2
|
||||
|
||||
|
||||
def test_temperature_in_valid_range():
|
||||
result = get_weather_impl("TestCity")
|
||||
assert 20 <= result["temperature"] <= 95
|
||||
|
||||
|
||||
def test_humidity_in_valid_range():
|
||||
result = get_weather_impl("TestCity")
|
||||
assert 30 <= result["humidity"] <= 90
|
||||
|
||||
|
||||
def test_case_insensitivity():
|
||||
r_lower = get_weather_impl("tokyo")
|
||||
r_upper = get_weather_impl("TOKYO")
|
||||
r_mixed = get_weather_impl("Tokyo")
|
||||
assert r_lower["temperature"] == r_upper["temperature"] == r_mixed["temperature"]
|
||||
assert r_lower["conditions"] == r_upper["conditions"] == r_mixed["conditions"]
|
||||
|
||||
|
||||
def test_feels_like_within_five_of_temperature():
|
||||
result = get_weather_impl("TestCity")
|
||||
assert abs(result["feels_like"] - result["temperature"]) <= 5
|
||||
|
||||
|
||||
def test_wind_speed_in_valid_range():
|
||||
result = get_weather_impl("TestCity")
|
||||
assert 2 <= result["wind_speed"] <= 30
|
||||
@@ -0,0 +1,62 @@
|
||||
import pytest
|
||||
import logging
|
||||
from unittest.mock import patch
|
||||
from tools import query_data_impl
|
||||
|
||||
|
||||
def test_returns_list():
|
||||
result = query_data_impl("any query")
|
||||
assert isinstance(result, list)
|
||||
|
||||
|
||||
def test_returns_nonempty():
|
||||
result = query_data_impl("revenue breakdown")
|
||||
assert len(result) > 0
|
||||
|
||||
|
||||
def test_rows_have_expected_columns():
|
||||
result = query_data_impl("test")
|
||||
row = result[0]
|
||||
assert "date" in row
|
||||
assert "category" in row
|
||||
assert "subcategory" in row
|
||||
assert "amount" in row
|
||||
assert "type" in row
|
||||
|
||||
|
||||
def test_query_param_doesnt_filter():
|
||||
r1 = query_data_impl("revenue")
|
||||
r2 = query_data_impl("expenses")
|
||||
assert len(r1) == len(r2) # same data regardless of query
|
||||
|
||||
|
||||
def test_all_six_columns_present():
|
||||
"""Verify all 6 expected columns including 'notes'."""
|
||||
result = query_data_impl("test")
|
||||
row = result[0]
|
||||
for col in ("date", "category", "subcategory", "amount", "type", "notes"):
|
||||
assert col in row, f"Missing column: {col}"
|
||||
|
||||
|
||||
def test_amount_is_string():
|
||||
"""Amount should be a string (CSV DictReader returns strings, mock data also uses strings)."""
|
||||
result = query_data_impl("test")
|
||||
for row in result:
|
||||
assert isinstance(row["amount"], str), (
|
||||
f"amount should be str, got {type(row['amount'])}"
|
||||
)
|
||||
|
||||
|
||||
def test_csv_fallback_uses_mock_data(caplog):
|
||||
"""When CSV path doesn't exist, module falls back to mock data with a warning."""
|
||||
# We can't easily re-trigger module-level loading, but we can verify the
|
||||
# mock data structure matches expectations — the _MOCK_DATA is what gets
|
||||
# used when CSV is missing.
|
||||
from tools.query_data import _MOCK_DATA
|
||||
|
||||
assert len(_MOCK_DATA) == 3
|
||||
for row in _MOCK_DATA:
|
||||
assert "date" in row
|
||||
assert "category" in row
|
||||
assert "notes" in row
|
||||
assert isinstance(row["amount"], str)
|
||||
@@ -0,0 +1,302 @@
|
||||
"""Tests for the render_mode middleware."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import sys
|
||||
import os
|
||||
|
||||
# Ensure the shared python package is importable.
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
|
||||
|
||||
from middleware.render_mode import (
|
||||
get_render_mode,
|
||||
get_output_schema,
|
||||
apply_render_mode_prompt,
|
||||
JSONL_RENDER_INSTRUCTION,
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# get_render_mode
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
class TestGetRenderMode:
|
||||
def test_default_when_empty(self):
|
||||
"""No context entries -> default to 'tool-based'."""
|
||||
assert get_render_mode([]) == "tool-based"
|
||||
|
||||
def test_default_when_no_match(self):
|
||||
"""Context entries exist but none with description 'render_mode'."""
|
||||
ctx = [{"description": "other", "value": "foo"}]
|
||||
assert get_render_mode(ctx) == "tool-based"
|
||||
|
||||
def test_hashbrown(self):
|
||||
"""Context with render_mode='hashbrown' is extracted."""
|
||||
ctx = [
|
||||
{"description": "something_else", "value": "x"},
|
||||
{"description": "render_mode", "value": "hashbrown"},
|
||||
]
|
||||
assert get_render_mode(ctx) == "hashbrown"
|
||||
|
||||
def test_a2ui(self):
|
||||
ctx = [{"description": "render_mode", "value": "a2ui"}]
|
||||
assert get_render_mode(ctx) == "a2ui"
|
||||
|
||||
def test_json_render(self):
|
||||
ctx = [{"description": "render_mode", "value": "json-render"}]
|
||||
assert get_render_mode(ctx) == "json-render"
|
||||
|
||||
def test_missing_value_defaults(self):
|
||||
"""Entry exists but value key is absent -> 'tool-based'."""
|
||||
ctx = [{"description": "render_mode"}]
|
||||
assert get_render_mode(ctx) == "tool-based"
|
||||
|
||||
# --- Additional tests ---
|
||||
|
||||
def test_render_mode_not_first_in_context(self):
|
||||
"""render_mode is the last of multiple context entries."""
|
||||
ctx = [
|
||||
{"description": "user_id", "value": "user-123"},
|
||||
{"description": "session_id", "value": "sess-456"},
|
||||
{"description": "locale", "value": "en-US"},
|
||||
{"description": "render_mode", "value": "a2ui"},
|
||||
]
|
||||
assert get_render_mode(ctx) == "a2ui"
|
||||
|
||||
def test_render_mode_in_middle_of_context(self):
|
||||
"""render_mode is sandwiched between other entries."""
|
||||
ctx = [
|
||||
{"description": "theme", "value": "dark"},
|
||||
{"description": "render_mode", "value": "json-render"},
|
||||
{"description": "feature_flags", "value": "beta"},
|
||||
]
|
||||
assert get_render_mode(ctx) == "json-render"
|
||||
|
||||
def test_invalid_render_mode_value_passes_through(self):
|
||||
"""An unrecognized render_mode value is returned as-is.
|
||||
|
||||
The middleware does not validate the value -- that is the
|
||||
responsibility of callers. This test documents that behavior.
|
||||
"""
|
||||
ctx = [{"description": "render_mode", "value": "not-a-real-mode"}]
|
||||
assert get_render_mode(ctx) == "not-a-real-mode"
|
||||
|
||||
def test_first_render_mode_entry_wins(self):
|
||||
"""When multiple render_mode entries exist, the first one wins."""
|
||||
ctx = [
|
||||
{"description": "render_mode", "value": "hashbrown"},
|
||||
{"description": "render_mode", "value": "a2ui"},
|
||||
]
|
||||
assert get_render_mode(ctx) == "hashbrown"
|
||||
|
||||
def test_tool_based_explicit(self):
|
||||
"""Explicit tool-based value is returned."""
|
||||
ctx = [{"description": "render_mode", "value": "tool-based"}]
|
||||
assert get_render_mode(ctx) == "tool-based"
|
||||
|
||||
def test_empty_string_value(self):
|
||||
"""Empty string value is returned (falsy but still a string)."""
|
||||
ctx = [{"description": "render_mode", "value": ""}]
|
||||
assert get_render_mode(ctx) == ""
|
||||
|
||||
def test_none_value_defaults(self):
|
||||
"""None value triggers the default via .get fallback."""
|
||||
ctx = [{"description": "render_mode", "value": None}]
|
||||
# .get("value", "tool-based") returns None (key exists), not default
|
||||
assert get_render_mode(ctx) is None
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# get_output_schema
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
class TestGetOutputSchema:
|
||||
def test_none_when_empty(self):
|
||||
assert get_output_schema([]) is None
|
||||
|
||||
def test_none_when_no_match(self):
|
||||
ctx = [{"description": "render_mode", "value": "hashbrown"}]
|
||||
assert get_output_schema(ctx) is None
|
||||
|
||||
def test_parses_json_string(self):
|
||||
schema = {"type": "object", "properties": {"temp": {"type": "number"}}}
|
||||
ctx = [{"description": "output_schema", "value": json.dumps(schema)}]
|
||||
result = get_output_schema(ctx)
|
||||
assert result == schema
|
||||
|
||||
def test_returns_dict_directly(self):
|
||||
schema = {"type": "object", "properties": {"name": {"type": "string"}}}
|
||||
ctx = [{"description": "output_schema", "value": schema}]
|
||||
result = get_output_schema(ctx)
|
||||
assert result == schema
|
||||
|
||||
def test_invalid_json_returns_none(self):
|
||||
ctx = [{"description": "output_schema", "value": "not-json{{{"}]
|
||||
assert get_output_schema(ctx) is None
|
||||
|
||||
# --- Additional tests ---
|
||||
|
||||
def test_json_string_vs_dict_both_work(self):
|
||||
"""Both JSON string and native dict should return the same result."""
|
||||
schema = {"type": "object", "properties": {"x": {"type": "integer"}}}
|
||||
ctx_str = [{"description": "output_schema", "value": json.dumps(schema)}]
|
||||
ctx_dict = [{"description": "output_schema", "value": schema}]
|
||||
assert get_output_schema(ctx_str) == get_output_schema(ctx_dict)
|
||||
|
||||
def test_complex_nested_schema(self):
|
||||
"""A deeply nested schema is handled correctly."""
|
||||
schema = {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"items": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {"type": "string"},
|
||||
"value": {"type": "number"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
ctx = [{"description": "output_schema", "value": json.dumps(schema)}]
|
||||
result = get_output_schema(ctx)
|
||||
assert result == schema
|
||||
|
||||
def test_output_schema_not_first_in_context(self):
|
||||
"""output_schema is found even when not the first entry."""
|
||||
schema = {"type": "object"}
|
||||
ctx = [
|
||||
{"description": "render_mode", "value": "hashbrown"},
|
||||
{"description": "user_id", "value": "u-1"},
|
||||
{"description": "output_schema", "value": schema},
|
||||
]
|
||||
result = get_output_schema(ctx)
|
||||
assert result == schema
|
||||
|
||||
def test_missing_value_key_returns_none(self):
|
||||
"""Entry with description=output_schema but no value key returns None."""
|
||||
ctx = [{"description": "output_schema"}]
|
||||
result = get_output_schema(ctx)
|
||||
assert result is None
|
||||
|
||||
def test_empty_dict_schema(self):
|
||||
"""An empty dict schema is still returned."""
|
||||
ctx = [{"description": "output_schema", "value": {}}]
|
||||
result = get_output_schema(ctx)
|
||||
assert result == {}
|
||||
|
||||
def test_integer_value_is_returned(self):
|
||||
"""Non-dict, non-string values are returned as-is."""
|
||||
ctx = [{"description": "output_schema", "value": 42}]
|
||||
result = get_output_schema(ctx)
|
||||
assert result == 42
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# apply_render_mode_prompt
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
class TestApplyRenderModePrompt:
|
||||
BASE = "You are a helpful agent."
|
||||
|
||||
def test_tool_based_unchanged(self):
|
||||
result = apply_render_mode_prompt(self.BASE, "tool-based")
|
||||
assert result == self.BASE
|
||||
|
||||
def test_a2ui_unchanged(self):
|
||||
result = apply_render_mode_prompt(self.BASE, "a2ui")
|
||||
assert result == self.BASE
|
||||
|
||||
def test_json_render_appends_jsonl_instruction(self):
|
||||
result = apply_render_mode_prompt(self.BASE, "json-render")
|
||||
assert result.startswith(self.BASE)
|
||||
assert JSONL_RENDER_INSTRUCTION in result
|
||||
assert "```spec" in result
|
||||
assert "JSONL" in result
|
||||
|
||||
def test_unknown_mode_unchanged(self):
|
||||
result = apply_render_mode_prompt(self.BASE, "future-mode")
|
||||
assert result == self.BASE
|
||||
|
||||
# --- Additional tests ---
|
||||
|
||||
def test_json_render_contains_op_field_instruction(self):
|
||||
"""JSONL instruction mentions op field for patch objects."""
|
||||
result = apply_render_mode_prompt(self.BASE, "json-render")
|
||||
assert '"op"' in result
|
||||
assert "add" in result
|
||||
assert "replace" in result
|
||||
assert "remove" in result
|
||||
|
||||
def test_json_render_contains_path_field_instruction(self):
|
||||
"""JSONL instruction mentions path field (JSON-Pointer)."""
|
||||
result = apply_render_mode_prompt(self.BASE, "json-render")
|
||||
assert '"path"' in result or "path" in result
|
||||
|
||||
def test_hashbrown_unchanged(self):
|
||||
"""HashBrown mode does not modify the prompt (structured output is via response_format)."""
|
||||
result = apply_render_mode_prompt(self.BASE, "hashbrown")
|
||||
assert result == self.BASE
|
||||
|
||||
def test_empty_base_prompt_still_works(self):
|
||||
"""An empty base prompt gets the instruction appended."""
|
||||
result = apply_render_mode_prompt("", "json-render")
|
||||
assert JSONL_RENDER_INSTRUCTION in result
|
||||
|
||||
def test_prompt_injection_content_preserved(self):
|
||||
"""Base prompt with special characters is preserved verbatim."""
|
||||
tricky_base = "You are an agent. Do NOT output ```json blocks."
|
||||
result = apply_render_mode_prompt(tricky_base, "json-render")
|
||||
assert result.startswith(tricky_base)
|
||||
assert JSONL_RENDER_INSTRUCTION in result
|
||||
|
||||
def test_json_render_instruction_is_exact_constant(self):
|
||||
"""The appended instruction is exactly the JSONL_RENDER_INSTRUCTION constant."""
|
||||
result = apply_render_mode_prompt(self.BASE, "json-render")
|
||||
assert result == self.BASE + JSONL_RENDER_INSTRUCTION
|
||||
|
||||
def test_empty_string_mode_unchanged(self):
|
||||
"""Empty string as mode returns prompt unchanged."""
|
||||
result = apply_render_mode_prompt(self.BASE, "")
|
||||
assert result == self.BASE
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# HashBrown mode with missing output_schema (should not crash)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
class TestHashBrownMissingSchema:
|
||||
def test_no_output_schema_entry_returns_none(self):
|
||||
"""HashBrown mode with no output_schema in context returns None from get_output_schema."""
|
||||
ctx = [{"description": "render_mode", "value": "hashbrown"}]
|
||||
assert get_output_schema(ctx) is None
|
||||
|
||||
def test_hashbrown_mode_with_no_schema_does_not_modify_prompt(self):
|
||||
"""HashBrown mode does not add prompt instructions even without a schema."""
|
||||
base = "System prompt."
|
||||
result = apply_render_mode_prompt(base, "hashbrown")
|
||||
assert result == base
|
||||
|
||||
def test_hashbrown_mode_with_null_schema_value(self):
|
||||
"""output_schema entry with None value returns None."""
|
||||
ctx = [
|
||||
{"description": "render_mode", "value": "hashbrown"},
|
||||
{"description": "output_schema", "value": None},
|
||||
]
|
||||
assert get_output_schema(ctx) is None
|
||||
|
||||
def test_hashbrown_mode_with_empty_string_schema(self):
|
||||
"""output_schema with empty string returns None (invalid JSON)."""
|
||||
ctx = [
|
||||
{"description": "render_mode", "value": "hashbrown"},
|
||||
{"description": "output_schema", "value": ""},
|
||||
]
|
||||
# Empty string -> json.loads raises -> returns None
|
||||
assert get_output_schema(ctx) is None
|
||||
@@ -0,0 +1,60 @@
|
||||
import pytest
|
||||
from tools import manage_sales_todos_impl, get_sales_todos_impl, INITIAL_TODOS
|
||||
|
||||
|
||||
def test_initial_todos_have_fixed_ids():
|
||||
assert INITIAL_TODOS[0]["id"] == "st-001"
|
||||
assert INITIAL_TODOS[1]["id"] == "st-002"
|
||||
assert INITIAL_TODOS[2]["id"] == "st-003"
|
||||
|
||||
|
||||
def test_initial_todos_count():
|
||||
assert len(INITIAL_TODOS) == 3
|
||||
|
||||
|
||||
def test_manage_assigns_id_to_missing():
|
||||
result = manage_sales_todos_impl([{"title": "New deal"}])
|
||||
assert result[0]["id"] # should have an ID assigned
|
||||
assert len(result[0]["id"]) > 0
|
||||
|
||||
|
||||
def test_manage_preserves_existing_id():
|
||||
result = manage_sales_todos_impl([{"id": "keep-me", "title": "Deal"}])
|
||||
assert result[0]["id"] == "keep-me"
|
||||
|
||||
|
||||
def test_manage_provides_defaults():
|
||||
result = manage_sales_todos_impl([{"title": "Minimal"}])
|
||||
assert result[0]["stage"] == "prospect"
|
||||
assert result[0]["value"] == 0
|
||||
assert result[0]["dueDate"] == ""
|
||||
assert result[0]["assignee"] == ""
|
||||
assert result[0]["completed"] == False
|
||||
|
||||
|
||||
def test_get_returns_initial_when_none():
|
||||
result = get_sales_todos_impl(None)
|
||||
assert len(result) == 3
|
||||
assert result[0]["id"] == "st-001"
|
||||
|
||||
|
||||
def test_get_returns_empty_when_empty_list():
|
||||
result = get_sales_todos_impl([])
|
||||
assert result == []
|
||||
|
||||
|
||||
def test_get_returns_provided_todos():
|
||||
todos = [
|
||||
{
|
||||
"id": "1",
|
||||
"title": "Test",
|
||||
"stage": "prospect",
|
||||
"value": 100,
|
||||
"dueDate": "",
|
||||
"assignee": "",
|
||||
"completed": False,
|
||||
}
|
||||
]
|
||||
result = get_sales_todos_impl(todos)
|
||||
assert len(result) == 1
|
||||
assert result[0]["title"] == "Test"
|
||||
@@ -0,0 +1,22 @@
|
||||
import pytest
|
||||
from tools import schedule_meeting_impl
|
||||
|
||||
|
||||
def test_returns_pending_status():
|
||||
result = schedule_meeting_impl("discuss roadmap")
|
||||
assert result["status"] == "pending_approval"
|
||||
|
||||
|
||||
def test_includes_reason():
|
||||
result = schedule_meeting_impl("quarterly review")
|
||||
assert result["reason"] == "quarterly review"
|
||||
|
||||
|
||||
def test_includes_duration_minutes():
|
||||
result = schedule_meeting_impl("sync", 45)
|
||||
assert result["duration_minutes"] == 45
|
||||
|
||||
|
||||
def test_default_duration():
|
||||
result = schedule_meeting_impl("sync")
|
||||
assert result["duration_minutes"] == 30
|
||||
@@ -0,0 +1,85 @@
|
||||
import pytest
|
||||
from tools import search_flights_impl
|
||||
from tools.search_flights import SURFACE_ID, CATALOG_ID
|
||||
|
||||
_FULL_FLIGHT = {
|
||||
"airline": "Test Air",
|
||||
"flightNumber": "TA100",
|
||||
"origin": "SFO",
|
||||
"destination": "JFK",
|
||||
"date": "Tue, Apr 15",
|
||||
"departureTime": "08:00",
|
||||
"arrivalTime": "16:00",
|
||||
"duration": "5h",
|
||||
"status": "On Time",
|
||||
"statusColor": "#22c55e",
|
||||
"price": "$299",
|
||||
"currency": "USD",
|
||||
"airlineLogo": "https://example.com/logo.png",
|
||||
}
|
||||
|
||||
|
||||
def test_returns_a2ui_operations():
|
||||
result = search_flights_impl([_FULL_FLIGHT])
|
||||
assert "a2ui_operations" in result
|
||||
|
||||
|
||||
def test_operations_structure():
|
||||
flights = [{"airline": "Test"}]
|
||||
result = search_flights_impl(flights)
|
||||
ops = result["a2ui_operations"]
|
||||
assert any(op["type"] == "create_surface" for op in ops)
|
||||
assert any(op["type"] == "update_components" for op in ops)
|
||||
|
||||
|
||||
def test_all_three_operation_types_present():
|
||||
result = search_flights_impl([_FULL_FLIGHT])
|
||||
ops = result["a2ui_operations"]
|
||||
types = [op["type"] for op in ops]
|
||||
assert "create_surface" in types
|
||||
assert "update_components" in types
|
||||
assert "update_data_model" in types
|
||||
|
||||
|
||||
def test_surface_and_catalog_ids():
|
||||
result = search_flights_impl([_FULL_FLIGHT])
|
||||
ops = result["a2ui_operations"]
|
||||
create_op = next(op for op in ops if op["type"] == "create_surface")
|
||||
assert create_op["surfaceId"] == SURFACE_ID
|
||||
assert create_op["catalogId"] == CATALOG_ID
|
||||
|
||||
|
||||
def test_flight_data_embedded_in_data_model():
|
||||
flights = [_FULL_FLIGHT, {"airline": "Second Air"}]
|
||||
result = search_flights_impl(flights)
|
||||
ops = result["a2ui_operations"]
|
||||
data_op = next(op for op in ops if op["type"] == "update_data_model")
|
||||
assert data_op["data"]["flights"] == flights
|
||||
|
||||
|
||||
def test_empty_flights_list():
|
||||
result = search_flights_impl([])
|
||||
ops = result["a2ui_operations"]
|
||||
assert len(ops) == 3
|
||||
data_op = next(op for op in ops if op["type"] == "update_data_model")
|
||||
assert data_op["data"]["flights"] == []
|
||||
|
||||
|
||||
def test_properly_formed_flight_objects():
|
||||
result = search_flights_impl([_FULL_FLIGHT])
|
||||
ops = result["a2ui_operations"]
|
||||
data_op = next(op for op in ops if op["type"] == "update_data_model")
|
||||
flight = data_op["data"]["flights"][0]
|
||||
for key in (
|
||||
"airline",
|
||||
"flightNumber",
|
||||
"origin",
|
||||
"destination",
|
||||
"date",
|
||||
"departureTime",
|
||||
"arrivalTime",
|
||||
"duration",
|
||||
"status",
|
||||
"price",
|
||||
):
|
||||
assert key in flight
|
||||
Reference in New Issue
Block a user