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
11 changes: 9 additions & 2 deletions src/specify_cli/bundler/lib/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from pathlib import Path

from ..._project import _resolve_init_dir_override
from ...integration_state import clean_integration_key
from .. import BundlerError
from .yamlio import ensure_within, load_json

Expand Down Expand Up @@ -97,6 +98,12 @@ def active_integration(project_root: Path) -> str | None:
or data.get("id")
or data.get("active")
)
if isinstance(value, str) and value:
return value
# Normalize through the same helper the canonical reader uses rather
# than re-implementing the check. ``isinstance(value, str) and value``
# accepted a whitespace-only key as a real one -- truthy, so it also
# suppressed the "not determinable" fallback -- and returned a padded
# key verbatim, which matches no registered integration:
# ' copilot ' -> ' copilot ' (canonical: 'copilot')
# ' ' -> ' ' (canonical: None)
return clean_integration_key(value)
return None
36 changes: 36 additions & 0 deletions tests/contract/test_bundle_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -1075,3 +1075,39 @@ def fake_open_url(url, timeout=None, extra_headers=None, redirect_validator=None
# Rich may wrap the message across lines; normalise whitespace before checking.
output_flat = " ".join(result.output.split())
assert "exceeds maximum size of 100 bytes" in output_flat


@pytest.mark.parametrize(
"recorded,expected",
[
("copilot", "copilot"),
(" copilot ", "copilot"), # padded: previously returned verbatim
(" ", None), # whitespace-only: previously truthy
("\t\n", None),
("", None),
(None, None),
(5, None),
],
ids=["plain", "padded", "spaces", "tabs", "empty", "null", "non_string"],
)
def test_active_integration_matches_the_canonical_key_reader(
tmp_path: Path, recorded, expected
):
"""`active_integration` must normalize the way the canonical reader does.

Its own comment says it matches `integration_state`'s reader, but that
reader runs every value through `clean_integration_key`, while this one
only checked `isinstance(value, str) and value`. A whitespace-only key is
truthy, so it was returned as a real integration *and* suppressed the
"not determinable" fallback; a padded key was returned verbatim and
matches no registered integration.
"""
from specify_cli.bundler.lib.project import active_integration

project = tmp_path / "proj"
(project / ".specify").mkdir(parents=True)
(project / ".specify" / "integration.json").write_text(
json.dumps({"default_integration": recorded}), encoding="utf-8"
)

assert active_integration(project) == expected