# retoor import pytest from devplacepy.services.skills import limits from devplacepy.services.skills.fetcher import ( SkillFetchError, classify_candidate, decode_text, directory_url, extract_candidates, normalize_input_url, origin_of, ) from devplacepy.services.skills.parser import SkillFormatError def test_normalize_adds_scheme_and_strips_fragment(): assert normalize_input_url("example.org/skill#frag") == "https://example.org/skill" assert normalize_input_url(" http://example.org/a/SKILL.md ") == "http://example.org/a/SKILL.md" @pytest.mark.parametrize( "raw", ["", " ", "ftp://example.org/x", "https://user:pw@example.org/x", "x" * (limits.URL_MAX_CHARS + 1)], ) def test_normalize_rejects_bad_urls(raw): with pytest.raises(SkillFetchError): normalize_input_url(raw) @pytest.mark.parametrize( "url,expected", [ ("https://e.org/a/SKILL.md", "https://e.org/a/"), ("https://e.org/a/index.html", "https://e.org/a/"), ("https://e.org/a", "https://e.org/a/"), ("https://e.org/a/", "https://e.org/a/"), ("https://e.org", "https://e.org/"), ], ) def test_directory_url(url, expected): assert directory_url(url) == expected def test_origin_uses_default_ports(): assert origin_of("https://Example.org/x") == ("https", "example.org", 443) assert origin_of("http://example.org:8080/x") == ("http", "example.org", 8080) def test_candidates_come_from_links_and_code_spans_once(): body = ( "See [the script](scripts/run.py) and `references/guide.md`.\n" "Again [dup](scripts/run.py#anchor) and [other](docs/readme.md) and `not a path`." ) assert extract_candidates(body) == ["scripts/run.py", "references/guide.md"] @pytest.mark.parametrize( "candidate,accepted", [ ("scripts/run.py", True), ("./references/guide.md", True), ("assets/templates/SPEC.md", True), ("assets/img/hero.png", False), ("../scripts/run.py", False), ("scripts/../../etc/passwd", False), ("scripts/%2e%2e/x.py", False), ("scripts/a.py?x=1", False), ("/scripts/run.py", False), ("https://evil.org/scripts/run.py", False), ("scripts\\run.py", False), ("other/run.py", False), ("scripts", False), ("scripts/sp ace.py", False), ], ) def test_classify_candidate_only_accepts_safe_relative_text_paths(candidate, accepted): path, reason = classify_candidate(candidate) assert (path is not None) is accepted assert (reason == "") is accepted def test_overlong_path_is_rejected(): path, reason = classify_candidate("scripts/" + "a" * limits.BUNDLED_PATH_MAX_CHARS + ".py") assert path is None and reason def test_decode_text(): assert decode_text("héllo".encode("utf-8")) == "héllo" with pytest.raises(SkillFormatError): decode_text(b"a\x00b") with pytest.raises(SkillFormatError): decode_text(b"\xff\xfe\xfa")