File: test_api.py

package info (click to toggle)
python-validate-pyproject 0.24.1-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 1,340 kB
  • sloc: python: 3,053; makefile: 46; sh: 25
file content (151 lines) | stat: -rw-r--r-- 5,335 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
from collections.abc import Mapping
from functools import partial, wraps

import fastjsonschema as FJS
import pytest

from validate_pyproject import _tomllib as tomllib
from validate_pyproject import api, errors, plugins, types

PYPA_SPECS = "https://packaging.python.org/en/latest/specifications"


def test_load():
    spec = api.load("pyproject_toml")
    assert isinstance(spec, Mapping)

    assert spec["$id"] == f"{PYPA_SPECS}/declaring-build-dependencies/"

    spec = api.load("project_metadata")
    assert spec["$id"] == f"{PYPA_SPECS}/pyproject-toml/"


def test_load_plugin():
    spec = api.load_builtin_plugin("distutils")
    assert spec["$id"].startswith("https://setuptools.pypa.io")
    assert "deprecated/distutils" in spec["$id"]

    spec = api.load_builtin_plugin("setuptools")
    assert spec["$id"].startswith("https://setuptools.pypa.io")
    assert "pyproject" in spec["$id"]


class TestRegistry:
    def test_with_plugins(self):
        plg = plugins.list_from_entry_points()
        registry = api.SchemaRegistry(plg)
        main_schema = registry[registry.main]
        project = main_schema["properties"]["project"]
        assert project["$ref"] == f"{PYPA_SPECS}/pyproject-toml/"
        tool = main_schema["properties"]["tool"]
        assert "setuptools" in tool["properties"]
        assert "$ref" in tool["properties"]["setuptools"]

    def fake_plugin(self, name, schema_version=7, end="#"):
        schema = {
            "$id": f"https://example.com/{name}.schema.json",
            "$schema": f"http://json-schema.org/draft-{schema_version:02d}/schema{end}",
            "type": "object",
        }
        return types.Schema(schema)

    @pytest.mark.parametrize("end", ["", "#"], ids=["no#", "with#"])
    def test_schema_ending(self, end):
        fn = wraps(self.fake_plugin)(partial(self.fake_plugin, end=end))
        plg = plugins.PluginWrapper("plugin", fn)
        registry = api.SchemaRegistry([plg])
        main_schema = registry[registry.main]
        assert main_schema["$schema"] == "http://json-schema.org/draft-07/schema#"

    def test_incompatible_versions(self):
        fn = wraps(self.fake_plugin)(partial(self.fake_plugin, schema_version=8))
        plg = plugins.PluginWrapper("plugin", fn)
        with pytest.raises(errors.InvalidSchemaVersion):
            api.SchemaRegistry([plg])

    def test_duplicated_id_different_tools(self):
        schema = self.fake_plugin("plg")
        fn = wraps(self.fake_plugin)(lambda _: schema)  # Same ID
        plg = [plugins.PluginWrapper(f"plg{i}", fn) for i in range(2)]
        with pytest.raises(errors.SchemaWithDuplicatedId):
            api.SchemaRegistry(plg)

    def test_allow_overwrite_same_tool(self):
        plg = [plugins.PluginWrapper("plg", self.fake_plugin) for _ in range(2)]
        registry = api.SchemaRegistry(plg)
        sid = self.fake_plugin("plg")["$id"]
        assert sid in registry

    def test_missing_id(self):
        def _fake_plugin(name):
            plg = dict(self.fake_plugin(name))
            del plg["$id"]
            return types.Schema(plg)

        plg = plugins.PluginWrapper("plugin", _fake_plugin)
        with pytest.raises(errors.SchemaMissingId):
            api.SchemaRegistry([plg])


class TestValidator:
    example_toml = """\
    [project]
    name = "myproj"
    version = "0"

    [tool.setuptools]
    zip-safe = false
    packages = {find = {}}
    """

    @property
    def valid_example(self):
        return tomllib.loads(self.example_toml)

    @property
    def invalid_example(self):
        example = self.valid_example
        example["tool"]["setuptools"]["zip-safe"] = {"hello": "world"}
        return example

    def test_valid(self):
        validator = api.Validator()
        assert validator(self.valid_example) is not None

    def test_invalid(self):
        validator = api.Validator()
        with pytest.raises(FJS.JsonSchemaValueException):
            validator(self.invalid_example)

    # ---

    def plugin(self, tool):
        plg = plugins.list_from_entry_points(filtering=lambda e: e.name == tool)
        return plg[0]

    TOOLS = ("distutils", "setuptools")

    @pytest.mark.parametrize("tool, other_tool", zip(TOOLS, reversed(TOOLS)))
    def test_plugin_not_enabled(self, tool, other_tool):
        plg = self.plugin(tool)
        validator = api.Validator([plg])
        registry = validator.registry
        main_schema = registry[registry.main]
        assert tool in main_schema["properties"]["tool"]["properties"]
        assert other_tool not in main_schema["properties"]["tool"]["properties"]
        tool_properties = main_schema["properties"]["tool"]["properties"]
        assert tool_properties[tool]["$ref"] == plg.schema["$id"]

    def test_invalid_but_plugin_not_enabled(self):
        # When the plugin is not enabled, the validator should ignore the tool
        validator = api.Validator([self.plugin("distutils")])
        try:
            assert validator(self.invalid_example) is not None
        except Exception:
            registry = validator.registry
            main_schema = registry[registry.main]
            assert "setuptools" not in main_schema["properties"]["tool"]["properties"]
            import json

            assert "setuptools" not in json.dumps(main_schema)
            raise