unitysvc-core 0.2.32__tar.gz → 0.2.34__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {unitysvc_core-0.2.32/src/unitysvc_core.egg-info → unitysvc_core-0.2.34}/PKG-INFO +14 -1
- {unitysvc_core-0.2.32 → unitysvc_core-0.2.34}/README.md +13 -0
- {unitysvc_core-0.2.32 → unitysvc_core-0.2.34}/pyproject.toml +1 -1
- {unitysvc_core-0.2.32 → unitysvc_core-0.2.34}/src/unitysvc_core/models/base.py +5 -0
- unitysvc_core-0.2.34/src/unitysvc_core/parameter_usage.py +89 -0
- {unitysvc_core-0.2.32 → unitysvc_core-0.2.34}/src/unitysvc_core/validator.py +22 -0
- {unitysvc_core-0.2.32 → unitysvc_core-0.2.34/src/unitysvc_core.egg-info}/PKG-INFO +14 -1
- {unitysvc_core-0.2.32 → unitysvc_core-0.2.34}/src/unitysvc_core.egg-info/SOURCES.txt +2 -0
- unitysvc_core-0.2.34/tests/test_parameter_usage.py +121 -0
- {unitysvc_core-0.2.32 → unitysvc_core-0.2.34}/tests/test_validators.py +8 -0
- {unitysvc_core-0.2.32 → unitysvc_core-0.2.34}/LICENSE +0 -0
- {unitysvc_core-0.2.32 → unitysvc_core-0.2.34}/setup.cfg +0 -0
- {unitysvc_core-0.2.32 → unitysvc_core-0.2.34}/src/unitysvc_core/__init__.py +0 -0
- {unitysvc_core-0.2.32 → unitysvc_core-0.2.34}/src/unitysvc_core/models/__init__.py +0 -0
- {unitysvc_core-0.2.32 → unitysvc_core-0.2.34}/src/unitysvc_core/models/documents.py +0 -0
- {unitysvc_core-0.2.32 → unitysvc_core-0.2.34}/src/unitysvc_core/models/listing_data.py +0 -0
- {unitysvc_core-0.2.32 → unitysvc_core-0.2.34}/src/unitysvc_core/models/listing_v1.py +0 -0
- {unitysvc_core-0.2.32 → unitysvc_core-0.2.34}/src/unitysvc_core/models/offering_data.py +0 -0
- {unitysvc_core-0.2.32 → unitysvc_core-0.2.34}/src/unitysvc_core/models/offering_v1.py +0 -0
- {unitysvc_core-0.2.32 → unitysvc_core-0.2.34}/src/unitysvc_core/models/pricing.py +0 -0
- {unitysvc_core-0.2.32 → unitysvc_core-0.2.34}/src/unitysvc_core/models/promotion_data.py +0 -0
- {unitysvc_core-0.2.32 → unitysvc_core-0.2.34}/src/unitysvc_core/models/provider_data.py +0 -0
- {unitysvc_core-0.2.32 → unitysvc_core-0.2.34}/src/unitysvc_core/models/provider_v1.py +0 -0
- {unitysvc_core-0.2.32 → unitysvc_core-0.2.34}/src/unitysvc_core/models/service.py +0 -0
- {unitysvc_core-0.2.32 → unitysvc_core-0.2.34}/src/unitysvc_core/models/service_group_data.py +0 -0
- {unitysvc_core-0.2.32 → unitysvc_core-0.2.34}/src/unitysvc_core/models/validators.py +0 -0
- {unitysvc_core-0.2.32 → unitysvc_core-0.2.34}/src/unitysvc_core/py.typed +0 -0
- {unitysvc_core-0.2.32 → unitysvc_core-0.2.34}/src/unitysvc_core/schema/base.json +0 -0
- {unitysvc_core-0.2.32 → unitysvc_core-0.2.34}/src/unitysvc_core/schema/listing_v1.json +0 -0
- {unitysvc_core-0.2.32 → unitysvc_core-0.2.34}/src/unitysvc_core/schema/offering_v1.json +0 -0
- {unitysvc_core-0.2.32 → unitysvc_core-0.2.34}/src/unitysvc_core/schema/promotion_v1.json +0 -0
- {unitysvc_core-0.2.32 → unitysvc_core-0.2.34}/src/unitysvc_core/schema/provider_v1.json +0 -0
- {unitysvc_core-0.2.32 → unitysvc_core-0.2.34}/src/unitysvc_core/schema/service_group_v1.json +0 -0
- {unitysvc_core-0.2.32 → unitysvc_core-0.2.34}/src/unitysvc_core/utils.py +0 -0
- {unitysvc_core-0.2.32 → unitysvc_core-0.2.34}/src/unitysvc_core.egg-info/dependency_links.txt +0 -0
- {unitysvc_core-0.2.32 → unitysvc_core-0.2.34}/src/unitysvc_core.egg-info/requires.txt +0 -0
- {unitysvc_core-0.2.32 → unitysvc_core-0.2.34}/src/unitysvc_core.egg-info/top_level.txt +0 -0
- {unitysvc_core-0.2.32 → unitysvc_core-0.2.34}/tests/test_access_interface_routing.py +0 -0
- {unitysvc_core-0.2.32 → unitysvc_core-0.2.34}/tests/test_effective_price.py +0 -0
- {unitysvc_core-0.2.32 → unitysvc_core-0.2.34}/tests/test_hidden_path_skip.py +0 -0
- {unitysvc_core-0.2.32 → unitysvc_core-0.2.34}/tests/test_offering_data.py +0 -0
- {unitysvc_core-0.2.32 → unitysvc_core-0.2.34}/tests/test_pricing.py +0 -0
- {unitysvc_core-0.2.32 → unitysvc_core-0.2.34}/tests/test_provider_account_rate_limits.py +0 -0
- {unitysvc_core-0.2.32 → unitysvc_core-0.2.34}/tests/test_service_group.py +0 -0
- {unitysvc_core-0.2.32 → unitysvc_core-0.2.34}/tests/test_service_status.py +0 -0
- {unitysvc_core-0.2.32 → unitysvc_core-0.2.34}/tests/test_utils.py +0 -0
- {unitysvc_core-0.2.32 → unitysvc_core-0.2.34}/tests/test_validator.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: unitysvc-core
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.34
|
|
4
4
|
Summary: Shared data models and validation helpers for UnitySVC packages
|
|
5
5
|
Author-email: Bo Peng <bo.peng@unitysvc.com>
|
|
6
6
|
Maintainer-email: Bo Peng <bo.peng@unitysvc.com>
|
|
@@ -132,3 +132,16 @@ moved to `unitysvc-sellers`.
|
|
|
132
132
|
## License
|
|
133
133
|
|
|
134
134
|
MIT
|
|
135
|
+
|
|
136
|
+
### Required enrollment parameter usage
|
|
137
|
+
|
|
138
|
+
The shared validator rejects required user parameters that no channel, interface,
|
|
139
|
+
or document consumes. File validation checks a listing against its paired offering
|
|
140
|
+
when present; backend ingest calls the same `validate_required_parameter_usage`
|
|
141
|
+
helper with the complete listing and offering. Schema declarations alone are not
|
|
142
|
+
consumers. Jinja attribute access, string-key lookups, and `params.get("name")`
|
|
143
|
+
are inspected; dynamic access and whole-map iteration conservatively consume all
|
|
144
|
+
parameters. Deprecated content can still be retired.
|
|
145
|
+
|
|
146
|
+
This validates usage, not channel attribution: a document reference counts as a
|
|
147
|
+
consumer without proving that the intended paid channel takes enrollment.
|
|
@@ -97,3 +97,16 @@ moved to `unitysvc-sellers`.
|
|
|
97
97
|
## License
|
|
98
98
|
|
|
99
99
|
MIT
|
|
100
|
+
|
|
101
|
+
### Required enrollment parameter usage
|
|
102
|
+
|
|
103
|
+
The shared validator rejects required user parameters that no channel, interface,
|
|
104
|
+
or document consumes. File validation checks a listing against its paired offering
|
|
105
|
+
when present; backend ingest calls the same `validate_required_parameter_usage`
|
|
106
|
+
helper with the complete listing and offering. Schema declarations alone are not
|
|
107
|
+
consumers. Jinja attribute access, string-key lookups, and `params.get("name")`
|
|
108
|
+
are inspected; dynamic access and whole-map iteration conservatively consume all
|
|
109
|
+
parameters. Deprecated content can still be retired.
|
|
110
|
+
|
|
111
|
+
This validates usage, not channel attribution: a document reference counts as a
|
|
112
|
+
consumer without proving that the intended paid channel takes enrollment.
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "unitysvc-core"
|
|
7
|
-
version = "0.2.
|
|
7
|
+
version = "0.2.34"
|
|
8
8
|
description = "Shared data models and validation helpers for UnitySVC packages"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
authors = [{ name = "Bo Peng", email = "bo.peng@unitysvc.com" }]
|
|
@@ -281,6 +281,11 @@ class RequestTransformEnum(StrEnum):
|
|
|
281
281
|
# the gateway auto-detects the incoming format and translates in-process only
|
|
282
282
|
# when needed (#923/#929/#1400). Replaces the earlier ``llm_translate`` key.
|
|
283
283
|
llm_translator = "llm_translator"
|
|
284
|
+
# Normalize a `msg` envelope (title, body, links, attachment, format) for
|
|
285
|
+
# the destination channel's declared capabilities, optionally mapping the
|
|
286
|
+
# result into the upstream body. Runs before body_transformer, which can
|
|
287
|
+
# template the normalized msg (unitysvc/unitysvc#2650).
|
|
288
|
+
msg_transformer = "msg_transformer"
|
|
284
289
|
|
|
285
290
|
|
|
286
291
|
class ServiceTypeEnum(StrEnum):
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"""Shared cross-file validation of required enrollment parameter usage.
|
|
2
|
+
|
|
3
|
+
This checks that required parameters have consumers. It does not prove that
|
|
4
|
+
an intended enrollment channel consumes them; interface/document use counts.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from jinja2 import Environment, TemplateSyntaxError, nodes
|
|
10
|
+
|
|
11
|
+
_ENV = Environment()
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def referenced_param_names(value: Any) -> set[str] | None:
|
|
15
|
+
"""Names read from ``params`` in Jinja, or None for dynamic/whole-map use.
|
|
16
|
+
|
|
17
|
+
Literal strings/comments are not references. Dictionary iteration and
|
|
18
|
+
dynamic lookups conservatively count as consuming all parameters.
|
|
19
|
+
"""
|
|
20
|
+
names: set[str] = set()
|
|
21
|
+
if isinstance(value, str):
|
|
22
|
+
if "{{" not in value and "{%" not in value:
|
|
23
|
+
return names
|
|
24
|
+
tree = _ENV.parse(value)
|
|
25
|
+
parents = {id(child): parent for parent in tree.find_all(nodes.Node) for child in parent.iter_child_nodes()}
|
|
26
|
+
for node in tree.find_all(nodes.Name):
|
|
27
|
+
if node.name != "params" or node.ctx != "load":
|
|
28
|
+
continue
|
|
29
|
+
parent = parents.get(id(node))
|
|
30
|
+
if isinstance(parent, nodes.Getitem) and isinstance(parent.arg, nodes.Const):
|
|
31
|
+
if isinstance(parent.arg.value, str):
|
|
32
|
+
names.add(parent.arg.value)
|
|
33
|
+
continue
|
|
34
|
+
if isinstance(parent, nodes.Getattr):
|
|
35
|
+
call = parents.get(id(parent))
|
|
36
|
+
if isinstance(call, nodes.Call) and call.node is parent:
|
|
37
|
+
if parent.attr == "get" and call.args and isinstance(call.args[0], nodes.Const):
|
|
38
|
+
if isinstance(call.args[0].value, str):
|
|
39
|
+
names.add(call.args[0].value)
|
|
40
|
+
continue
|
|
41
|
+
return None
|
|
42
|
+
names.add(parent.attr)
|
|
43
|
+
continue
|
|
44
|
+
return None
|
|
45
|
+
return names
|
|
46
|
+
items = value.values() if isinstance(value, dict) else value if isinstance(value, (list, tuple)) else ()
|
|
47
|
+
for item in items:
|
|
48
|
+
found = referenced_param_names(item)
|
|
49
|
+
if found is None:
|
|
50
|
+
return None
|
|
51
|
+
names |= found
|
|
52
|
+
return names
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def unused_required_params(parameters_schema: dict[str, Any] | None, *sources: Any) -> list[str]:
|
|
56
|
+
"""Required names that no source consumes (schema declarations are not sources)."""
|
|
57
|
+
required = (parameters_schema or {}).get("required") or []
|
|
58
|
+
if not required:
|
|
59
|
+
return []
|
|
60
|
+
used: set[str] = set()
|
|
61
|
+
for source in sources:
|
|
62
|
+
names = referenced_param_names(source)
|
|
63
|
+
if names is None:
|
|
64
|
+
return []
|
|
65
|
+
used |= names
|
|
66
|
+
return [name for name in required if name not in used]
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def validate_required_parameter_usage(listing: dict[str, Any], offering: dict[str, Any]) -> list[str]:
|
|
70
|
+
"""Return hard validation errors, shared by seller validation and backend ingest.
|
|
71
|
+
|
|
72
|
+
Deprecated content can still be retired. Referencing a parameter in a
|
|
73
|
+
document satisfies usage without guaranteeing correct channel attribution.
|
|
74
|
+
"""
|
|
75
|
+
if listing.get("status") == "deprecated" or offering.get("status") == "deprecated":
|
|
76
|
+
return []
|
|
77
|
+
schema = listing.get("user_parameters_schema") or listing.get("parameters_schema")
|
|
78
|
+
content = {k: v for k, v in listing.items() if k not in ("user_parameters_schema", "parameters_schema")}
|
|
79
|
+
try:
|
|
80
|
+
unused = unused_required_params(schema, content, offering)
|
|
81
|
+
except TemplateSyntaxError as exc:
|
|
82
|
+
return [f"Cannot validate required parameter usage: invalid Jinja template: {exc}"]
|
|
83
|
+
if not unused:
|
|
84
|
+
return []
|
|
85
|
+
return [
|
|
86
|
+
f"Required user parameter(s) {', '.join(unused)} are used by no channel, interface or document. "
|
|
87
|
+
"Reference each one as {{ params.NAME }} (for example ${ customer_secrets.{{ params.NAME }} }), "
|
|
88
|
+
"or drop it from the required list."
|
|
89
|
+
]
|
|
@@ -10,6 +10,7 @@ from urllib.parse import urlparse
|
|
|
10
10
|
from jinja2 import Environment, StrictUndefined, TemplateSyntaxError, UndefinedError
|
|
11
11
|
from jsonschema.validators import Draft7Validator
|
|
12
12
|
|
|
13
|
+
from .parameter_usage import validate_required_parameter_usage
|
|
13
14
|
from .utils import SCHEMA_TO_STEM, is_hidden_path, load_data_file, schema_for_path
|
|
14
15
|
|
|
15
16
|
# ``details.model``: a provider-neutral model name (``glm-5.2``, not
|
|
@@ -1004,10 +1005,31 @@ class DataValidator:
|
|
|
1004
1005
|
errors.extend(validate_listing_smtp_base_urls(uai))
|
|
1005
1006
|
errors.extend(validate_listing_mcp_base_urls(uai))
|
|
1006
1007
|
errors.extend(validate_listing_jinja_var_references(data))
|
|
1008
|
+
errors.extend(self.validate_required_parameter_usage(data, file_path))
|
|
1007
1009
|
errors.extend(self.validate_listing_name_namespace(data, file_path))
|
|
1008
1010
|
|
|
1009
1011
|
return len(errors) == 0, errors
|
|
1010
1012
|
|
|
1013
|
+
def validate_required_parameter_usage(self, listing: dict[str, Any], file_path: Path) -> list[str]:
|
|
1014
|
+
"""Check the listing against its paired offering, after overrides/presets.
|
|
1015
|
+
|
|
1016
|
+
Standalone listings without an offering cannot establish cross-file
|
|
1017
|
+
usage; full service uploads are always checked by backend ingest too.
|
|
1018
|
+
"""
|
|
1019
|
+
schema = listing.get("user_parameters_schema") or listing.get("parameters_schema")
|
|
1020
|
+
if not isinstance(schema, dict) or not schema.get("required"):
|
|
1021
|
+
return []
|
|
1022
|
+
for candidate in sorted(file_path.parent.glob("offering*.*")):
|
|
1023
|
+
if candidate.suffix not in (".json", ".toml") or ".override." in candidate.name:
|
|
1024
|
+
continue
|
|
1025
|
+
try:
|
|
1026
|
+
offering, _ = load_data_file(candidate)
|
|
1027
|
+
except Exception as exc:
|
|
1028
|
+
return [f"Cannot check required parameter usage: {exc}"]
|
|
1029
|
+
if isinstance(offering, dict) and (offering.get("schema") == "offering_v1" or candidate.stem == "offering"):
|
|
1030
|
+
return validate_required_parameter_usage(listing, offering)
|
|
1031
|
+
return []
|
|
1032
|
+
|
|
1011
1033
|
def _resolve_provider_name(self, file_path: Path) -> str | None:
|
|
1012
1034
|
"""Find the provider name (``provider_v1.name``) governing a listing file.
|
|
1013
1035
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: unitysvc-core
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.34
|
|
4
4
|
Summary: Shared data models and validation helpers for UnitySVC packages
|
|
5
5
|
Author-email: Bo Peng <bo.peng@unitysvc.com>
|
|
6
6
|
Maintainer-email: Bo Peng <bo.peng@unitysvc.com>
|
|
@@ -132,3 +132,16 @@ moved to `unitysvc-sellers`.
|
|
|
132
132
|
## License
|
|
133
133
|
|
|
134
134
|
MIT
|
|
135
|
+
|
|
136
|
+
### Required enrollment parameter usage
|
|
137
|
+
|
|
138
|
+
The shared validator rejects required user parameters that no channel, interface,
|
|
139
|
+
or document consumes. File validation checks a listing against its paired offering
|
|
140
|
+
when present; backend ingest calls the same `validate_required_parameter_usage`
|
|
141
|
+
helper with the complete listing and offering. Schema declarations alone are not
|
|
142
|
+
consumers. Jinja attribute access, string-key lookups, and `params.get("name")`
|
|
143
|
+
are inspected; dynamic access and whole-map iteration conservatively consume all
|
|
144
|
+
parameters. Deprecated content can still be retired.
|
|
145
|
+
|
|
146
|
+
This validates usage, not channel attribution: a document reference counts as a
|
|
147
|
+
consumer without proving that the intended paid channel takes enrollment.
|
|
@@ -2,6 +2,7 @@ LICENSE
|
|
|
2
2
|
README.md
|
|
3
3
|
pyproject.toml
|
|
4
4
|
src/unitysvc_core/__init__.py
|
|
5
|
+
src/unitysvc_core/parameter_usage.py
|
|
5
6
|
src/unitysvc_core/py.typed
|
|
6
7
|
src/unitysvc_core/utils.py
|
|
7
8
|
src/unitysvc_core/validator.py
|
|
@@ -34,6 +35,7 @@ tests/test_access_interface_routing.py
|
|
|
34
35
|
tests/test_effective_price.py
|
|
35
36
|
tests/test_hidden_path_skip.py
|
|
36
37
|
tests/test_offering_data.py
|
|
38
|
+
tests/test_parameter_usage.py
|
|
37
39
|
tests/test_pricing.py
|
|
38
40
|
tests/test_provider_account_rate_limits.py
|
|
39
41
|
tests/test_service_group.py
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
"""Required enrollment parameters must have consumers across service data."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
import pytest
|
|
7
|
+
|
|
8
|
+
from unitysvc_core.parameter_usage import referenced_param_names, validate_required_parameter_usage
|
|
9
|
+
from unitysvc_core.validator import DataValidator
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@pytest.mark.parametrize("expression", ["params.name", "params['name']", 'params.get("name")'])
|
|
13
|
+
def test_named_reads(expression):
|
|
14
|
+
assert referenced_param_names("{{ " + expression + " }}") == {"name"}
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@pytest.mark.parametrize(
|
|
18
|
+
"expression",
|
|
19
|
+
[
|
|
20
|
+
"params",
|
|
21
|
+
"params | tojson",
|
|
22
|
+
"params.items()",
|
|
23
|
+
"params.keys()",
|
|
24
|
+
"params.values()",
|
|
25
|
+
"params.get(key)",
|
|
26
|
+
"params[key]",
|
|
27
|
+
],
|
|
28
|
+
)
|
|
29
|
+
def test_dynamic_and_whole_map_reads(expression):
|
|
30
|
+
assert referenced_param_names("{{ " + expression + " }}") is None
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def test_strings_comments_and_non_template_text_are_not_consumers():
|
|
34
|
+
assert referenced_param_names(["params.name", "{# params.name #}", '{{ "params.name" }}']) == set()
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def listing(**changes):
|
|
38
|
+
return {"status": "ready", "user_parameters_schema": {"required": ["host", "token"]}, **changes}
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def test_required_names_in_the_schema_do_not_count_as_use():
|
|
42
|
+
errors = validate_required_parameter_usage(
|
|
43
|
+
listing(), {"upstream_access_config": {"free": {"api_key": "${ customer_secrets.TOKEN }"}}}
|
|
44
|
+
)
|
|
45
|
+
assert len(errors) == 1
|
|
46
|
+
assert "host, token are used by no channel" in errors[0]
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def test_partial_use_is_rejected():
|
|
50
|
+
errors = validate_required_parameter_usage(listing(), {"host": "{{ params.host }}"})
|
|
51
|
+
assert "token are used by no channel" in errors[0]
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def test_channel_interface_and_document_reads_count_without_proving_attribution():
|
|
55
|
+
data = listing(documents={"example": {"body": "{{ params.token }}"}})
|
|
56
|
+
assert validate_required_parameter_usage(data, {"host": "{{ params.host }}"}) == []
|
|
57
|
+
assert (
|
|
58
|
+
validate_required_parameter_usage(
|
|
59
|
+
listing(user_access_interfaces={"url": "{{ params.host }}"}, documents={"body": "{{ params.token }}"}), {}
|
|
60
|
+
)
|
|
61
|
+
== []
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def test_required_parameter_can_have_punctuation_in_its_name():
|
|
66
|
+
assert (
|
|
67
|
+
validate_required_parameter_usage(
|
|
68
|
+
listing(user_parameters_schema={"required": ["host-name"]}), {"host": "{{ params['host-name'] }}"}
|
|
69
|
+
)
|
|
70
|
+
== []
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def test_deprecated_content_can_be_retired():
|
|
75
|
+
assert validate_required_parameter_usage(listing(status="deprecated"), {}) == []
|
|
76
|
+
assert validate_required_parameter_usage(listing(), {"status": "deprecated"}) == []
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def test_no_required_parameters():
|
|
80
|
+
assert validate_required_parameter_usage({}, {}) == []
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
@pytest.mark.parametrize("suffix", ["json", "toml"])
|
|
84
|
+
def test_file_validator_checks_paired_offering(tmp_path, suffix):
|
|
85
|
+
schema_dir = Path(__file__).parents[1] / "src/unitysvc_core/schema"
|
|
86
|
+
validator = DataValidator(tmp_path, schema_dir)
|
|
87
|
+
data = listing()
|
|
88
|
+
(tmp_path / "listing.json").write_text(json.dumps(data))
|
|
89
|
+
path = tmp_path / f"offering.{suffix}"
|
|
90
|
+
path.write_text("{}" if suffix == "json" else "")
|
|
91
|
+
errors = validator.validate_required_parameter_usage(data, tmp_path / "listing.json")
|
|
92
|
+
assert "host, token" in errors[0]
|
|
93
|
+
path.write_text(
|
|
94
|
+
json.dumps({"host": "{{ params.host }}", "token": "{{ params.token }}"})
|
|
95
|
+
if suffix == "json"
|
|
96
|
+
else 'host = "{{ params.host }}"\ntoken = "{{ params.token }}"\n'
|
|
97
|
+
)
|
|
98
|
+
assert validator.validate_required_parameter_usage(data, tmp_path / "listing.json") == []
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def test_validation_pipeline_invokes_the_shared_rule(tmp_path, monkeypatch):
|
|
102
|
+
schema_dir = Path(__file__).parents[1] / "src/unitysvc_core/schema"
|
|
103
|
+
validator = DataValidator(tmp_path, schema_dir)
|
|
104
|
+
path = tmp_path / "listing.json"
|
|
105
|
+
path.write_text(json.dumps({"name": "labs/test"}))
|
|
106
|
+
calls = []
|
|
107
|
+
|
|
108
|
+
def check(data, file_path):
|
|
109
|
+
calls.append(file_path)
|
|
110
|
+
return ["unused-param-sentinel"]
|
|
111
|
+
|
|
112
|
+
monkeypatch.setattr(validator, "validate_required_parameter_usage", check)
|
|
113
|
+
valid, errors = validator.validate_data_file(path, "listing_v1")
|
|
114
|
+
assert not valid
|
|
115
|
+
assert "unused-param-sentinel" in errors
|
|
116
|
+
assert calls == [path]
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def test_invalid_jinja_does_not_bypass_the_rule():
|
|
120
|
+
errors = validate_required_parameter_usage(listing(), {"host": "{{ params.host"})
|
|
121
|
+
assert "invalid Jinja template" in errors[0]
|
|
@@ -675,3 +675,11 @@ class TestPlatformListingBaseUrls:
|
|
|
675
675
|
def test_requires_the_address(self, value) -> None:
|
|
676
676
|
errors = validate_platform_listing_base_urls(value)
|
|
677
677
|
assert len(errors) == 1 and "/p/<route>" in errors[0]
|
|
678
|
+
|
|
679
|
+
|
|
680
|
+
def test_request_transformer_enum_has_msg_transformer():
|
|
681
|
+
"""The gateway's msg_transformer key must be a valid request_transformer
|
|
682
|
+
key, or the backend rejects routes that use it (unitysvc/unitysvc#2650)."""
|
|
683
|
+
from unitysvc_core.models import RequestTransformEnum
|
|
684
|
+
|
|
685
|
+
assert RequestTransformEnum("msg_transformer") is RequestTransformEnum.msg_transformer
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{unitysvc_core-0.2.32 → unitysvc_core-0.2.34}/src/unitysvc_core/models/service_group_data.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{unitysvc_core-0.2.32 → unitysvc_core-0.2.34}/src/unitysvc_core/schema/service_group_v1.json
RENAMED
|
File without changes
|
|
File without changes
|
{unitysvc_core-0.2.32 → unitysvc_core-0.2.34}/src/unitysvc_core.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|