diff --git a/cloudsmith_cli/cli/commands/upstream.py b/cloudsmith_cli/cli/commands/upstream.py index f999071d..d881d002 100644 --- a/cloudsmith_cli/cli/commands/upstream.py +++ b/cloudsmith_cli/cli/commands/upstream.py @@ -67,9 +67,28 @@ def build_row(u): click.style(fmt_bool(u["verify_ssl"]), fg="green"), ] + if upstream_fmt == "alpine": + # RSA verification fields are alpine-only + row.append( + click.style( + maybe_truncate_string(str(u.get("rsa_key_inline") or "")), + fg="yellow", + ) + ) + row.append( + click.style( + maybe_truncate_string(str(u.get("rsa_key_url") or "")), + fg="yellow", + ) + ) + row.append(click.style(str(u.get("rsa_verification") or ""), fg="yellow")) + row.append( + click.style(str(u.get("rsa_verification_status") or ""), fg="yellow") + ) + if upstream_fmt == "deb": # `Component`, `Distribution Versions` and `Upstream Distribution` are deb-only - row.append(click.style(str(u.get("component", None)), fg="yellow")) + row.append(click.style(str(u.get("component", None) or ""), fg="yellow")) row.append( click.style( str(maybe_truncate_list(u.get("distro_versions", []))), @@ -77,7 +96,9 @@ def build_row(u): ) ) row.append( - click.style(str(u.get("upstream_distribution", None)), fg="yellow") + click.style( + str(u.get("upstream_distribution", None) or ""), fg="yellow" + ) ) if upstream_fmt == "rpm": @@ -105,6 +126,12 @@ def build_row(u): "Verify SSL", ] + if upstream_fmt == "alpine": + headers.append("RSA Key Inline") + headers.append("RSA Key URL") + headers.append("RSA Verification") + headers.append("RSA Verification Status") + if upstream_fmt == "deb": headers.append("Component") headers.append("Distribution Versions") diff --git a/cloudsmith_cli/cli/tests/commands/test_upstream.py b/cloudsmith_cli/cli/tests/commands/test_upstream.py index 99dea9e0..4362bb64 100644 --- a/cloudsmith_cli/cli/tests/commands/test_upstream.py +++ b/cloudsmith_cli/cli/tests/commands/test_upstream.py @@ -168,3 +168,80 @@ def test_upstream_commands( assert result.exit_code == 0 result_data = json.loads(result.output)["data"] assert not result_data # We should have no upstreams at this point + + +@pytest.mark.usefixtures("set_api_key_env_var", "set_api_host_env_var") +@pytest.mark.integration +def test_alpine_upstream_ls_pretty_rsa_columns( + runner, organization, tmp_repository, tmp_path +): + """Pretty-output ls for alpine must render all four RSA columns with correct headers and values. + + Alpine is the only format with RSA verification fields (rsa_key_inline, rsa_key_url, + rsa_verification, rsa_verification_status). These are appended to the common column set + inside print_upstreams(), so a regression in the branching logic or header list would + silently drop or misalign them. This test catches that by asserting against the rendered + table text rather than JSON output. + """ + upstream_url = "https://www.cloudsmith.io" + rsa_key_url = "https://www.cloudsmith.io/alpine-rsa-key.pub" + rsa_verification = "Reject Invalid" + upstream_config = { + "name": "cli-test-upstream-alpine-rsa", + "upstream_url": upstream_url, + "rsa_key_url": rsa_key_url, + "rsa_verification": rsa_verification, + } + + upstream_config_file = tmp_path / "cli-test-upstream-alpine-rsa.json" + upstream_config_file.write_text(json.dumps(upstream_config)) + + org_repo = f"{organization}/{tmp_repository['slug']}" + + # Create the upstream and capture its slug_perm for later cleanup + create_result = runner.invoke( + upstream, + args=["alpine", "create", org_repo, str(upstream_config_file), "-F", "json"], + catch_exceptions=False, + ) + assert create_result.exit_code == 0 + slug_perm = json.loads(create_result.output)["data"]["slug_perm"] + + try: + # Run ls with default pretty output — the path under test + result = runner.invoke( + upstream, + args=["alpine", "ls", org_repo], + catch_exceptions=False, + ) + assert result.exit_code == 0 + + # All four RSA column headers must appear in the table header row + assert "RSA Key Inline" in result.output + assert "RSA Key URL" in result.output + # "RSA Verification" is a substring of "RSA Verification Status", so verifying + # both distinct headers exist requires counting occurrences, not just membership. + assert result.output.count("RSA Verification") == 2 + + # The distinct rsa_key_url value we set must appear in the data row + assert rsa_key_url in result.output + + # The non-default rsa_verification value we set must appear in the data row + assert rsa_verification in result.output + + # rsa_verification_status is server-computed and not settable on create; it + # defaults to "Unknown" until the server verifies a package. Assert on that + # default to confirm the column renders rather than being blank/dropped. + assert "Unknown" in result.output + + # Common non-RSA headers must still be present (guard against over-trimming) + assert "Name" in result.output + assert "Upstream Url" in result.output + assert "Verify SSL" in result.output + + finally: + runner.invoke( + upstream, + args=["alpine", "delete", f"{org_repo}/{slug_perm}", "-y"], + catch_exceptions=False, + )