| from backend.database import AirQualityDatabase |
| from backend.app import clean_model_text, render_result_summary |
| from backend.security import create_session_token, verify_session_token |
|
|
|
|
| def test_session_token_round_trip(): |
| token = create_session_token("correct horse battery staple", 60) |
| assert verify_session_token(token, "correct horse battery staple") |
| assert not verify_session_token(token, "wrong password") |
|
|
|
|
| def test_expired_session_token_is_rejected(): |
| token = create_session_token("password", -1) |
| assert not verify_session_token(token, "password") |
|
|
|
|
| def test_sql_guard_accepts_read_only_queries(): |
| sql = AirQualityDatabase.validate_sql( |
| """ |
| WITH city_avg AS ( |
| SELECT city, AVG(pm25) AS avg_pm25 |
| FROM air_quality |
| WHERE pm25 IS NOT NULL |
| GROUP BY city |
| ) |
| SELECT city, ROUND(avg_pm25, 2) AS avg_pm25 |
| FROM city_avg |
| ORDER BY avg_pm25 DESC |
| LIMIT 10 |
| """ |
| ) |
| assert sql.lower().startswith("with") |
|
|
|
|
| def test_sql_guard_rejects_file_access_and_writes(): |
| blocked = [ |
| "SELECT * FROM read_csv_auto('/proc/self/environ')", |
| "COPY air_quality TO '/tmp/export.csv'", |
| "SELECT * FROM air_quality; DROP TABLE air_quality", |
| "PRAGMA database_list", |
| "SELECT * FROM air_quality, information_schema.tables", |
| "SELECT * FROM duckdb_secrets()", |
| "SELECT * FROM air_quality CROSS JOIN states", |
| "SELECT * FROM air_quality, states", |
| ( |
| "WITH RECURSIVE x(n) AS " |
| "(SELECT 1 UNION ALL SELECT n + 1 FROM x) " |
| "SELECT * FROM x, air_quality" |
| ), |
| "SELECT * FROM air_quality, range(1000000000)", |
| ] |
| for sql in blocked: |
| try: |
| AirQualityDatabase.validate_sql(sql) |
| except ValueError: |
| continue |
| raise AssertionError(f"Unsafe SQL was accepted: {sql}") |
|
|
|
|
| def test_sql_guard_rejects_unknown_tables(): |
| try: |
| AirQualityDatabase.validate_sql("SELECT * FROM information_schema.tables") |
| except ValueError: |
| return |
| raise AssertionError("Unknown table was accepted") |
|
|
|
|
| def test_model_text_control_characters_are_removed(): |
| assert clean_model_text("151.51 \bµg/m\b³") == "151.51 µg/m³" |
|
|
|
|
| def test_model_text_latex_units_are_normalized(): |
| text = r"Delhi averaged 104.61 $\mu\text{g/m}^3$ and CO was 1.2 $\text{mg/m}^3$." |
| assert clean_model_text(text) == ( |
| "Delhi averaged 104.61 µg/m³ and CO was 1.2 mg/m³." |
| ) |
|
|
|
|
| def test_model_text_common_plain_unit_variants_are_normalized(): |
| assert clean_model_text("140 Pcg/cm³; 80 μg/m³; 50 ug/m3") == ( |
| "140 µg/m³; 80 µg/m³; 50 µg/m³" |
| ) |
|
|
|
|
| def test_verified_result_values_fill_summary_template(): |
| answer = render_result_summary( |
| ( |
| "{{city}} ranks highest at {{average_pm25}} µg/m³. " |
| "The table contains {{result_count}} qualifying cities." |
| ), |
| ["city", "average_pm25", "observation_days"], |
| [ |
| { |
| "city": "Byrnihat", |
| "average_pm25": 140.51, |
| "observation_days": 1_200, |
| }, |
| { |
| "city": "Delhi", |
| "average_pm25": 104.61, |
| "observation_days": 2_000, |
| }, |
| ], |
| ) |
| assert answer == ( |
| "Byrnihat ranks highest at 140.51 µg/m³. " |
| "The table contains 2 qualifying cities." |
| ) |
|
|
|
|
| def test_unknown_summary_placeholder_uses_safe_fallback(): |
| answer = render_result_summary( |
| "The answer is {{invented_value}}.", |
| ["city", "average_pm25"], |
| [{"city": "Byrnihat", "average_pm25": 140.51}], |
| ) |
| assert "{{invented_value}}" not in answer |
| assert "Byrnihat" in answer |
|
|