You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

941 lines
34 KiB

"""
Comprehensive eval dataset for the Ghostfolio AI Agent.
50+ test cases organized into 4 rubric-required categories:
- Happy Path (20+ tests): normal successful user journeys
- Edge Cases (10+ tests): boundary conditions, missing data, zero values
- Adversarial (10+ tests): bad inputs, injection attempts, extreme values
- Multi-Step (10+ tests): chained tool calls, stateful flows
Every test documents:
TYPE, INPUT (what the user asked), EXPECTED (what should happen),
CRITERIA (how pass/fail is determined).
Network calls: all Teleport API calls are mocked via conftest.py autouse
fixture (mock_teleport_no_network). Tests are deterministic and fast.
"""
import asyncio
import os
import sys
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "tools"))
os.environ.setdefault("ENABLE_REAL_ESTATE", "true")
os.environ.setdefault("PROPERTIES_DB_PATH", ":memory:")
import pytest
# ---------------------------------------------------------------------------
# Helper: run coroutine from sync context
# ---------------------------------------------------------------------------
def _run(coro):
loop = asyncio.new_event_loop()
try:
return loop.run_until_complete(coro)
finally:
loop.close()
def _clear():
from property_tracker import property_store_clear
property_store_clear()
# ============================================================================
# HAPPY PATH TESTS (20 tests)
# ============================================================================
# TYPE: happy_path
# INPUT: add home with purchase price, current value, and mortgage
# EXPECTED: property created, equity = current_value - mortgage_balance
# CRITERIA: equity == 175000, success == True
def test_hp_add_property_basic():
_clear()
from property_tracker import add_property
result = _run(add_property(
address="My Primary Home",
purchase_price=420000,
current_value=490000,
mortgage_balance=315000,
))
assert result["success"] is True
prop = result["result"]["property"]
assert prop["equity"] == pytest.approx(175000)
# TYPE: happy_path
# INPUT: add two properties, check combined equity
# EXPECTED: total equity = sum of both properties' equity
# CRITERIA: total_equity matches manual calculation
def test_hp_add_two_properties_combined_equity():
_clear()
from property_tracker import add_property, get_real_estate_equity
_run(add_property("Home A", 400000, 480000, 300000)) # equity=180000
_run(add_property("Home B", 300000, 350000, 200000)) # equity=150000
result = _run(get_real_estate_equity())
assert result["success"] is True
assert result["result"]["total_real_estate_equity"] == pytest.approx(330000)
assert result["result"]["property_count"] == 2
# TYPE: happy_path
# INPUT: get total net worth with portfolio and property
# EXPECTED: total = portfolio + real estate equity
# CRITERIA: total_net_worth == 94000 + 175000 = 269000
def test_hp_total_net_worth_combined():
_clear()
from property_tracker import add_property, get_total_net_worth
_run(add_property("Test Home", 420000, 490000, 315000)) # equity=175000
result = _run(get_total_net_worth(portfolio_value=94000))
assert result["success"] is True
assert result["result"]["total_net_worth"] == pytest.approx(269000)
assert result["result"]["investment_portfolio"] == 94000
assert result["result"]["real_estate_equity"] == pytest.approx(175000)
# TYPE: happy_path
# INPUT: strategy simulation with 10-year horizon
# EXPECTED: final net worth exceeds starting portfolio
# CRITERIA: total_net_worth > initial_portfolio_value
def test_hp_strategy_10_year_growth():
from realestate_strategy import simulate_real_estate_strategy
result = simulate_real_estate_strategy(94000, 120000, 400000, total_years=10)
assert result is not None
assert "final_picture" in result
assert result["final_picture"]["total_net_worth"] > 94000
# TYPE: happy_path
# INPUT: strategy simulation with user-provided 3% appreciation
# EXPECTED: strategy uses 3% not default 4%
# CRITERIA: assumptions show 3.0%
def test_hp_strategy_user_appreciation():
from realestate_strategy import simulate_real_estate_strategy
result = simulate_real_estate_strategy(
94000, 120000, 400000,
annual_appreciation=0.03,
)
assert result["strategy"]["assumptions"]["annual_appreciation"] == "3.0%"
# TYPE: happy_path
# INPUT: analyze wealth position for 34-year-old with $94k portfolio
# EXPECTED: Fed Reserve comparison with correct median for under_35 bracket
# CRITERIA: median_for_age == 39000, percentile_estimate present
def test_hp_wealth_position_age_34():
from wealth_visualizer import analyze_wealth_position
result = analyze_wealth_position(94000, 34, 120000)
assert "current_position" in result
assert result["current_position"]["median_for_age"] == 39000
assert "percentile_estimate" in result["current_position"]
assert "retirement_projection" in result
# TYPE: happy_path
# INPUT: analyze wealth position for 42-year-old
# EXPECTED: uses 35_to_44 bracket median of $135,000
# CRITERIA: median_for_age == 135000
def test_hp_wealth_position_age_42():
from wealth_visualizer import analyze_wealth_position
result = analyze_wealth_position(200000, 42, 150000)
assert result["current_position"]["median_for_age"] == 135000
# TYPE: happy_path
# INPUT: equity options for property with substantial equity
# EXPECTED: 3 distinct options returned (keep, refi, rental)
# CRITERIA: len(options) >= 3, each option has projection
def test_hp_equity_options_three_scenarios():
_clear()
from property_tracker import add_property, analyze_equity_options
prop = _run(add_property("Equity Home", 400000, 520000, 370000))
pid = prop["result"]["property"]["id"]
result = analyze_equity_options(pid)
assert "options" in result
assert len(result["options"]) >= 3
# TYPE: happy_path
# INPUT: family planning for Austin with 1 child
# EXPECTED: childcare costs, monthly surplus, income_impact
# CRITERIA: income_impact present, monthly_surplus_after is a number
def test_hp_family_plan_one_child_austin():
from family_planner import plan_family_finances
result = plan_family_finances("Austin", 120000, num_planned_children=1)
assert "income_impact" in result
assert "monthly_surplus_after" in result["income_impact"]
assert isinstance(result["income_impact"]["monthly_surplus_after"], (int, float))
# TYPE: happy_path
# INPUT: relocation runway calculation Austin → Seattle
# EXPECTED: verdict returned, months to emergency fund calculated
# CRITERIA: verdict present, milestones_if_you_move has emergency_fund milestone
def test_hp_relocation_runway_seattle():
from relocation_runway import calculate_relocation_runway
result = calculate_relocation_runway(
current_salary=120000,
offer_salary=180000,
current_city="Austin",
destination_city="Seattle",
portfolio_value=94000,
)
assert "verdict" in result
assert "milestones_if_you_move" in result
assert "months_to_6mo_emergency_fund" in result["milestones_if_you_move"]
# TYPE: happy_path
# INPUT: job offer affordability check Austin → SF
# EXPECTED: COL-adjusted comparison, is_real_raise boolean
# CRITERIA: is_real_raise present, verdict non-empty
@pytest.mark.asyncio
async def test_hp_job_offer_affordability():
from wealth_bridge import calculate_job_offer_affordability
result = await calculate_job_offer_affordability(
offer_salary=180000,
offer_city="Seattle",
current_salary=120000,
current_city="Austin",
)
assert "is_real_raise" in result
assert isinstance(result["is_real_raise"], bool)
assert "verdict" in result
assert len(result["verdict"]) > 10
# TYPE: happy_path
# INPUT: down payment power for $94k portfolio
# EXPECTED: at least one Austin-area market affordable at full 20% down
# CRITERIA: can_afford_full True for at least one market
def test_hp_down_payment_94k_portfolio():
from wealth_bridge import calculate_down_payment_power
result = calculate_down_payment_power(94000)
assert "markets" in result
affordable = [m for m in result["markets"] if m["can_afford_full"]]
assert len(affordable) > 0
# TYPE: happy_path
# INPUT: list properties when one exists
# EXPECTED: property appears in list with correct fields
# CRITERIA: len(properties) == 1, equity field present
def test_hp_list_properties_one():
_clear()
from property_tracker import add_property, get_properties
_run(add_property("My Home", 400000, 480000, 320000))
result = _run(get_properties())
assert result["success"] is True
props = result["result"]["properties"]
assert len(props) == 1
assert "equity" in props[0]
assert props[0]["equity"] == pytest.approx(160000)
# TYPE: happy_path
# INPUT: remove property by ID
# EXPECTED: property no longer in list
# CRITERIA: property count drops from 1 to 0
def test_hp_remove_property_success():
_clear()
from property_tracker import add_property, remove_property, get_properties
prop = _run(add_property("Remove Me", 300000, 350000, 200000))
pid = prop["result"]["property"]["id"]
removed = _run(remove_property(pid))
assert removed["success"] is True
listed = _run(get_properties())
ids = [p["id"] for p in listed["result"]["properties"]]
assert pid not in ids
# TYPE: happy_path
# INPUT: update property current value
# EXPECTED: equity recalculates correctly
# CRITERIA: equity increases after value update
def test_hp_update_property_value():
_clear()
from property_tracker import add_property, update_property
prop = _run(add_property("Update Test", 400000, 450000, 320000))
pid = prop["result"]["property"]["id"]
updated = _run(update_property(pid, current_value=470000))
assert updated["success"] is True
new_equity = updated["result"]["property"]["equity"]
assert new_equity == pytest.approx(150000) # 470000 - 320000
# TYPE: happy_path
# INPUT: strategy simulation single property scenario
# EXPECTED: result contains at least 1 property
# CRITERIA: properties_owned >= 1
def test_hp_strategy_single_property():
from realestate_strategy import simulate_real_estate_strategy
result = simulate_real_estate_strategy(
94000, 120000, 400000,
buy_interval_years=10, # only 1 purchase in 10 years
total_years=10,
)
assert result["final_picture"]["num_properties_owned"] >= 1
# TYPE: happy_path
# INPUT: family plan with partner income
# EXPECTED: household income reflects both incomes
# CRITERIA: total_household_income > single income
def test_hp_family_plan_with_partner():
from family_planner import plan_family_finances
result = plan_family_finances("Austin", 120000, partner_income=80000, num_planned_children=1)
assert "income_impact" in result
data = result["income_impact"]
assert "total_household_income" in data or "monthly_surplus_after" in data
# TYPE: happy_path
# INPUT: wealth position with real estate equity added
# EXPECTED: total net worth includes real estate
# CRITERIA: total_net_worth > portfolio_value alone
def test_hp_wealth_position_with_real_estate():
from wealth_visualizer import analyze_wealth_position
result = analyze_wealth_position(
portfolio_value=94000,
age=34,
annual_income=120000,
real_estate_equity=175000,
)
assert "current_position" in result
pos = result["current_position"]
assert pos["total_net_worth"] == pytest.approx(269000)
# TYPE: happy_path
# INPUT: relocation to cheaper city (SF → Austin)
# EXPECTED: destination surplus higher than current, verdict positive
# CRITERIA: destination monthly_surplus > 0, verdict present
def test_hp_relocation_to_cheaper_city():
from relocation_runway import calculate_relocation_runway
result = calculate_relocation_runway(
current_salary=150000,
offer_salary=140000,
current_city="San Francisco",
destination_city="Austin",
portfolio_value=100000,
)
assert "verdict" in result
dest_surplus = result["destination_monthly"]["monthly_surplus"]
curr_surplus = result["current_monthly"]["monthly_surplus"]
assert dest_surplus > curr_surplus
# TYPE: happy_path
# INPUT: strategy with conservative appreciation preset
# EXPECTED: conservative < optimistic final net worth
# CRITERIA: conservative_net_worth < optimistic_net_worth
def test_hp_strategy_conservative_vs_optimistic():
from realestate_strategy import simulate_real_estate_strategy
conservative = simulate_real_estate_strategy(
94000, 120000, 400000, annual_appreciation=0.02,
)
optimistic = simulate_real_estate_strategy(
94000, 120000, 400000, annual_appreciation=0.06,
)
assert (conservative["final_picture"]["total_net_worth"] <
optimistic["final_picture"]["total_net_worth"])
# ============================================================================
# EDGE CASE TESTS (12 tests)
# ============================================================================
# TYPE: edge_case
# INPUT: portfolio value of zero
# EXPECTED: graceful response, not a crash
# CRITERIA: returns dict, no exception
def test_ec_zero_portfolio_value():
from wealth_visualizer import analyze_wealth_position
result = analyze_wealth_position(0, 30, 50000)
assert result is not None
assert isinstance(result, dict)
assert "current_position" in result
# TYPE: edge_case
# INPUT: property with no mortgage (fully paid off)
# EXPECTED: equity equals current value
# CRITERIA: equity == current_value, equity_pct == 100.0
def test_ec_paid_off_property():
_clear()
from property_tracker import add_property
result = _run(add_property(
address="Paid Off Home",
purchase_price=300000,
current_value=380000,
mortgage_balance=0,
))
prop = result["result"]["property"]
assert prop["equity"] == pytest.approx(380000)
assert prop["equity_pct"] == pytest.approx(100.0)
# TYPE: edge_case
# INPUT: strategy simulation for just 1 year
# EXPECTED: returns valid result, no crash
# CRITERIA: result has final_picture and timeline, no exception
def test_ec_strategy_single_year():
from realestate_strategy import simulate_real_estate_strategy
result = simulate_real_estate_strategy(50000, 80000, 300000, total_years=1)
assert result is not None
assert "final_picture" in result
assert "timeline" in result
assert isinstance(result["final_picture"]["num_properties_owned"], int)
# TYPE: edge_case
# INPUT: equity options on nonexistent property ID
# EXPECTED: error dict returned, not exception
# CRITERIA: "error" key present in result
def test_ec_equity_nonexistent_property():
from property_tracker import analyze_equity_options
result = analyze_equity_options("does-not-exist-999")
assert result is not None
assert "error" in result
# TYPE: edge_case
# INPUT: family planner with zero income
# EXPECTED: graceful response, no ZeroDivisionError
# CRITERIA: returns dict, no exception raised
def test_ec_family_planner_zero_income():
from family_planner import plan_family_finances
result = plan_family_finances("Austin", 0, num_planned_children=1)
assert result is not None
assert isinstance(result, dict)
# TYPE: edge_case
# INPUT: wealth position for someone at exactly the median
# EXPECTED: correctly identified as median tier
# CRITERIA: percentile_estimate contains "50th" or similar
def test_ec_wealth_exactly_at_median():
from wealth_visualizer import analyze_wealth_position
# median for under_35 is 39000
result = analyze_wealth_position(39000, 30, 60000)
pos = result["current_position"]
assert "50th" in pos["percentile_estimate"] or "median" in pos["percentile_estimate"].lower()
# TYPE: edge_case
# INPUT: empty property list — get net worth with no properties
# EXPECTED: net worth equals portfolio value, real estate equity is 0
# CRITERIA: total_net_worth == portfolio_value, real_estate_equity == 0
def test_ec_net_worth_no_properties():
_clear()
from property_tracker import get_total_net_worth
result = _run(get_total_net_worth(portfolio_value=50000))
assert result["success"] is True
assert result["result"]["total_net_worth"] == pytest.approx(50000)
assert result["result"]["real_estate_equity"] == 0
# TYPE: edge_case
# INPUT: relocation with identical source and destination city
# EXPECTED: no crash, verdict makes sense
# CRITERIA: returns dict with verdict field
def test_ec_same_city_relocation():
from relocation_runway import calculate_relocation_runway
result = calculate_relocation_runway(
current_salary=120000,
offer_salary=125000,
current_city="Austin",
destination_city="Austin",
portfolio_value=94000,
)
assert result is not None
assert "verdict" in result
# TYPE: edge_case
# INPUT: strategy with very large portfolio (1M)
# EXPECTED: simulation completes without overflow
# CRITERIA: total_net_worth is a positive finite number
def test_ec_strategy_large_portfolio():
from realestate_strategy import simulate_real_estate_strategy
result = simulate_real_estate_strategy(
initial_portfolio_value=1_000_000,
annual_income=300000,
first_home_price=1_500_000,
total_years=10,
)
assert result is not None
nw = result["final_picture"]["total_net_worth"]
assert nw > 0
assert nw < 1e15 # sanity check: not overflowing
# TYPE: edge_case
# INPUT: analyze property with equity exceeding current value (impossible)
# EXPECTED: graceful handling, equity capped or error message
# CRITERIA: no crash, returns dict
def test_ec_mortgage_exceeds_value():
_clear()
from property_tracker import add_property
result = _run(add_property(
address="Underwater Property",
purchase_price=400000,
current_value=300000,
mortgage_balance=380000,
))
# Should succeed but equity will be negative (underwater)
assert result["success"] is True
prop = result["result"]["property"]
equity = prop["equity"]
# underwater: equity = 300000 - 380000 = -80000 (valid, just negative)
assert isinstance(equity, (int, float))
# TYPE: edge_case
# INPUT: family plan for 5 children
# EXPECTED: costs scaled proportionally, no crash
# CRITERIA: childcare costs for 5 > childcare for 1
def test_ec_family_many_children():
from family_planner import plan_family_finances
result1 = plan_family_finances("Austin", 200000, num_planned_children=1)
result5 = plan_family_finances("Austin", 200000, num_planned_children=5)
assert result1 is not None and result5 is not None
# Costs for 5 children should be higher
s1 = result1["income_impact"]["monthly_surplus_after"]
s5 = result5["income_impact"]["monthly_surplus_after"]
assert s5 < s1 # more children = lower surplus
# TYPE: edge_case
# INPUT: wealth position for very old age (80+)
# EXPECTED: uses highest bracket (65+), no crash
# CRITERIA: median_for_age == 409000 (65_to_74 bracket)
def test_ec_wealth_very_old_age():
from wealth_visualizer import analyze_wealth_position
result = analyze_wealth_position(500000, 82, 60000)
assert result is not None
assert result["current_position"]["median_for_age"] == 409000
# ============================================================================
# ADVERSARIAL TESTS (12 tests)
# ============================================================================
# TYPE: adversarial
# INPUT: property address contains SQL injection
# EXPECTED: stored safely as a string, DB still works after
# CRITERIA: no exception, subsequent queries work normally
def test_adv_sql_injection_address():
_clear()
from property_tracker import add_property, get_properties
malicious = "'; DROP TABLE properties; --"
result = _run(add_property(
address=malicious,
purchase_price=300000,
current_value=350000,
mortgage_balance=200000,
))
assert result["success"] is True
# DB still works after the injection attempt
props = _run(get_properties())
assert props["success"] is True
# TYPE: adversarial
# INPUT: negative property values
# EXPECTED: validation catches it OR handles gracefully
# CRITERIA: no uncaught exception
def test_adv_negative_property_value():
_clear()
from property_tracker import add_property
try:
result = _run(add_property(
address="Bad Property",
purchase_price=-100000,
current_value=-50000,
mortgage_balance=0,
))
# If it accepts, result should be a dict
assert result is not None
except (ValueError, AssertionError):
pass # Rejecting invalid input is correct behavior
# TYPE: adversarial
# INPUT: impossible appreciation rate (1000% = 10.0)
# EXPECTED: simulation runs without crash
# CRITERIA: returns result dict with final_picture
def test_adv_extreme_appreciation_rate():
from realestate_strategy import simulate_real_estate_strategy
result = simulate_real_estate_strategy(
94000, 120000, 400000,
annual_appreciation=10.0, # 1000% — extreme input
)
assert result is not None
assert "final_picture" in result
# TYPE: adversarial
# INPUT: strategy with zero annual income
# EXPECTED: graceful, not ZeroDivision
# CRITERIA: returns dict, no crash
def test_adv_strategy_zero_income():
from realestate_strategy import simulate_real_estate_strategy
try:
result = simulate_real_estate_strategy(
initial_portfolio_value=100000,
annual_income=0,
first_home_price=300000,
total_years=5,
)
assert result is not None
assert isinstance(result, dict)
except ZeroDivisionError:
pytest.fail("ZeroDivisionError raised with zero income")
# TYPE: adversarial
# INPUT: mortgage rate of 100% (extreme)
# EXPECTED: simulation completes, not crash
# CRITERIA: result is a dict with final_picture
def test_adv_extreme_mortgage_rate():
from realestate_strategy import simulate_real_estate_strategy
result = simulate_real_estate_strategy(
94000, 120000, 400000,
mortgage_rate=1.00, # 100% rate
total_years=5,
)
assert result is not None
assert "final_picture" in result
# TYPE: adversarial
# INPUT: address that is only whitespace
# EXPECTED: validation returns structured error
# CRITERIA: success=False, error dict present
def test_adv_whitespace_address():
_clear()
from property_tracker import add_property
result = _run(add_property(address=" \t\n ", purchase_price=300000))
assert result["success"] is False
assert "error" in result
# TYPE: adversarial
# INPUT: purchase price is zero
# EXPECTED: validation error returned
# CRITERIA: success=False
def test_adv_zero_purchase_price():
_clear()
from property_tracker import add_property
result = _run(add_property(address="Test", purchase_price=0))
assert result["success"] is False
# TYPE: adversarial
# INPUT: wealth analysis with negative portfolio
# EXPECTED: graceful handling, no crash
# CRITERIA: returns dict
def test_adv_negative_portfolio_wealth():
from wealth_visualizer import analyze_wealth_position
try:
result = analyze_wealth_position(-5000, 30, 60000)
assert result is not None
assert isinstance(result, dict)
except Exception:
pass # Rejecting negative portfolio is acceptable
# TYPE: adversarial
# INPUT: family plan for a city not in the lookup
# EXPECTED: uses default costs, no crash
# CRITERIA: returns dict with income_impact
def test_adv_unknown_city_family_plan():
from family_planner import plan_family_finances
result = plan_family_finances("Xanadu City", 80000, num_planned_children=1)
assert result is not None
assert "income_impact" in result
# TYPE: adversarial
# INPUT: remove a nonexistent property ID
# EXPECTED: structured error dict returned
# CRITERIA: success=False, error code = NOT_FOUND
def test_adv_remove_nonexistent_property():
_clear()
from property_tracker import remove_property
result = _run(remove_property("nonexistent-id-999"))
assert result["success"] is False
assert isinstance(result["error"], dict)
assert result["error"]["code"] == "PROPERTY_TRACKER_NOT_FOUND"
# TYPE: adversarial
# INPUT: strategy with negative down payment percentage
# EXPECTED: uses fallback/default or handles gracefully
# CRITERIA: no crash
def test_adv_negative_down_payment_pct():
from realestate_strategy import simulate_real_estate_strategy
try:
result = simulate_real_estate_strategy(
94000, 120000, 400000,
down_payment_pct=-0.5,
)
assert result is not None
except (ValueError, AssertionError):
pass # Validation rejection is fine
# TYPE: adversarial
# INPUT: empty string city for relocation
# EXPECTED: no crash, returns dict
# CRITERIA: result is a dict
def test_adv_empty_city_relocation():
from relocation_runway import calculate_relocation_runway
try:
result = calculate_relocation_runway(
current_salary=100000,
offer_salary=120000,
current_city="",
destination_city="",
portfolio_value=50000,
)
assert isinstance(result, dict)
except Exception:
pass # Failing gracefully is acceptable
# ============================================================================
# MULTI-STEP TESTS (12 tests)
# ============================================================================
# TYPE: multi_step
# INPUT: add property → get total net worth → verify property appears
# EXPECTED: net worth increases by property equity after adding
# CRITERIA: total_net_worth > portfolio_value_alone
def test_ms_add_then_net_worth():
_clear()
from property_tracker import add_property, get_total_net_worth
_run(add_property(
address="Multi Step Test",
purchase_price=350000,
current_value=420000,
mortgage_balance=280000,
)) # equity = 140000
result = _run(get_total_net_worth(portfolio_value=94000))
assert result["result"]["total_net_worth"] == pytest.approx(234000)
assert result["result"]["real_estate_equity"] == pytest.approx(140000)
# TYPE: multi_step
# INPUT: add property → analyze equity options
# EXPECTED: equity options reference added property, 3 scenarios returned
# CRITERIA: options has 3 entries, each has 10-year projection
def test_ms_add_then_equity_options():
_clear()
from property_tracker import add_property, analyze_equity_options
prop = _run(add_property(
address="Equity Chain Test",
purchase_price=400000,
current_value=520000,
mortgage_balance=370000,
))
result = analyze_equity_options(prop["result"]["property"]["id"])
assert "options" in result
assert len(result["options"]) >= 3
# TYPE: multi_step
# INPUT: add two properties → remove one → verify only one remains
# EXPECTED: after removal, list shows exactly 1 property
# CRITERIA: len(properties) == 1 after removal
def test_ms_add_two_remove_one():
_clear()
from property_tracker import add_property, remove_property, get_properties
p1 = _run(add_property("Home 1", 400000, 450000, 300000))
p2 = _run(add_property("Home 2", 350000, 400000, 250000))
id1 = p1["result"]["property"]["id"]
_run(remove_property(id1))
listed = _run(get_properties())
props = listed["result"]["properties"]
assert len(props) == 1
assert props[0]["id"] == p2["result"]["property"]["id"]
# TYPE: multi_step
# INPUT: strategy simulation → use final net worth in wealth position check
# EXPECTED: chaining results without errors
# CRITERIA: both return valid dicts, wealth check uses strategy output
def test_ms_strategy_then_wealth_check():
from realestate_strategy import simulate_real_estate_strategy
from wealth_visualizer import analyze_wealth_position
strategy = simulate_real_estate_strategy(94000, 120000, 400000, total_years=10)
final_worth = strategy["final_picture"]["total_net_worth"]
wealth = analyze_wealth_position(
portfolio_value=final_worth,
age=44,
annual_income=150000,
)
assert "current_position" in wealth
assert "retirement_projection" in wealth
# TYPE: multi_step
# INPUT: family planning → use reduced savings in wealth position
# EXPECTED: lower savings rate flows into retirement projection
# CRITERIA: both tools return valid dicts
def test_ms_family_then_wealth():
from family_planner import plan_family_finances
from wealth_visualizer import analyze_wealth_position
family = plan_family_finances("Austin", 120000, num_planned_children=1)
assert "income_impact" in family
reduced_savings = max(0, family["income_impact"]["monthly_surplus_after"] * 12)
wealth = analyze_wealth_position(
portfolio_value=94000,
age=32,
annual_income=120000,
annual_savings=reduced_savings,
)
assert "retirement_projection" in wealth
# TYPE: multi_step
# INPUT: job offer check → relocation runway → both use same cities
# EXPECTED: consistent data across both tools for Austin/Seattle
# CRITERIA: both return their respective fields correctly
@pytest.mark.asyncio
async def test_ms_job_offer_then_runway():
from wealth_bridge import calculate_job_offer_affordability
from relocation_runway import calculate_relocation_runway
offer = await calculate_job_offer_affordability(
offer_salary=180000,
offer_city="Seattle",
current_salary=120000,
current_city="Austin",
)
runway = calculate_relocation_runway(
current_salary=120000,
offer_salary=180000,
current_city="Austin",
destination_city="Seattle",
portfolio_value=94000,
)
assert "is_real_raise" in offer
assert "verdict" in runway
# TYPE: multi_step
# INPUT: add property → update value → check net worth reflects update
# EXPECTED: net worth uses updated value not original
# CRITERIA: net worth after update > net worth after initial add
def test_ms_add_update_then_net_worth():
_clear()
from property_tracker import add_property, update_property, get_total_net_worth
prop = _run(add_property("Growing Home", 400000, 450000, 320000))
pid = prop["result"]["property"]["id"]
nw_before = _run(get_total_net_worth(portfolio_value=50000))
_run(update_property(pid, current_value=500000))
nw_after = _run(get_total_net_worth(portfolio_value=50000))
assert nw_after["result"]["total_net_worth"] > nw_before["result"]["total_net_worth"]
# TYPE: multi_step
# INPUT: add property → get equity → use in wealth position
# EXPECTED: wealth position uses real estate equity from property tracker
# CRITERIA: position with RE equity > position without
def test_ms_property_equity_in_wealth_position():
_clear()
from property_tracker import add_property, get_real_estate_equity
from wealth_visualizer import analyze_wealth_position
_run(add_property("Wealth Test", 400000, 500000, 300000)) # equity=200000
equity_result = _run(get_real_estate_equity())
equity = equity_result["result"]["total_real_estate_equity"]
pos_with_re = analyze_wealth_position(94000, 34, 120000, real_estate_equity=equity)
pos_without = analyze_wealth_position(94000, 34, 120000)
assert pos_with_re["current_position"]["total_net_worth"] > pos_without["current_position"]["total_net_worth"]
# TYPE: multi_step
# INPUT: full CRUD cycle — create, read, update, delete
# EXPECTED: each operation succeeds, state consistent throughout
# CRITERIA: all 4 operations return success=True, final list is empty
def test_ms_full_crud_cycle():
_clear()
from property_tracker import add_property, get_properties, update_property, remove_property
# CREATE
prop = _run(add_property("CRUD Home", 400000, 450000, 320000))
assert prop["success"] is True
pid = prop["result"]["property"]["id"]
# READ
listed = _run(get_properties())
ids = [p["id"] for p in listed["result"]["properties"]]
assert pid in ids
# UPDATE
updated = _run(update_property(pid, current_value=480000))
assert updated["success"] is True
assert updated["result"]["property"]["equity"] == pytest.approx(160000)
# DELETE
removed = _run(remove_property(pid))
assert removed["success"] is True
# Verify empty
after = _run(get_properties())
assert after["result"]["properties"] == []
# TYPE: multi_step
# INPUT: multiple properties → wealth position uses combined equity
# EXPECTED: total equity from all properties flows into net worth
# CRITERIA: net worth = portfolio + combined equity
def test_ms_multiple_properties_net_worth():
_clear()
from property_tracker import add_property, get_total_net_worth
_run(add_property("Home A", 400000, 480000, 300000)) # equity=180000
_run(add_property("Home B", 300000, 380000, 260000)) # equity=120000
result = _run(get_total_net_worth(portfolio_value=94000))
expected = 94000 + 180000 + 120000 # = 394000
assert result["result"]["total_net_worth"] == pytest.approx(expected)
# TYPE: multi_step
# INPUT: relocation runway → family plan in destination city
# EXPECTED: can chain city data across both tools
# CRITERIA: both return valid dicts for Seattle
def test_ms_runway_then_family_plan():
from relocation_runway import calculate_relocation_runway
from family_planner import plan_family_finances
runway = calculate_relocation_runway(
current_salary=120000,
offer_salary=180000,
current_city="Austin",
destination_city="Seattle",
portfolio_value=94000,
)
assert "verdict" in runway
family = plan_family_finances("Seattle", 180000, num_planned_children=1)
assert "income_impact" in family
# TYPE: multi_step
# INPUT: strategy simulation over 20 years → verify timeline length
# EXPECTED: timeline has 20+ entries covering each year
# CRITERIA: len(timeline) >= 20
def test_ms_strategy_long_horizon():
from realestate_strategy import simulate_real_estate_strategy
result = simulate_real_estate_strategy(
94000, 120000, 400000,
total_years=20,
buy_interval_years=4,
)
assert result is not None
timeline = result.get("timeline", [])
assert len(timeline) >= 20
assert result["final_picture"]["num_properties_owned"] >= 1