Files

387 lines
13 KiB
Python

"""
Tests for django-nepkit views (views.py).
Covers district_list_view, municipality_list_view, and private helpers.
"""
import pytest
from django.test import RequestFactory
@pytest.fixture
def rf():
return RequestFactory()
class TestRenderOptions:
"""Tests for _render_options internal helper."""
def test_renders_placeholder_option(self):
from django_nepkit.views import _render_options
response = _render_options([], "Select District")
html = response.content.decode()
assert 'value=""' in html
assert "Select District" in html
def test_renders_data_options(self):
from django_nepkit.views import _render_options
data = [{"id": "Kathmandu", "text": "Kathmandu"}]
response = _render_options(data, "Select")
html = response.content.decode()
assert 'value="Kathmandu"' in html
assert ">Kathmandu<" in html
def test_renders_multiple_options(self):
from django_nepkit.views import _render_options
data = [
{"id": "Kathmandu", "text": "Kathmandu"},
{"id": "Lalitpur", "text": "Lalitpur"},
]
response = _render_options(data, "Select")
html = response.content.decode()
assert "Kathmandu" in html
assert "Lalitpur" in html
def test_escapes_xss_in_id(self):
"""District names with HTML should be escaped, not injected verbatim."""
from django_nepkit.views import _render_options
data = [
{
"id": '"></option><script>alert(1)</script>',
"text": "Bad",
}
]
response = _render_options(data, "Select")
html = response.content.decode()
assert "<script>alert(1)</script>" not in html
assert "&lt;script&gt;" in html
def test_escapes_xss_in_text(self):
from django_nepkit.views import _render_options
data = [{"id": "X", "text": "<img src=x onerror=alert(1)>"}]
response = _render_options(data, "Select")
html = response.content.decode()
assert "<img" not in html
assert "&lt;img" in html
def test_escapes_placeholder(self):
from django_nepkit.views import _render_options
response = _render_options([], '"><script>x</script>')
html = response.content.decode()
assert "<script>x</script>" not in html
assert "&lt;script&gt;" in html
def test_missing_keys_use_empty_string(self):
"""Items missing ``id`` / ``text`` render an empty option, not a crash."""
from django_nepkit.views import _render_options
data = [{"id": "KTM"}, {"text": "Only Text"}, {}]
response = _render_options(data, "Select")
html = response.content.decode()
assert 'value="KTM"' in html
assert ">Only Text<" in html
# The empty item should still produce a (value-less) option.
assert html.count("<option") >= 3
def test_non_dict_item_does_not_crash(self):
"""Malformed rows (e.g. a stray string) are silently skipped."""
from django_nepkit.views import _render_options
data = ["not-a-dict", {"id": "KTM", "text": "KTM"}, None, 42]
response = _render_options(data, "Select")
html = response.content.decode()
assert 'value="KTM"' in html
def test_escapes_quote_breakout_in_id_attribute(self):
"""A ``"><img`` payload must not close the ``value="..."`` attribute."""
from django_nepkit.views import _render_options
data = [{"id": '"><img src=x onerror=alert(1)>', "text": "x"}]
response = _render_options(data, "Select")
html = response.content.decode()
assert 'value=""' not in html or "&quot;" in html
assert "<img" not in html
assert "&lt;img" in html
class TestGetPrimaryParam:
"""Tests for _get_primary_param internal helper."""
def test_returns_named_param(self, rf):
from django_nepkit.views import _get_primary_param
request = rf.get("/", {"province": "Bagmati Province"})
value = _get_primary_param(request, "province")
assert value == "Bagmati Province"
def test_falls_back_to_first_non_internal_param(self, rf):
from django_nepkit.views import _get_primary_param
# No "province" key, but has "field" key (not an internal param)
request = rf.get("/", {"field": "Bagmati Province"})
value = _get_primary_param(request, "province")
assert value == "Bagmati Province"
def test_no_fallback_when_multiple_non_internal_params(self, rf):
"""If the URL has multiple non-internal params, the named one wins and
the fallback is skipped to avoid the wrong-key foot-gun."""
from django_nepkit.views import _get_primary_param
request = rf.get(
"/",
{"province": "Bagmati Province", "field": "Koshi Province"},
)
value = _get_primary_param(request, "province")
assert value == "Bagmati Province"
def test_internal_params_excluded_from_fallback(self, rf):
from django_nepkit.views import _get_primary_param
# Only internal params (ne, en, html) present — should return None
request = rf.get("/", {"ne": "true", "en": "true", "html": "true"})
value = _get_primary_param(request, "province")
assert value is None
def test_returns_none_when_no_params(self, rf):
from django_nepkit.views import _get_primary_param
request = rf.get("/")
value = _get_primary_param(request, "province")
assert value is None
class TestParseLanguageParams:
"""Tests for _parse_language_params internal helper."""
def test_defaults_to_english(self, rf):
from django_nepkit.views import _parse_language_params
request = rf.get("/")
ne, en = _parse_language_params(request)
assert ne is False
assert en is True
def test_ne_true_from_query(self, rf):
from django_nepkit.views import _parse_language_params
request = rf.get("/", {"ne": "true"})
ne, en = _parse_language_params(request)
assert ne is True
assert en is False
def test_en_true_from_query(self, rf):
from django_nepkit.views import _parse_language_params
request = rf.get("/", {"en": "true"})
ne, en = _parse_language_params(request)
assert en is True
def test_ne_false_from_query(self, rf):
from django_nepkit.views import _parse_language_params
request = rf.get("/", {"ne": "false"})
ne, en = _parse_language_params(request)
assert ne is False
assert en is True
def test_unknown_value_falls_back_to_default(self, rf):
"""Unrecognised values fall back to the project default (non-strict)."""
from django_nepkit.views import _parse_language_params
request = rf.get("/", {"ne": "banana"})
ne, en = _parse_language_params(request)
# Default in test settings is "en", so ne should be False.
assert ne is False
assert en is True
def test_strict_raises_on_garbage(self, rf):
from django_nepkit.views import _parse_language_params
request = rf.get("/", {"ne": "banana"})
with pytest.raises(ValueError, match="Invalid value"):
_parse_language_params(request, strict=True)
def test_strict_accepts_all_canonical_forms(self, rf):
from django_nepkit.views import _parse_language_params
for value, expected in [
("true", True),
("True", True),
("1", True),
("yes", True),
("on", True),
("false", False),
("FALSE", False),
("0", False),
("no", False),
("off", False),
]:
request = rf.get("/", {"ne": value})
ne, _ = _parse_language_params(request, strict=True)
assert ne is expected, f"failed for {value!r}"
class TestShouldReturnHtml:
"""Tests for _should_return_html internal helper."""
def test_html_param_true(self, rf):
from django_nepkit.views import _should_return_html
request = rf.get("/", {"html": "true"})
assert _should_return_html(request) is True
def test_html_param_false(self, rf):
from django_nepkit.views import _should_return_html
request = rf.get("/", {"html": "false"})
assert _should_return_html(request) is False
def test_htmx_request_header(self, rf):
from django_nepkit.views import _should_return_html
request = rf.get("/", HTTP_HX_REQUEST="true")
assert _should_return_html(request) is True
def test_no_html_indicator(self, rf):
from django_nepkit.views import _should_return_html
request = rf.get("/")
assert _should_return_html(request) is False
class TestDistrictListView:
"""Tests for district_list_view."""
def test_returns_json_for_valid_province(self, rf):
from django_nepkit.views import district_list_view
request = rf.get("/", {"province": "Bagmati Province"})
response = district_list_view(request)
assert response.status_code == 200
import json
data = json.loads(response.content)
assert isinstance(data, list)
assert len(data) > 0
# Each item has id and text
assert "id" in data[0]
assert "text" in data[0]
def test_returns_empty_json_when_no_province(self, rf):
from django_nepkit.views import district_list_view
request = rf.get("/")
response = district_list_view(request)
assert response.status_code == 200
import json
data = json.loads(response.content)
assert data == []
def test_returns_html_when_html_true(self, rf):
from django_nepkit.views import district_list_view
request = rf.get("/", {"province": "Bagmati Province", "html": "true"})
response = district_list_view(request)
assert response.status_code == 200
assert "text/html" in response["Content-Type"]
assert b"<option" in response.content
def test_returns_nepali_names_when_ne_true(self, rf):
from django_nepkit.views import district_list_view
import json
request = rf.get("/", {"province": "Bagmati Province", "ne": "true"})
response = district_list_view(request)
data = json.loads(response.content)
assert len(data) > 0
# Nepali names should contain Devanagari characters
import re
has_devanagari = any(
re.search(r"[\u0900-\u097F]", item["text"]) for item in data
)
assert has_devanagari
def test_returns_html_with_nepali_placeholder(self, rf):
from django_nepkit.views import district_list_view
request = rf.get(
"/", {"province": "Bagmati Province", "html": "true", "ne": "true"}
)
response = district_list_view(request)
# Placeholder should contain Devanagari text (जिल्ला छान्नुहोस्)
decoded = response.content.decode("utf-8")
assert "जिल्ला" in decoded
class TestMunicipalityListView:
"""Tests for municipality_list_view."""
def test_returns_json_for_valid_district(self, rf):
from django_nepkit.views import municipality_list_view
import json
request = rf.get("/", {"district": "Kathmandu"})
response = municipality_list_view(request)
assert response.status_code == 200
data = json.loads(response.content)
assert isinstance(data, list)
assert len(data) > 0
def test_returns_empty_json_when_no_district(self, rf):
from django_nepkit.views import municipality_list_view
import json
request = rf.get("/")
response = municipality_list_view(request)
assert response.status_code == 200
data = json.loads(response.content)
assert data == []
def test_returns_html_when_html_true(self, rf):
from django_nepkit.views import municipality_list_view
request = rf.get("/", {"district": "Kathmandu", "html": "true"})
response = municipality_list_view(request)
assert response.status_code == 200
assert "text/html" in response["Content-Type"]
assert b"<option" in response.content
def test_returns_nepali_names_when_ne_true(self, rf):
from django_nepkit.views import municipality_list_view
import json
import re
request = rf.get("/", {"district": "Kathmandu", "ne": "true"})
response = municipality_list_view(request)
data = json.loads(response.content)
assert len(data) > 0
has_devanagari = any(
re.search(r"[\u0900-\u097F]", item["text"]) for item in data
)
assert has_devanagari
def test_htmx_request_returns_html(self, rf):
from django_nepkit.views import municipality_list_view
request = rf.get("/", {"district": "Kathmandu"}, HTTP_HX_REQUEST="true")
response = municipality_list_view(request)
assert "text/html" in response["Content-Type"]
assert b"<option" in response.content