store / tests /test_store_environment.py
Code-Father's picture
Store
b7a5bab
Raw History Blame Contribute Delete
5.44 kB
import random
import pytest
from store import OrderRequest, PriceUpdate, StoreAction, StoreEnv
from store.models import dump_model
from store.server.store_environment import StoreEnvironment
def test_reset_returns_full_plan_like_observation() -> None:
env = StoreEnvironment()
observation = env.reset()
assert observation.store.day == 1
assert len(observation.stock) == 30
assert len(observation.market) == 100
assert len(observation.marketing_actions) == 15
assert len(observation.seller_profiles) == 4
assert observation.pending_orders == []
assert observation.available_action.pass_action == StoreAction()
assert observation.available_action.example_action.action_ids == [7]
def test_pass_step_advances_three_days_but_reward_uses_six_day_forecast() -> None:
env = StoreEnvironment()
env.reset()
observation = env.step(StoreAction())
assert observation.store.day == 4
assert observation.metadata["reward_breakdown"]["commit_window_days"] == 3
assert observation.metadata["reward_breakdown"]["reward_horizon_days"] == 6
def test_eta_sampling_is_deterministic_for_a_seed() -> None:
env_a = StoreEnvironment()
env_b = StoreEnvironment()
env_a.reset()
env_b.reset()
env_a._rng = random.Random(7)
env_b._rng = random.Random(7)
assert env_a._sample_actual_eta("Seller C", 5) == env_b._sample_actual_eta("Seller C", 5)
def test_commit_state_only_keeps_three_days_of_progress() -> None:
env = StoreEnvironment()
env.reset()
env._sample_actual_eta = lambda seller, quoted: 4 # type: ignore[method-assign]
observation = env.step(
StoreAction(
orders=[OrderRequest(product="Rice", seller="Seller B", quantity=10)],
)
)
assert observation.store.day == 4
assert len(observation.pending_orders) == 1
assert observation.pending_orders[0].days_remaining == 1
def test_price_update_changes_customer_satisfaction() -> None:
env = StoreEnvironment()
reset_observation = env.reset()
milk_row = next(row for row in reset_observation.stock if row.product_name == "Milk")
observation = env.step(
StoreAction(
prices=[
PriceUpdate(
product="Milk",
price=max(1, int(milk_row.selling_price_inr) - 5),
)
]
)
)
assert observation.store.customer_satisfaction > reset_observation.store.customer_satisfaction
def test_marketing_actions_are_durable_and_cannot_be_reactivated() -> None:
env = StoreEnvironment()
env.reset()
observation = env.step(StoreAction(action_ids=[7]))
assert observation.store.active_action_ids == [7]
assert observation.store.active_recurring_cost_inr > 0
assert any(
action.action_id == 7 and action.is_active for action in observation.marketing_actions
)
with pytest.raises(ValueError):
env.step(StoreAction(action_ids=[7]))
def test_recurring_costs_are_deducted_each_simulated_day() -> None:
env = StoreEnvironment()
env.reset()
runtime = env._build_initial_runtime()
runtime["store"]["bank_balance_inr"] = 10_000.0
runtime["store"]["cumulative_profit_inr"] = 0.0
runtime["store"]["current_daily_expense_inr"] = 0.0
runtime["store"]["customer_satisfaction"] = 0.0
runtime["active_action_ids"] = {7}
metrics = env._simulate_days(runtime, 3)
assert metrics["recurring_costs_paid_inr"] == 1_800.0
assert runtime["store"]["bank_balance_inr"] == 8_200.0
assert runtime["store"]["cumulative_profit_inr"] == -1_800.0
def test_large_capital_loss_reduces_reward_and_ends_episode() -> None:
baseline = StoreEnvironment()
baseline.reset()
baseline_reward = baseline.step(StoreAction()).reward
env = StoreEnvironment()
env.reset()
observation = env.step(StoreAction(action_ids=[11]))
assert observation.reward < baseline_reward
assert observation.done is True
assert observation.metadata["done_reason"] == "bank_balance_below_zero"
def test_episode_ends_after_tenth_decision_window() -> None:
env = StoreEnvironment()
env.reset()
observation = None
for _ in range(9):
observation = env.step(StoreAction())
assert observation.done is False
observation = env.step(StoreAction())
assert observation is not None
assert observation.done is True
assert observation.store.day == 31
assert observation.metadata["done_reason"] == "episode_horizon_reached"
def test_client_payload_and_result_parsing_support_nested_models() -> None:
client = StoreEnv(base_url="http://localhost:8000")
action = StoreAction(
orders=[OrderRequest(product="Rice", seller="Seller A", quantity=5)],
prices=[PriceUpdate(product="Milk", price=33)],
action_ids=[7],
)
payload = client._step_payload(action)
assert payload == {
"orders": [{"product": "Rice", "seller": "Seller A", "quantity": 5}],
"prices": [{"product": "Milk", "price": 33}],
"action_ids": [7],
}
env = StoreEnvironment()
observation = env.reset()
result = client._parse_result(
{
"observation": dump_model(observation, exclude_none=True),
"reward": 0.0,
"done": False,
}
)
assert result.observation.store.day == 1
assert len(result.observation.market) == 100