chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,120 @@
|
||||
"""Integration tests for the catalog stack: precedence, policy gating, search."""
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
import pytest
|
||||
|
||||
from specify_cli.bundler import BundlerError
|
||||
from specify_cli.bundler.models.catalog import CatalogSource, InstallPolicy, Scope
|
||||
from specify_cli.bundler.services.catalog_stack import CatalogStack
|
||||
from tests.bundler_helpers import catalog_entry_dict, catalog_payload
|
||||
|
||||
|
||||
def _source(source_id, priority, policy, url="builtin://x"):
|
||||
return CatalogSource(
|
||||
id=source_id, url=url, priority=priority,
|
||||
install_policy=InstallPolicy(policy), scope=Scope.PROJECT,
|
||||
)
|
||||
|
||||
|
||||
def _stack(sources, payloads):
|
||||
def fetcher(src):
|
||||
return payloads[src.id]
|
||||
return CatalogStack(sources, fetcher)
|
||||
|
||||
|
||||
def test_resolve_prefers_highest_precedence_source():
|
||||
sources = [
|
||||
_source("low", 2, "install-allowed"),
|
||||
_source("high", 1, "discovery-only"),
|
||||
]
|
||||
payloads = {
|
||||
"high": catalog_payload({"b": catalog_entry_dict("b", version="9.0.0")}),
|
||||
"low": catalog_payload({"b": catalog_entry_dict("b", version="1.0.0")}),
|
||||
}
|
||||
resolved = _stack(sources, payloads).resolve("b")
|
||||
assert resolved.source.id == "high"
|
||||
assert resolved.entry.version == "9.0.0"
|
||||
assert resolved.install_allowed is False
|
||||
|
||||
|
||||
def test_resolve_unknown_bundle_errors():
|
||||
stack = _stack(
|
||||
[_source("only", 1, "install-allowed")],
|
||||
{"only": catalog_payload({})},
|
||||
)
|
||||
with pytest.raises(BundlerError, match="not found"):
|
||||
stack.resolve("missing")
|
||||
|
||||
|
||||
def test_search_dedupes_by_precedence_and_filters():
|
||||
sources = [_source("a", 1, "install-allowed"), _source("b", 2, "install-allowed")]
|
||||
payloads = {
|
||||
"a": catalog_payload({
|
||||
"alpha": catalog_entry_dict("alpha", role="developer"),
|
||||
}),
|
||||
"b": catalog_payload({
|
||||
"alpha": catalog_entry_dict("alpha", version="0.0.1"),
|
||||
"beta": catalog_entry_dict("beta", role="qa"),
|
||||
}),
|
||||
}
|
||||
stack = _stack(sources, payloads)
|
||||
|
||||
all_results = stack.search()
|
||||
ids = [r.entry.id for r in all_results]
|
||||
assert ids == ["alpha", "beta"]
|
||||
# alpha resolved from the higher-precedence source 'a'.
|
||||
alpha = next(r for r in all_results if r.entry.id == "alpha")
|
||||
assert alpha.source.id == "a"
|
||||
|
||||
qa_only = stack.search("qa")
|
||||
assert [r.entry.id for r in qa_only] == ["beta"]
|
||||
|
||||
|
||||
def test_search_does_not_surface_a_shadowed_lower_precedence_entry():
|
||||
"""Search must resolve each id at its highest-precedence source, then
|
||||
filter — never fall through to a shadowed lower-precedence entry the query
|
||||
happens to match.
|
||||
|
||||
If the query matched only the lower-precedence copy of an id, search used
|
||||
to return that copy, even though `resolve()`/install always use the
|
||||
higher-precedence one. That advertised a bundle (name/version/source) the
|
||||
user could never actually get.
|
||||
"""
|
||||
sources = [_source("high", 1, "install-allowed"), _source("low", 2, "install-allowed")]
|
||||
payloads = {
|
||||
# Highest-precedence entry for 'shared' does NOT match "widget".
|
||||
"high": catalog_payload({
|
||||
"shared": catalog_entry_dict(
|
||||
"shared", name="Alpha Tool", role="developer",
|
||||
description="nothing relevant", version="2.0.0",
|
||||
),
|
||||
}),
|
||||
# Lower-precedence entry for the same id DOES match "widget".
|
||||
"low": catalog_payload({
|
||||
"shared": catalog_entry_dict(
|
||||
"shared", name="Searchable Widget", version="1.0.0",
|
||||
),
|
||||
}),
|
||||
}
|
||||
stack = _stack(sources, payloads)
|
||||
|
||||
# resolve() uses the high-precedence entry.
|
||||
assert stack.resolve("shared").source.id == "high"
|
||||
|
||||
# A query that only the shadowed low-precedence entry matches returns
|
||||
# nothing — search agrees with resolve().
|
||||
assert stack.search("widget") == []
|
||||
|
||||
# And a query the high-precedence entry matches returns it (from 'high').
|
||||
alpha = stack.search("alpha tool")
|
||||
assert [r.entry.id for r in alpha] == ["shared"]
|
||||
assert alpha[0].source.id == "high"
|
||||
|
||||
|
||||
def test_unreachable_source_raises_named_error():
|
||||
def fetcher(src):
|
||||
raise RuntimeError("boom")
|
||||
stack = CatalogStack([_source("bad", 1, "install-allowed")], fetcher)
|
||||
with pytest.raises(BundlerError, match="bad"):
|
||||
stack.resolve("anything")
|
||||
Reference in New Issue
Block a user