From 6515897b7da3c4cd235f3e738c3524bc96e858f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Priit=20L=C3=A4tt?= Date: Wed, 15 Jul 2026 16:50:56 +0300 Subject: [PATCH] Fix Firebase App Distribution orderBy rejected as invalid argument Firebase's `releases.list` API now strictly validates `orderBy` and rejects the previously tolerated `createTimeDesc`, breaking `get-latest-build-version` and `releases list`. Send the documented `createTime desc` grammar instead. The API value is decoupled from the `--order-by` CLI token via a new tool-layer `ReleasesOrderByArgument`, so the CLI interface stays backwards compatible (`--order-by createTimeDesc` still works). Resolves #500. --- CHANGELOG.md | 6 +++++ pyproject.toml | 2 +- src/codemagic/__version__.py | 2 +- .../google/resources/firebase/order_by.py | 8 ++++++- .../action_groups/releases_action_group.py | 6 ++--- .../argument_types.py | 11 +++++++++ .../firebase_app_distribution/arguments.py | 8 +++---- .../firebase_app_distribution_action.py | 5 ++-- .../firebase_client/test_firebase_client.py | 23 +++++++++++++++++++ tests/tools/test_firebase_app_distribution.py | 20 ++++++++++++++++ uv.lock | 2 +- 11 files changed, 80 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7e639bf30..d41971349 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +Version 0.69.0 +------------- + +**Bugfixes** +- Fix `firebase-app-distribution` actions `get-latest-build-version` and `releases list`. The Firebase App Distribution API changed `orderBy` query parameter validation and rejects the previously tolerated `createTimeDesc`, requiring the documented space-separated grammar `createTime desc` instead. + Version 0.68.0 ------------- diff --git a/pyproject.toml b/pyproject.toml index f79ac029f..c6fa3143c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "codemagic-cli-tools" -version = "0.68.0" +version = "0.69.0" description = "CLI tools used in Codemagic builds" authors = [{ name = "Priit Lätt", email = "priit@nevercode.io" }] requires-python = ">=3.8,<4" diff --git a/src/codemagic/__version__.py b/src/codemagic/__version__.py index 1f623e628..e49d2a2c7 100644 --- a/src/codemagic/__version__.py +++ b/src/codemagic/__version__.py @@ -1,5 +1,5 @@ __title__ = "codemagic-cli-tools" __description__ = "CLI tools used in Codemagic builds" -__version__ = "0.68.0.dev" +__version__ = "0.69.0.dev" __url__ = "https://github.com/codemagic-ci-cd/cli-tools" __licence__ = "GNU General Public License v3.0" diff --git a/src/codemagic/google/resources/firebase/order_by.py b/src/codemagic/google/resources/firebase/order_by.py index a83f41073..84d0f0786 100644 --- a/src/codemagic/google/resources/firebase/order_by.py +++ b/src/codemagic/google/resources/firebase/order_by.py @@ -2,5 +2,11 @@ class OrderBy(ResourceEnum): - CREATE_TIME_DESC = "createTimeDesc" + """ + Values follow the Firebase App Distribution `releases.list` `orderBy` grammar, which uses a + space-separated `desc` suffix, see + https://firebase.google.com/docs/reference/app-distribution/rest/v1/projects.apps.releases/list + """ + + CREATE_TIME_DESC = "createTime desc" CREATE_TIME_ASC = "createTime" diff --git a/src/codemagic/tools/firebase_app_distribution/action_groups/releases_action_group.py b/src/codemagic/tools/firebase_app_distribution/action_groups/releases_action_group.py index 32c8faba8..4bcaac9a7 100644 --- a/src/codemagic/tools/firebase_app_distribution/action_groups/releases_action_group.py +++ b/src/codemagic/tools/firebase_app_distribution/action_groups/releases_action_group.py @@ -4,9 +4,9 @@ from codemagic import cli from codemagic.cli import Colors from codemagic.google.errors import GoogleError -from codemagic.google.resources.firebase import OrderBy from codemagic.google.resources.firebase import Release +from ..argument_types import ReleasesOrderByArgument from ..arguments import ReleasesArgument from ..arguments import ResourcesArgument from ..errors import FirebaseAppDistributionError @@ -26,7 +26,7 @@ def list_releases( self, app_id: str, limit: int = ResourcesArgument.LIMIT.get_default(), - order_by: OrderBy = ResourcesArgument.ORDER_BY.get_default(), + order_by: ReleasesOrderByArgument = ResourcesArgument.ORDER_BY.get_default(), should_print: bool = True, ) -> List[Release]: """ @@ -34,7 +34,7 @@ def list_releases( """ try: - releases = self.client.releases.list(self.project_number, app_id, order_by, limit) + releases = self.client.releases.list(self.project_number, app_id, order_by.order_by, limit) except GoogleError as e: raise FirebaseAppDistributionError(str(e)) diff --git a/src/codemagic/tools/firebase_app_distribution/argument_types.py b/src/codemagic/tools/firebase_app_distribution/argument_types.py index b394c8a39..857e9b11d 100644 --- a/src/codemagic/tools/firebase_app_distribution/argument_types.py +++ b/src/codemagic/tools/firebase_app_distribution/argument_types.py @@ -3,6 +3,17 @@ from typing import Dict from codemagic import cli +from codemagic.google.resources.firebase import OrderBy +from codemagic.models.enums import ResourceEnum + + +class ReleasesOrderByArgument(ResourceEnum): + CREATE_TIME_DESC = "createTimeDesc" + CREATE_TIME_ASC = "createTime" + + @property + def order_by(self) -> OrderBy: + return OrderBy[self.name] class CredentialsArgument(cli.EnvironmentArgumentValue[dict]): diff --git a/src/codemagic/tools/firebase_app_distribution/arguments.py b/src/codemagic/tools/firebase_app_distribution/arguments.py index 09a37c461..3fb51e3af 100644 --- a/src/codemagic/tools/firebase_app_distribution/arguments.py +++ b/src/codemagic/tools/firebase_app_distribution/arguments.py @@ -1,8 +1,8 @@ from codemagic import cli from codemagic.cli.colors import Colors -from codemagic.google.resources.firebase import OrderBy from .argument_types import CredentialsArgument +from .argument_types import ReleasesOrderByArgument select_project_group = cli.MutuallyExclusiveGroup( name="select project", @@ -50,12 +50,12 @@ class ResourcesArgument(cli.Argument): ORDER_BY = cli.ArgumentProperties( key="order_by", flags=("--order-by", "-o"), - type=OrderBy, + type=ReleasesOrderByArgument, description="Sort resources in the specified order", argparse_kwargs={ "required": False, - "default": OrderBy.CREATE_TIME_DESC, - "choices": list(OrderBy), + "default": ReleasesOrderByArgument.CREATE_TIME_DESC, + "choices": list(ReleasesOrderByArgument), }, ) diff --git a/src/codemagic/tools/firebase_app_distribution/firebase_app_distribution_action.py b/src/codemagic/tools/firebase_app_distribution/firebase_app_distribution_action.py index 2c8e3ca9f..bb0a7dd5c 100644 --- a/src/codemagic/tools/firebase_app_distribution/firebase_app_distribution_action.py +++ b/src/codemagic/tools/firebase_app_distribution/firebase_app_distribution_action.py @@ -5,9 +5,10 @@ from codemagic.google.firebase_client import FirebaseClient from codemagic.google.resources import ResourcePrinter -from codemagic.google.resources.firebase import OrderBy from codemagic.google.resources.firebase import Release +from .argument_types import ReleasesOrderByArgument + class FirebaseAppDistributionAction(ABC): client: FirebaseClient @@ -37,7 +38,7 @@ def list_releases( self, app_id: str, limit: int = 25, - order_by: OrderBy = OrderBy.CREATE_TIME_DESC, + order_by: ReleasesOrderByArgument = ReleasesOrderByArgument.CREATE_TIME_DESC, should_print: bool = True, ) -> List[Release]: from .action_groups.releases_action_group import ReleasesActionGroup diff --git a/tests/google/firebase_client/test_firebase_client.py b/tests/google/firebase_client/test_firebase_client.py index ab481e780..10abdf9d8 100644 --- a/tests/google/firebase_client/test_firebase_client.py +++ b/tests/google/firebase_client/test_firebase_client.py @@ -69,6 +69,29 @@ def test_list_releases(firebase_client, release, mock_releases): assert releases[1].buildVersion == "71" +@pytest.mark.parametrize( + ("order_by", "expected_order_by_param"), + ( + (OrderBy.CREATE_TIME_DESC, "createTime desc"), + (OrderBy.CREATE_TIME_ASC, "createTime"), + ), +) +def test_list_releases_order_by(firebase_client, mock_releases, order_by, expected_order_by_param): + firebase_client.releases.list( + "firebase-project-id", + "firebase-app-id", + order_by=order_by, + page_size=2, + ) + + mock_releases.return_value.list.assert_called_once_with( + orderBy=expected_order_by_param, + parent="projects/firebase-project-id/apps/firebase-app-id", + pageSize=2, + pageToken="", + ) + + def test_list_releases_limit(firebase_client, mock_releases): releases = firebase_client.releases.list( "firebase-project-number", diff --git a/tests/tools/test_firebase_app_distribution.py b/tests/tools/test_firebase_app_distribution.py index a97c49852..6d047319f 100644 --- a/tests/tools/test_firebase_app_distribution.py +++ b/tests/tools/test_firebase_app_distribution.py @@ -17,6 +17,7 @@ from codemagic.google.services.firebase import ReleasesService from codemagic.tools.firebase_app_distribution import FirebaseAppDistribution from codemagic.tools.firebase_app_distribution.argument_types import CredentialsArgument +from codemagic.tools.firebase_app_distribution.argument_types import ReleasesOrderByArgument from codemagic.tools.firebase_app_distribution.arguments import FirebaseArgument from codemagic.tools.firebase_app_distribution.errors import FirebaseAppDistributionError @@ -26,6 +27,25 @@ json_output_argument = FirebaseArgument.JSON_OUTPUT +@pytest.mark.parametrize( + ("argument", "expected_order_by", "expected_cli_token"), + ( + (ReleasesOrderByArgument.CREATE_TIME_DESC, OrderBy.CREATE_TIME_DESC, "createTimeDesc"), + (ReleasesOrderByArgument.CREATE_TIME_ASC, OrderBy.CREATE_TIME_ASC, "createTime"), + ), +) +def test_releases_order_by_argument(argument, expected_order_by, expected_cli_token): + # CLI parameter value stays a single, backwards-compatible token and maps to the API-facing OrderBy. + assert argument.value == expected_cli_token + assert argument.order_by is expected_order_by + + +def test_every_releases_order_by_argument_maps_to_order_by(): + # Guards against adding a CLI ordering choice without a matching API-facing OrderBy member. + for argument in ReleasesOrderByArgument: + assert isinstance(argument.order_by, OrderBy) + + @pytest.fixture def releases() -> List[Release]: mock_response_path = pathlib.Path(__file__).parent / "mocks" / "firebase_releases.json" diff --git a/uv.lock b/uv.lock index 5407fc88f..1dfb3c042 100644 --- a/uv.lock +++ b/uv.lock @@ -550,7 +550,7 @@ wheels = [ [[package]] name = "codemagic-cli-tools" -version = "0.68.0" +version = "0.69.0" source = { editable = "." } dependencies = [ { name = "cryptography" },