Spaces:
Sleeping
Sleeping
Download tests/test_environment.py from aadi-joshi/openenvhack: direct link, hf CLI and curl.
- Browser
- Download file 9.86 kB
-
https://huggingface.co/spaces/aadi-joshi/openenvhack/resolve/main/tests/test_environment.py
- Command line
-
hf download hf://spaces/aadi-joshi/openenvhack/tests/test_environment.py
-
curl -L -o test_environment.py https://huggingface.co/spaces/aadi-joshi/openenvhack/resolve/main/tests/test_environment.py
9.86 kB
| """Integration tests for DBEREnvironment -- step/reset/state API.""" | |
| import pytest | |
| from db_er.models import DBERAction, DBERObservation, DBERState | |
| from server.environment import DBEREnvironment | |
| # Helpers | |
| def make_action(action_type: str, **kwargs) -> DBERAction: | |
| return DBERAction(action_type=action_type, **kwargs) | |
| def sql(query: str) -> DBERAction: | |
| return make_action("execute_sql", query=query) | |
| def submit(notes: str = "done") -> DBERAction: | |
| return make_action("submit_resolution", notes=notes) | |
| # reset() | |
| class TestReset: | |
| def test_reset_returns_observation(self): | |
| env = DBEREnvironment() | |
| obs = env.reset(task_id=1) | |
| assert isinstance(obs, DBERObservation) | |
| assert obs.task_id == 1 | |
| assert obs.budget_remaining == 100 | |
| assert obs.step_count == 0 | |
| assert obs.done is False | |
| assert "users" in obs.incident_ticket.lower() or "UNIQUE" in obs.incident_ticket | |
| def test_reset_seed_picks_task(self): | |
| env = DBEREnvironment() | |
| obs = env.reset(seed=0) | |
| assert obs.task_id == 1 | |
| obs = env.reset(seed=1) | |
| assert obs.task_id == 2 | |
| obs = env.reset(seed=2) | |
| assert obs.task_id == 3 | |
| def test_reset_overrides_task_id(self): | |
| env = DBEREnvironment() | |
| obs = env.reset(seed=0, task_id=3) | |
| assert obs.task_id == 3 | |
| def test_reset_starts_with_violations(self): | |
| env = DBEREnvironment() | |
| obs1 = env.reset(task_id=1) | |
| assert obs1.violation_count > 0 # 3 duplicate rows | |
| def test_reset_gives_clean_state(self): | |
| env = DBEREnvironment() | |
| obs = env.reset(task_id=1) | |
| # Take a step | |
| env.step(sql("SELECT * FROM users")) | |
| # Reset again - must return to clean state | |
| obs2 = env.reset(task_id=1) | |
| assert obs2.step_count == 0 | |
| assert obs2.budget_remaining == 100 | |
| def test_double_reset_isolated(self): | |
| env = DBEREnvironment() | |
| env.reset(task_id=1) | |
| # Delete all users (should not persist after reset) | |
| env.step(sql("DELETE FROM users WHERE id >= 9")) | |
| obs2 = env.reset(task_id=1) | |
| assert obs2.violation_count == 3 # duplicates back | |
| def test_invalid_task_id_raises(self): | |
| env = DBEREnvironment() | |
| with pytest.raises(ValueError): | |
| env.reset(task_id=99) | |
| # step() - budget mechanics | |
| class TestBudget: | |
| def test_select_costs_1(self): | |
| env = DBEREnvironment() | |
| env.reset(task_id=1) | |
| obs = env.step(sql("SELECT * FROM users")) | |
| assert obs.budget_remaining == 99 | |
| def test_mutation_costs_5(self): | |
| env = DBEREnvironment() | |
| env.reset(task_id=1) | |
| obs = env.step(sql("DELETE FROM users WHERE id = 9")) | |
| assert obs.budget_remaining == 95 | |
| def test_pragma_costs_1(self): | |
| env = DBEREnvironment() | |
| env.reset(task_id=1) | |
| obs = env.step(sql("PRAGMA table_info(users)")) | |
| assert obs.budget_remaining == 99 | |
| def test_step_count_increments(self): | |
| env = DBEREnvironment() | |
| env.reset(task_id=1) | |
| for i in range(3): | |
| obs = env.step(sql("SELECT 1")) | |
| assert obs.step_count == 3 | |
| def test_budget_exhaustion_ends_episode(self): | |
| env = DBEREnvironment() | |
| env.reset(task_id=1) | |
| # Use up budget with mutations | |
| for _ in range(20): | |
| obs = env.step(sql("SELECT 1")) # 1 point each | |
| # Keep draining | |
| for _ in range(80): | |
| obs = env.step(sql("SELECT 1")) | |
| # At 0 budget, done should be True | |
| assert obs.done is True or obs.budget_remaining <= 0 | |
| # step() - SQL execution | |
| class TestSqlExecution: | |
| def test_select_returns_rows(self): | |
| env = DBEREnvironment() | |
| env.reset(task_id=1) | |
| obs = env.step(sql("SELECT id, email FROM users ORDER BY id")) | |
| assert isinstance(obs.last_query_result, list) | |
| assert len(obs.last_query_result) == 11 # 8 + 3 duplicates | |
| def test_mutation_returns_rows_affected(self): | |
| env = DBEREnvironment() | |
| env.reset(task_id=1) | |
| obs = env.step(sql("DELETE FROM users WHERE id = 9")) | |
| assert obs.last_action_type == "MUTATION" | |
| def test_bad_sql_returns_error(self): | |
| env = DBEREnvironment() | |
| env.reset(task_id=1) | |
| obs = env.step(sql("SELECT * FROM nonexistent_table_xyz")) | |
| assert obs.last_action_type == "ERROR" | |
| assert "error" in obs.error_logs.lower() or "no such table" in obs.error_logs.lower() | |
| def test_zero_row_delete_penalised(self): | |
| env = DBEREnvironment() | |
| env.reset(task_id=1) | |
| # Delete a row that doesn't exist | |
| obs = env.step(sql("DELETE FROM users WHERE id = 9999")) | |
| assert obs.reward is not None and obs.reward < 0 | |
| def test_spam_penalty_on_repeated_select(self): | |
| env = DBEREnvironment() | |
| env.reset(task_id=1) | |
| for _ in range(3): | |
| obs = env.step(sql("SELECT COUNT(*) FROM users")) | |
| # Third execution should incur spam penalty | |
| assert obs.reward is not None and obs.reward <= 0 | |
| # step() - intermediate rewards | |
| class TestIntermediateRewards: | |
| def test_violation_reduction_gives_positive_reward(self): | |
| env = DBEREnvironment() | |
| env.reset(task_id=1) | |
| # Delete 1 duplicate -> violation_count 3->2 -> +0.2 reward | |
| obs = env.step(sql("DELETE FROM users WHERE id = 9")) | |
| assert obs.reward is not None and obs.reward >= 0.2 | |
| def test_multiple_violation_reductions(self): | |
| env = DBEREnvironment() | |
| env.reset(task_id=1) | |
| # Delete all 3 duplicates at once -> violation_count 3->0 -> +0.6 | |
| obs = env.step(sql(""" | |
| DELETE FROM users | |
| WHERE id NOT IN (SELECT MIN(id) FROM users GROUP BY email) | |
| """)) | |
| assert obs.reward is not None and obs.reward >= 0.6 | |
| assert obs.violation_count == 0 | |
| # step() - safety violations | |
| class TestSafetyViolations: | |
| def test_drop_core_table_ends_episode(self): | |
| env = DBEREnvironment() | |
| env.reset(task_id=1) | |
| obs = env.step(sql("DROP TABLE users")) | |
| assert obs.done is True | |
| assert obs.reward == -1.0 | |
| def test_drop_non_core_table_is_ok(self): | |
| env = DBEREnvironment() | |
| env.reset(task_id=2) | |
| # Create and drop a temp table | |
| env.step(sql("CREATE TABLE temp_work (id INTEGER PRIMARY KEY)")) | |
| obs = env.step(sql("DROP TABLE temp_work")) | |
| assert obs.done is False | |
| assert obs.reward != -1.0 | |
| def test_bare_delete_ends_episode(self): | |
| env = DBEREnvironment() | |
| env.reset(task_id=1) | |
| obs = env.step(sql("DELETE FROM users")) | |
| assert obs.done is True | |
| assert obs.reward == -1.0 | |
| def test_catastrophic_row_loss_ends_episode(self): | |
| env = DBEREnvironment() | |
| env.reset(task_id=1) | |
| # Golden DB has 8 users. Safety floor = 90% of 8 = 7.2. | |
| # Delete 9 of 11 rows -> 2 remain, which is < 7.2 - catastrophic. | |
| obs = env.step(sql("DELETE FROM users WHERE id > 2")) | |
| assert obs.done is True | |
| assert obs.reward == -1.0 | |
| def test_safe_delete_with_where_does_not_end_episode(self): | |
| env = DBEREnvironment() | |
| env.reset(task_id=1) | |
| # Delete the 3 duplicate rows (11->8). Golden floor = 8 rows. | |
| # 8 >= 8*0.9 = 7.2 -> safe, no catastrophic loss. | |
| obs = env.step(sql("DELETE FROM users WHERE id IN (9, 10, 11)")) | |
| assert obs.done is False | |
| # submit_resolution() | |
| class TestSubmitResolution: | |
| def test_submit_without_fix_gives_low_score(self): | |
| env = DBEREnvironment() | |
| env.reset(task_id=1) | |
| obs = env.step(submit("Nothing fixed.")) | |
| assert obs.done is True | |
| # Grader score for unrepaired DB should be < 1.0 | |
| # Final reward = score - 0.5 penalty (if score < 0.5) | |
| assert obs.reward < 1.0 | |
| def test_submit_after_fix_gives_high_score(self): | |
| env = DBEREnvironment() | |
| env.reset(task_id=1) | |
| env.step(sql(""" | |
| DELETE FROM users | |
| WHERE id NOT IN (SELECT MIN(id) FROM users GROUP BY email) | |
| """)) | |
| obs = env.step(submit("Deleted duplicate rows.")) | |
| assert obs.done is True | |
| assert obs.reward is not None and obs.reward >= 0.9 | |
| def test_submit_ends_episode(self): | |
| env = DBEREnvironment() | |
| env.reset(task_id=1) | |
| obs = env.step(submit("done")) | |
| assert obs.done is True | |
| # Further steps should raise | |
| with pytest.raises(RuntimeError): | |
| env.step(sql("SELECT 1")) | |
| # state property | |
| class TestState: | |
| def test_state_reflects_current_episode(self): | |
| env = DBEREnvironment() | |
| env.reset(task_id=2) | |
| env.step(sql("SELECT * FROM vendors")) | |
| state = env.state | |
| assert isinstance(state, DBERState) | |
| assert state.task_id == 2 | |
| assert state.budget_remaining == 99 | |
| assert state.step_count == 1 | |
| def test_state_tracks_violations(self): | |
| env = DBEREnvironment() | |
| env.reset(task_id=1) | |
| state_before = env.state | |
| import json | |
| env.step(sql("DELETE FROM users WHERE id = 9")) | |
| state_after = env.state | |
| assert state_after.violation_count < state_before.violation_count | |
| # close() | |
| class TestClose: | |
| def test_close_is_idempotent(self): | |
| env = DBEREnvironment() | |
| env.reset(task_id=1) | |
| env.close() | |
| env.close() # should not raise | |
| def test_step_after_close_still_works(self): | |
| # close() is a no-op (class-level shared state for HTTP compatibility), | |
| # so step() after close() should succeed, not raise. | |
| env = DBEREnvironment() | |
| env.reset(task_id=1) | |
| env.close() | |
| obs = env.step(sql("SELECT 1")) | |
| assert obs is not None | |