/
/
/
1"""Tests for the provider manifest.json validator."""
2
3from scripts.check_manifests import PROVIDERS_PATH, _resolve_manifests, _validate, find_violations
4
5
6def _valid_manifest() -> dict[str, object]:
7 """Return a minimal valid manifest dict for the ``demo`` provider folder."""
8 return {
9 "type": "music",
10 "domain": "demo",
11 "name": "Demo",
12 "description": "A demo provider.",
13 "codeowners": ["@octocat"],
14 }
15
16
17def test_valid_manifest_has_no_issues() -> None:
18 """A complete, consistent manifest produces no issues."""
19 assert _validate(_valid_manifest(), "demo") == []
20
21
22def test_missing_mandatory_keys() -> None:
23 """Each absent mandatory key is reported."""
24 issues = _validate({"type": "music", "domain": "demo"}, "demo")
25 assert any("missing mandatory key 'name'" in issue for issue in issues)
26 assert any("missing mandatory key 'description'" in issue for issue in issues)
27 assert any("missing mandatory key 'codeowners'" in issue for issue in issues)
28
29
30def test_invalid_type() -> None:
31 """An unknown provider type is rejected."""
32 manifest = _valid_manifest()
33 manifest["type"] = "jukebox"
34 assert any("is not one of" in issue for issue in _validate(manifest, "demo"))
35
36
37def test_non_string_type_rejected() -> None:
38 """A non-string type (list/dict) is reported instead of crashing the validator."""
39 for bad_type in (["music"], {"type": "music"}):
40 manifest = _valid_manifest()
41 manifest["type"] = bad_type
42 assert any("is not one of" in issue for issue in _validate(manifest, "demo"))
43
44
45def test_domain_must_match_folder() -> None:
46 """A domain that differs from the folder name is reported."""
47 assert any(
48 "must match the provider folder name" in issue
49 for issue in _validate(_valid_manifest(), "other_folder")
50 )
51
52
53def test_codeowners_must_be_list() -> None:
54 """A bare-string codeowners value (not a list) is rejected."""
55 manifest = _valid_manifest()
56 manifest["codeowners"] = "@octocat"
57 assert any("must be a non-empty list" in issue for issue in _validate(manifest, "demo"))
58
59
60def test_codeowners_entries_need_at_sign() -> None:
61 """Codeowner handles must start with ``@``."""
62 manifest = _valid_manifest()
63 manifest["codeowners"] = ["octocat"]
64 assert any(
65 "must be a string starting with '@'" in issue for issue in _validate(manifest, "demo")
66 )
67
68
69def test_codeowners_null_rejected() -> None:
70 """A present-but-null codeowners value is rejected, not silently accepted."""
71 manifest = _valid_manifest()
72 manifest["codeowners"] = None
73 issues = _validate(manifest, "demo")
74 assert [issue for issue in issues if "must be a non-empty list" in issue]
75 assert not [issue for issue in issues if "missing mandatory key 'codeowners'" in issue]
76
77
78def test_empty_text_fields_rejected() -> None:
79 """Blank name/description values are reported."""
80 manifest = _valid_manifest()
81 manifest["name"] = " "
82 assert any(
83 "'name' must be a non-empty string" in issue for issue in _validate(manifest, "demo")
84 )
85
86
87def test_real_tree_is_clean() -> None:
88 """Every shipped provider manifest passes validation."""
89 assert find_violations() == []
90
91
92def test_resolve_manifests_from_folder() -> None:
93 """A provider folder argument resolves to that provider's manifest.json."""
94 folder = PROVIDERS_PATH / "_demo_music_provider"
95 assert _resolve_manifests([str(folder)]) == [folder / "manifest.json"]
96
97
98def test_resolve_manifests_ignores_non_manifest_paths() -> None:
99 """Arguments that are not manifests (or outside providers) are ignored."""
100 init_file = PROVIDERS_PATH / "_demo_music_provider" / "__init__.py"
101 assert _resolve_manifests([str(init_file)]) == []
102
103
104def test_find_violations_scoped_to_given_manifest() -> None:
105 """Validating a single explicit manifest only inspects that manifest."""
106 manifest = PROVIDERS_PATH / "_demo_music_provider" / "manifest.json"
107 assert find_violations([manifest]) == []
108