Files
wehub-resource-sync 3fbbd7970c
Code Quality / Python Lint & Format (push) Has been cancelled
Code Quality / Python Tests (push) Has been cancelled
Code Quality / JavaScript/TypeScript Lint (advisory) (push) Has been cancelled
Security Scan / CodeQL Analysis (python) (push) Has been cancelled
Security Scan / Dependency Review (push) Has been cancelled
Security Scan / CodeQL Analysis (javascript-typescript) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:43:57 +08:00

116 lines
3.7 KiB
Python

"""Tests for shared.python.input_validation."""
import pytest
from shared.python.input_validation import (
sanitize_prompt_input,
validate_email,
validate_number_input,
validate_text_input,
validate_url,
)
class TestValidateNumberInput:
def test_valid_number(self):
assert validate_number_input("5", min_val=1, max_val=20) == 5
def test_strips_whitespace(self):
assert validate_number_input(" 7 ") == 7
def test_below_minimum_raises(self):
with pytest.raises(ValueError, match="between"):
validate_number_input("0", min_val=1, max_val=20)
def test_above_maximum_raises(self):
with pytest.raises(ValueError, match="between"):
validate_number_input("21", min_val=1, max_val=20)
def test_non_numeric_raises(self):
with pytest.raises(ValueError):
validate_number_input("abc")
class TestValidateTextInput:
def test_valid_text(self):
assert validate_text_input("Hello World", max_length=100) == "Hello World"
def test_trims_result(self):
assert validate_text_input(" padded ") == "padded"
def test_empty_not_allowed_raises(self):
with pytest.raises(ValueError, match="empty"):
validate_text_input(" ")
def test_empty_allowed_returns_empty(self):
assert validate_text_input(" ", allow_empty=True) == ""
def test_too_long_raises(self):
with pytest.raises(ValueError, match="too long"):
validate_text_input("x" * 11, max_length=10)
def test_too_short_raises(self):
with pytest.raises(ValueError, match="too short"):
validate_text_input("ab", min_length=5)
class TestSanitizePromptInput:
def test_plain_text_unchanged(self):
assert sanitize_prompt_input("Hello, world!") == "Hello, world!"
def test_removes_template_injection(self):
result = sanitize_prompt_input("Hello {{system}} world")
assert "{{" not in result
assert "}}" not in result
def test_removes_variable_substitution(self):
result = sanitize_prompt_input("value ${danger} here")
assert "${" not in result
def test_removes_script_tags(self):
result = sanitize_prompt_input("hi <script>alert(1)</script> there")
assert "<script" not in result.lower()
def test_removes_javascript_url(self):
result = sanitize_prompt_input("click javascript:alert(1)")
assert "javascript:" not in result.lower()
def test_empty_returns_empty(self):
assert sanitize_prompt_input("") == ""
def test_too_long_raises(self):
with pytest.raises(ValueError, match="too long"):
sanitize_prompt_input("x" * 11, max_length=10)
def test_only_invalid_characters_raises(self):
with pytest.raises(ValueError, match="invalid characters"):
sanitize_prompt_input("{{a}}")
class TestValidateEmail:
def test_valid_email_lowercased(self):
assert validate_email("User@Example.COM") == "user@example.com"
@pytest.mark.parametrize(
"bad", ["notanemail", "missing@domain", "@nolocal.com", "spaces in@x.com"]
)
def test_invalid_email_raises(self, bad):
with pytest.raises(ValueError):
validate_email(bad)
class TestValidateUrl:
def test_valid_https(self):
assert validate_url("https://example.com/path") == "https://example.com/path"
def test_http_rejected_when_https_required(self):
with pytest.raises(ValueError):
validate_url("http://example.com")
def test_http_allowed_when_not_required(self):
assert validate_url("http://example.com", require_https=False) == "http://example.com"
def test_garbage_raises(self):
with pytest.raises(ValueError):
validate_url("not a url")