From c1ff9c0afe5d6bfeb38b7904d15309cbcdba467a Mon Sep 17 00:00:00 2001 From: Ayush7614 Date: Sat, 4 Jul 2026 05:31:38 +0530 Subject: [PATCH 1/2] test: add skills catalog loading and validation tests Cover validate_requested_skills, load_skills, internal category exclusion, and regression checks for merged community skills. --- tests/test_skills.py | 87 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 tests/test_skills.py diff --git a/tests/test_skills.py b/tests/test_skills.py new file mode 100644 index 000000000..0802c13b8 --- /dev/null +++ b/tests/test_skills.py @@ -0,0 +1,87 @@ +"""Tests for strix.skills catalog loading and validation.""" + +from __future__ import annotations + +import pytest + +from strix.skills import ( + _INTERNAL_SKILL_CATEGORIES, + get_all_skill_names, + get_available_skills, + load_skills, + validate_requested_skills, +) +from strix.utils.resource_paths import get_strix_resource_path + + +def test_internal_categories_excluded_from_user_skill_names() -> None: + names = get_all_skill_names() + grouped = get_available_skills() + skills_dir = get_strix_resource_path("skills") + + for category in _INTERNAL_SKILL_CATEGORIES: + assert category not in grouped + for skill_file in (skills_dir / category).glob("*.md"): + assert skill_file.stem not in names + + +def test_merged_security_skills_are_registered() -> None: + """Regression guard for community skills shipped on main.""" + expected = { + "oauth", + "aws", + "django", + "prototype_pollution", + "insecure_deserialization", + } + names = get_all_skill_names() + missing = expected - names + assert not missing, f"Expected skills missing from catalog: {sorted(missing)}" + + +@pytest.mark.parametrize( + ("skill_list", "expected_substring"), + [ + ([], None), + (["idor", "xss"], None), + (["a", "b", "c", "d", "e", "f"], "more than 5"), + (["not_a_real_skill"], "Invalid skill name"), + (["idor", "fake_skill"], "Invalid skill name"), + ], +) +def test_validate_requested_skills( + skill_list: list[str], + expected_substring: str | None, +) -> None: + result = validate_requested_skills(skill_list) + if expected_substring is None: + assert result is None + else: + assert result is not None + assert expected_substring in result + + +def test_load_skills_strips_frontmatter_and_resolves_bare_name() -> None: + content = load_skills(["idor"]) + assert "idor" in content + body = content["idor"] + assert not body.startswith("---") + assert "# IDOR" in body + assert "name: idor" not in body + + +def test_load_skills_accepts_category_prefixed_name() -> None: + content = load_skills(["vulnerabilities/idor"]) + assert "idor" in content + assert "# IDOR" in content["idor"] + + +def test_load_skills_skips_missing_skill() -> None: + assert load_skills(["definitely_missing_skill_xyz"]) == {} + + +def test_every_user_selectable_skill_loads_nonempty() -> None: + for name in sorted(get_all_skill_names()): + content = load_skills([name]) + assert name in content, f"{name}: load_skills did not return content" + assert len(content[name].strip()) > 100, f"{name}: loaded body too short" From 03d72f0a963fe71051e939e2ef51348fc67bbed8 Mon Sep 17 00:00:00 2001 From: Ayush7614 Date: Sat, 4 Jul 2026 05:37:44 +0530 Subject: [PATCH 2/2] Address Greptile review: relax skill body length check Assert non-empty loaded content instead of a 100-char minimum. --- tests/test_skills.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_skills.py b/tests/test_skills.py index 0802c13b8..ff78cef8d 100644 --- a/tests/test_skills.py +++ b/tests/test_skills.py @@ -84,4 +84,4 @@ def test_every_user_selectable_skill_loads_nonempty() -> None: for name in sorted(get_all_skill_names()): content = load_skills([name]) assert name in content, f"{name}: load_skills did not return content" - assert len(content[name].strip()) > 100, f"{name}: loaded body too short" + assert content[name].strip(), f"{name}: loaded body is empty"