Skip to content

Commit 3a3725b

Browse files
author
vijay
committed
Strengthen SEP-2640 validation test coverage
Parametrize the digest-format rejection test over near-miss cases (uppercase, wrong length, missing/wrong prefix), and add explicit JSON round-trip tests for both shapes of the resources union type (a static array and the "dynamic" marker) to prove neither collapses or mistags on the wire.
1 parent 0729d95 commit 3a3725b

1 file changed

Lines changed: 38 additions & 2 deletions

File tree

tests/shared/test_skills.py

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,29 @@ def test_skill_name_from_uri_rejects_a_uri_with_no_recoverable_name() -> None:
6969
skill_name_from_uri("skill:///SKILL.md")
7070

7171

72+
def test_skill_with_a_static_resources_array_round_trips_through_json() -> None:
73+
"""SEP-2640 Resources: `resources` MUST serialize as a JSON array of `{uri, digest, size}`
74+
triples - proves the union type doesn't collapse or mistag on the wire."""
75+
original = _skill()
76+
dumped = original.model_dump(mode="json", by_alias=True)
77+
assert isinstance(dumped["resources"], list)
78+
restored = Skill.model_validate(dumped)
79+
assert restored == original
80+
81+
82+
def test_skill_with_dynamic_resources_round_trips_through_json_as_the_literal_string() -> None:
83+
"""SEP-2640 Resources: a dynamically generated skill MUST carry the literal string
84+
`"dynamic"` in place of an array - not `null`, not `{}`, not omitted."""
85+
original = Skill(
86+
uri="skill://generated/SKILL.md", frontmatter={"name": "generated", "description": "d"}, resources="dynamic"
87+
)
88+
dumped = original.model_dump(mode="json", by_alias=True)
89+
assert dumped["resources"] == "dynamic"
90+
restored = Skill.model_validate(dumped)
91+
assert restored == original
92+
assert restored.resources == "dynamic"
93+
94+
7295
def test_validate_skill_accepts_a_conformant_skill() -> None:
7396
validate_skill(_skill())
7497

@@ -170,9 +193,22 @@ def test_validate_skill_rejects_duplicate_resource_uris() -> None:
170193
validate_skill(skill)
171194

172195

173-
def test_validate_skill_rejects_an_invalid_digest_format() -> None:
196+
@pytest.mark.parametrize(
197+
"digest",
198+
[
199+
"not-a-digest", # no sha256: prefix at all
200+
"sha256:" + "A" * 64, # uppercase hex - spec requires lowercase
201+
"sha256:" + "a" * 63, # one hex char short
202+
"sha256:" + "a" * 65, # one hex char long
203+
"sha1:" + "a" * 40, # wrong algorithm prefix
204+
"sha256:" + "g" * 64, # non-hex characters
205+
],
206+
)
207+
def test_validate_skill_rejects_malformed_digest_formats(digest: str) -> None:
208+
"""SEP-2640 Integrity and verification: `sha256:{hex}` where `{hex}` is exactly 64
209+
lowercase hexadecimal characters - each of these near-misses must still be rejected."""
174210
root = "skill://git-workflow/SKILL.md"
175-
bad = SkillResource(uri=root, digest="not-a-digest", size=1)
211+
bad = SkillResource(uri=root, digest=digest, size=1)
176212
skill = Skill(uri=root, frontmatter={"name": "git-workflow", "description": "d"}, resources=[bad])
177213
with pytest.raises(ValueError, match="digest"):
178214
validate_skill(skill)

0 commit comments

Comments
 (0)