Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions src/specify_cli/bundler/services/resolver.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,16 @@ def resolve_install_plan(

# FR-019: integration-compatibility — a bundle that pins a different
# integration than the project's active one halts (no silent change).
#
# A blank integration arrives as ``""``, not ``None`` — which is not a usable
# integration id but satisfied NEITHER guard below (the first is a truthiness
# test, the second an ``is None`` test), so a pinned bundle was silently
# adopted: precisely the outcome this guard exists to prevent. Treat blank as
# indeterminate, and strip first like the writer
# (``integration_state.clean_integration_key``) so a padded value is not
# reported as clashing with itself.
if active_integration is not None:
active_integration = active_integration.strip() or None
effective_integration = active_integration
if manifest.integration is not None:
required = manifest.integration.id
Expand Down
26 changes: 26 additions & 0 deletions tests/unit/test_bundler_resolver.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,32 @@ def test_pinned_integration_with_indeterminate_active_fails():
)


@pytest.mark.parametrize("blank", ["", " ", "\t"])
def test_pinned_integration_with_blank_active_fails(blank):
"""A blank active integration is indeterminate, not a match.

The clash guard is a truthiness test and the indeterminate guard is an
`is None` test, so `""` satisfied neither and fell through to
`effective_integration = required` — silently adopting the bundle's pinned
integration, the exact outcome the docstring says the guard prevents.
"""
manifest = _manifest(integration={"id": "claude"})
with pytest.raises(BundlerError, match="could not be determined"):
resolve_install_plan(
manifest, speckit_version="0.11.2", active_integration=blank
)


def test_padded_active_integration_is_not_a_clash_with_itself():
"""A padded value must strip, like the writer's clean_integration_key,
rather than be reported as clashing with its own unpadded form."""
manifest = _manifest(integration={"id": "claude"})
plan = resolve_install_plan(
manifest, speckit_version="0.11.2", active_integration=" claude "
)
assert plan.effective_integration == "claude"


def test_pinned_integration_with_indeterminate_active_allows_explicit_override():
manifest = _manifest(integration={"id": "claude"})
plan = resolve_install_plan(
Expand Down