From 73972a2cd88220060b32c10d6ad9ae71e000de22 Mon Sep 17 00:00:00 2001 From: Gyan Ranjan Panda Date: Sat, 21 Feb 2026 18:27:44 +0530 Subject: [PATCH] Fix #1980: Support all core output formats in run command Centralize output formats in pipes/output.py and ensure only stdout streaming compatible formats are enabled for the run command. Signed-off-by: Gyan Ranjan Panda --- scanpipe/management/commands/output.py | 11 +----- scanpipe/management/commands/run.py | 3 +- scanpipe/pipes/output.py | 21 +++++++++++ scanpipe/tests/test_commands.py | 50 ++++++++++++++++++++++++++ 4 files changed, 74 insertions(+), 11 deletions(-) diff --git a/scanpipe/management/commands/output.py b/scanpipe/management/commands/output.py index 8d3d78f50a..36d3d01387 100644 --- a/scanpipe/management/commands/output.py +++ b/scanpipe/management/commands/output.py @@ -24,16 +24,7 @@ from scanpipe.management.commands import ProjectCommand from scanpipe.pipes import output - -SUPPORTED_FORMATS = [ - "json", - "csv", - "xlsx", - "attribution", - "spdx", - "cyclonedx", - "ort-package-list", -] +from scanpipe.pipes.output import SUPPORTED_FORMATS class Command(ProjectCommand): diff --git a/scanpipe/management/commands/run.py b/scanpipe/management/commands/run.py index 3a3242e57c..c1cdcbdb1a 100644 --- a/scanpipe/management/commands/run.py +++ b/scanpipe/management/commands/run.py @@ -30,6 +30,7 @@ from scanpipe.management.commands import extract_tag_from_input_file from scanpipe.pipes.fetch import SCHEME_TO_FETCHER_MAPPING +from scanpipe.pipes.output import PRINT_SUPPORTED_FORMATS class Command(BaseCommand): @@ -59,7 +60,7 @@ def add_arguments(self, parser): parser.add_argument( "--format", default="json", - choices=["json", "spdx", "cyclonedx", "attribution", "ort-package-list"], + choices=PRINT_SUPPORTED_FORMATS, help="Specifies the output serialization format for the results.", ) diff --git a/scanpipe/pipes/output.py b/scanpipe/pipes/output.py index 03fc4ca51c..a2587ae7dc 100644 --- a/scanpipe/pipes/output.py +++ b/scanpipe/pipes/output.py @@ -68,6 +68,27 @@ scanpipe_app = apps.get_app_config("scanpipe") +# All formats supported by the `output` management command. +SUPPORTED_FORMATS = [ + "json", + "csv", + "xlsx", + "attribution", + "spdx", + "cyclonedx", + "ort-package-list", +] + +# Subset of formats compatible with stdout streaming (--print mode). +# Excludes binary formats (xlsx) and multi-file formats (csv). +PRINT_SUPPORTED_FORMATS = [ + "json", + "spdx", + "cyclonedx", + "attribution", + "ort-package-list", +] + def safe_filename(filename): """Convert the provided `filename` to a safe filename.""" diff --git a/scanpipe/tests/test_commands.py b/scanpipe/tests/test_commands.py index 7e93bc1c64..c16c75aaf0 100644 --- a/scanpipe/tests/test_commands.py +++ b/scanpipe/tests/test_commands.py @@ -48,6 +48,8 @@ from scanpipe.models import WebhookSubscription from scanpipe.pipes import flag from scanpipe.pipes import purldb +from scanpipe.pipes.output import PRINT_SUPPORTED_FORMATS +from scanpipe.pipes.output import SUPPORTED_FORMATS as PIPE_SUPPORTED_FORMATS from scanpipe.tests import filter_warnings from scanpipe.tests import make_dependency from scanpipe.tests import make_mock_response @@ -756,6 +758,29 @@ def test_scanpipe_management_command_output(self): self.assertIn('"bomFormat": "CycloneDX"', out_value) self.assertIn('"specVersion": "1.5",', out_value) + def test_supported_formats_sync(self): + """ + Ensure SUPPORTED_FORMATS in management/commands/output.py always matches + the canonical list in pipes/output.py, and that PRINT_SUPPORTED_FORMATS + is a strict subset of SUPPORTED_FORMATS (never includes csv or xlsx). + """ + from scanpipe.management.commands.output import SUPPORTED_FORMATS as CMD_FORMATS + + self.assertEqual( + set(PIPE_SUPPORTED_FORMATS), + set(CMD_FORMATS), + "SUPPORTED_FORMATS in management/commands/output.py is out of sync " + "with pipes/output.py. Update one of them.", + ) + # PRINT_SUPPORTED_FORMATS must be a subset of SUPPORTED_FORMATS + self.assertTrue( + set(PRINT_SUPPORTED_FORMATS).issubset(set(PIPE_SUPPORTED_FORMATS)), + "PRINT_SUPPORTED_FORMATS contains formats not in SUPPORTED_FORMATS.", + ) + # csv and xlsx must never be in PRINT_SUPPORTED_FORMATS (--print incompatible) + self.assertNotIn("csv", PRINT_SUPPORTED_FORMATS) + self.assertNotIn("xlsx", PRINT_SUPPORTED_FORMATS) + def test_scanpipe_management_command_delete_project(self): project = make_project(name="my_project") work_path = project.work_path @@ -984,6 +1009,31 @@ def test_scanpipe_management_command_run(self): json_data = json.loads(out.getvalue()) self.assertEqual(3, len(json_data["files"])) + # Test --format spdx + out = StringIO() + with redirect_stdout(out): + call_command("run", "do_nothing", input_location, "--format", "spdx") + out_value = out.getvalue() + self.assertIn('"spdxVersion":', out_value) + + # Test --format cyclonedx + out = StringIO() + with redirect_stdout(out): + call_command("run", "do_nothing", input_location, "--format", "cyclonedx") + out_value = out.getvalue() + self.assertIn('"bomFormat": "CycloneDX"', out_value) + + # Test --format ort-package-list + # do_nothing pipeline doesn't find packages, so it generates an empty list, but it shouldn't crash + out = StringIO() + with redirect_stdout(out): + call_command("run", "do_nothing", input_location, "--format", "ort-package-list") + + # Test incompatible streaming formats are rejected by argparse choices + expected = "Error: argument --format: invalid choice: 'csv'" + with self.assertRaisesMessage(CommandError, expected): + call_command("run", "do_nothing", input_location, "--format", "csv") + # Multiple pipeline and selected_groups are supported out = StringIO() with redirect_stdout(out):