7a0da7932b
OSV-Scanner (Scheduled) / scan-scheduled (push) Failing after 0s
Create Release / test-gate (push) Has been cancelled
Create Release / release-gate (push) Has been cancelled
Create Release / ci-gate (push) Has been cancelled
Create Release / version-check (push) Has been cancelled
Create Release / e2e-test-gate (push) Has been cancelled
Create Release / responsive-test-gate (push) Has been cancelled
Create Release / compat-test-gate (push) Has been cancelled
Create Release / compose-integration-gate (push) Has been cancelled
Create Release / vulture-gate (push) Has been cancelled
Create Release / build (push) Has been cancelled
Create Release / provenance (push) Has been cancelled
Create Release / prerelease-docker (push) Has been cancelled
Create Release / publish-docker (push) Has been cancelled
Create Release / create-release (push) Has been cancelled
Create Release / cleanup-changelog (push) Has been cancelled
Create Release / trigger-pypi (push) Has been cancelled
Create Release / monitor-pypi (push) Has been cancelled
Create Release / Clean up orphan prerelease tags and signatures (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-form] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-metrics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-workflow] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-core] (push) Has been cancelled
CodeQL Advanced / Analyze (javascript-typescript) (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [history-news] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [library] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [link-analytics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-core] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-lifecycle] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [error-benchmark] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) (push) Has been cancelled
Docker Tests (Consolidated) / Accessibility Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Unit Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Example Tests (push) Has been cancelled
Docker Tests (Consolidated) / Production Image Smoke Test (push) Has been cancelled
Docker Tests (Consolidated) / Infrastructure Tests (push) Has been cancelled
OSSF Scorecard / OSSF Security Scorecard Analysis (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [mobile] (push) Has been cancelled
Backwards Compatibility / Verify Encryption Constants (push) Has been cancelled
Backwards Compatibility / PyPI Version Compatibility (push) Has been cancelled
Backwards Compatibility / Database Migration Tests (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Docker Tests (Consolidated) / detect-changes (push) Has been cancelled
Docker Tests (Consolidated) / Build Test Image (push) Has been cancelled
Docker Tests (Consolidated) / All Pytest Tests + Coverage (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [accessibility] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [api-crud] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-login] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-register] (push) Has been cancelled
279 lines
9.1 KiB
Python
279 lines
9.1 KiB
Python
"""
|
|
Deep behavioral tests for safe_error_message and flask_api helpers.
|
|
Tests exception type mapping, context formatting, and field mapping patterns.
|
|
"""
|
|
|
|
from local_deep_research.news.flask_api import safe_error_message
|
|
|
|
|
|
# --- Exception type mapping ---
|
|
|
|
|
|
class TestSafeErrorMessageTypes:
|
|
"""Tests for exception type → message mapping."""
|
|
|
|
def test_value_error_returns_invalid_input(self):
|
|
result = safe_error_message(ValueError("bad value"), "test")
|
|
assert result == "Invalid input provided"
|
|
|
|
def test_key_error_returns_required_data_missing(self):
|
|
result = safe_error_message(KeyError("missing_key"), "test")
|
|
assert result == "Required data missing"
|
|
|
|
def test_type_error_returns_invalid_data_format(self):
|
|
result = safe_error_message(TypeError("wrong type"), "test")
|
|
assert result == "Invalid data format"
|
|
|
|
def test_runtime_error_returns_generic(self):
|
|
result = safe_error_message(RuntimeError("oops"), "test")
|
|
assert "An error occurred" in result
|
|
|
|
def test_exception_returns_generic(self):
|
|
result = safe_error_message(Exception("generic"), "test")
|
|
assert "An error occurred" in result
|
|
|
|
def test_os_error_returns_generic(self):
|
|
result = safe_error_message(OSError("disk full"), "test")
|
|
assert "An error occurred" in result
|
|
|
|
def test_attribute_error_returns_generic(self):
|
|
result = safe_error_message(AttributeError("no attr"), "test")
|
|
assert "An error occurred" in result
|
|
|
|
def test_index_error_returns_generic(self):
|
|
result = safe_error_message(IndexError("out of range"), "test")
|
|
assert "An error occurred" in result
|
|
|
|
|
|
# --- Context formatting ---
|
|
|
|
|
|
class TestSafeErrorMessageContext:
|
|
"""Tests for context string formatting."""
|
|
|
|
def test_context_included_in_generic_message(self):
|
|
result = safe_error_message(Exception("err"), "getting news feed")
|
|
assert "getting news feed" in result
|
|
|
|
def test_context_with_while(self):
|
|
result = safe_error_message(Exception("err"), "loading data")
|
|
assert "while loading data" in result
|
|
|
|
def test_empty_context(self):
|
|
result = safe_error_message(Exception("err"), "")
|
|
assert "An error occurred" in result
|
|
assert "while" not in result
|
|
|
|
def test_no_context(self):
|
|
result = safe_error_message(Exception("err"))
|
|
assert "An error occurred" in result
|
|
|
|
def test_context_not_in_value_error(self):
|
|
"""ValueError messages should not include context."""
|
|
result = safe_error_message(ValueError("err"), "loading data")
|
|
assert result == "Invalid input provided"
|
|
|
|
def test_context_not_in_key_error(self):
|
|
"""KeyError messages should not include context."""
|
|
result = safe_error_message(KeyError("err"), "loading data")
|
|
assert result == "Required data missing"
|
|
|
|
def test_context_not_in_type_error(self):
|
|
"""TypeError messages should not include context."""
|
|
result = safe_error_message(TypeError("err"), "loading data")
|
|
assert result == "Invalid data format"
|
|
|
|
|
|
# --- Security: no internal details leaked ---
|
|
|
|
|
|
class TestSafeErrorMessageSecurity:
|
|
"""Tests that internal error details are not exposed."""
|
|
|
|
def test_value_error_hides_message(self):
|
|
result = safe_error_message(ValueError("secret_db_password"))
|
|
assert "secret_db_password" not in result
|
|
|
|
def test_key_error_hides_message(self):
|
|
result = safe_error_message(KeyError("internal_column_name"))
|
|
assert "internal_column_name" not in result
|
|
|
|
def test_type_error_hides_message(self):
|
|
result = safe_error_message(TypeError("NoneType has no attribute foo"))
|
|
assert "NoneType" not in result
|
|
|
|
def test_generic_error_hides_stack_trace(self):
|
|
result = safe_error_message(
|
|
RuntimeError("Traceback: at line 42 in secret.py")
|
|
)
|
|
assert "Traceback" not in result
|
|
assert "secret.py" not in result
|
|
|
|
def test_generic_error_hides_exception_message(self):
|
|
result = safe_error_message(
|
|
RuntimeError("Connection refused at 192.168.1.1:5432")
|
|
)
|
|
assert "192.168.1.1" not in result
|
|
assert "5432" not in result
|
|
|
|
|
|
# --- Flask API field mapping ---
|
|
|
|
|
|
class TestFlaskApiFieldMapping:
|
|
"""Tests for the field mapping dictionary used in update_subscription."""
|
|
|
|
def test_field_mapping_keys(self):
|
|
"""Verify the expected request fields are mapped."""
|
|
# This tests the pattern used at flask_api.py:433-446
|
|
field_mapping = {
|
|
"query": "query_or_topic",
|
|
"name": "name",
|
|
"refresh_minutes": "refresh_interval_minutes",
|
|
"is_active": "is_active",
|
|
"folder_id": "folder_id",
|
|
"model_provider": "model_provider",
|
|
"model": "model",
|
|
"search_strategy": "search_strategy",
|
|
"custom_endpoint": "custom_endpoint",
|
|
"search_engine": "search_engine",
|
|
"search_iterations": "search_iterations",
|
|
"questions_per_iteration": "questions_per_iteration",
|
|
}
|
|
assert "query" in field_mapping
|
|
assert field_mapping["query"] == "query_or_topic"
|
|
|
|
def test_field_mapping_refresh_minutes(self):
|
|
field_mapping = {
|
|
"refresh_minutes": "refresh_interval_minutes",
|
|
}
|
|
data = {"refresh_minutes": 30}
|
|
update_data = {}
|
|
for req_field, storage_field in field_mapping.items():
|
|
if req_field in data:
|
|
update_data[storage_field] = data[req_field]
|
|
assert update_data["refresh_interval_minutes"] == 30
|
|
|
|
def test_field_mapping_only_present_keys(self):
|
|
field_mapping = {
|
|
"query": "query_or_topic",
|
|
"name": "name",
|
|
"refresh_minutes": "refresh_interval_minutes",
|
|
}
|
|
data = {"name": "My Sub"}
|
|
update_data = {}
|
|
for req_field, storage_field in field_mapping.items():
|
|
if req_field in data:
|
|
update_data[storage_field] = data[req_field]
|
|
assert "name" in update_data
|
|
assert "query_or_topic" not in update_data
|
|
assert "refresh_interval_minutes" not in update_data
|
|
|
|
def test_field_mapping_all_fields(self):
|
|
field_mapping = {
|
|
"query": "query_or_topic",
|
|
"name": "name",
|
|
"refresh_minutes": "refresh_interval_minutes",
|
|
"is_active": "is_active",
|
|
}
|
|
data = {
|
|
"query": "AI news",
|
|
"name": "My AI Feed",
|
|
"refresh_minutes": 60,
|
|
"is_active": True,
|
|
}
|
|
update_data = {}
|
|
for req_field, storage_field in field_mapping.items():
|
|
if req_field in data:
|
|
update_data[storage_field] = data[req_field]
|
|
assert update_data["query_or_topic"] == "AI news"
|
|
assert update_data["name"] == "My AI Feed"
|
|
assert update_data["refresh_interval_minutes"] == 60
|
|
assert update_data["is_active"] is True
|
|
|
|
|
|
# --- Subscription ID validation pattern ---
|
|
|
|
|
|
class TestSubscriptionIdValidation:
|
|
"""Tests for the subscription ID validation pattern from flask_api."""
|
|
|
|
def test_null_string_is_invalid(self):
|
|
subscription_id = "null"
|
|
assert (
|
|
subscription_id == "null"
|
|
or subscription_id == "undefined"
|
|
or not subscription_id
|
|
)
|
|
|
|
def test_undefined_string_is_invalid(self):
|
|
subscription_id = "undefined"
|
|
is_invalid = (
|
|
subscription_id == "null"
|
|
or subscription_id == "undefined"
|
|
or not subscription_id
|
|
)
|
|
assert is_invalid
|
|
|
|
def test_empty_string_is_invalid(self):
|
|
subscription_id = ""
|
|
is_invalid = (
|
|
subscription_id == "null"
|
|
or subscription_id == "undefined"
|
|
or not subscription_id
|
|
)
|
|
assert is_invalid
|
|
|
|
def test_valid_uuid_is_valid(self):
|
|
subscription_id = "abc-123-def-456"
|
|
is_invalid = (
|
|
subscription_id == "null"
|
|
or subscription_id == "undefined"
|
|
or not subscription_id
|
|
)
|
|
assert not is_invalid
|
|
|
|
def test_numeric_id_is_valid(self):
|
|
subscription_id = "12345"
|
|
is_invalid = (
|
|
subscription_id == "null"
|
|
or subscription_id == "undefined"
|
|
or not subscription_id
|
|
)
|
|
assert not is_invalid
|
|
|
|
|
|
# --- Vote validation pattern ---
|
|
|
|
|
|
class TestVoteValidation:
|
|
"""Tests for the vote validation pattern."""
|
|
|
|
def test_up_is_valid(self):
|
|
vote = "up"
|
|
assert vote in ["up", "down"]
|
|
|
|
def test_down_is_valid(self):
|
|
vote = "down"
|
|
assert vote in ["up", "down"]
|
|
|
|
def test_sideways_is_invalid(self):
|
|
vote = "sideways"
|
|
assert vote not in ["up", "down"]
|
|
|
|
def test_empty_is_invalid(self):
|
|
vote = ""
|
|
assert vote not in ["up", "down"]
|
|
|
|
def test_none_is_invalid(self):
|
|
vote = None
|
|
assert vote not in ["up", "down"]
|
|
|
|
def test_uppercase_up_is_invalid(self):
|
|
vote = "Up"
|
|
assert vote not in ["up", "down"]
|
|
|
|
def test_numeric_is_invalid(self):
|
|
vote = 1
|
|
assert vote not in ["up", "down"]
|