Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
Next Next commit
add tests
  • Loading branch information
asonnenschein committed Nov 11, 2025
commit c3c497d77ca73e0a199bbb4518b3ca5c5f2461a6
15 changes: 11 additions & 4 deletions planet/cli/destinations.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,13 @@ async def _patch_destination(ctx, destination_id, data, pretty):
raise ClickException(f"Failed to patch destination: {e}")


async def _list_destinations(ctx, archived, is_owner, can_write, pretty):
async def _list_destinations(ctx, archived, is_owner, can_write, is_default, pretty):
async with destinations_client(ctx) as cl:
try:
response = await cl.list_destinations(archived,
is_owner,
can_write)
can_write,
is_default)
echo_json(response, pretty)
except Exception as e:
raise ClickException(f"Failed to list destinations: {e}")
Expand Down Expand Up @@ -109,7 +110,13 @@ async def _get_default_destination(ctx, pretty):
default=None,
help="""Set to true to include only destinations the user can modify,
false to exclude them.""")
async def list_destinations(ctx, archived, is_owner, can_write, pretty):
@click.option(
'--is-default',
type=bool,
default=None,
help="""Set to true to include only the default destination,
false to exclude it.""")
async def list_destinations(ctx, archived, is_owner, can_write, is_default, pretty):
"""
List destinations with optional filters

Expand All @@ -120,7 +127,7 @@ async def list_destinations(ctx, archived, is_owner, can_write, pretty):

planet destinations list --archived false --is-owner true --can-write true
"""
await _list_destinations(ctx, archived, is_owner, can_write, pretty)
await _list_destinations(ctx, archived, is_owner, can_write, is_default, pretty)


@command(destinations, name="get")
Expand Down
6 changes: 5 additions & 1 deletion planet/clients/destinations.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,14 +60,16 @@ def __init__(self,
async def list_destinations(self,
archived: Optional[bool] = None,
is_owner: Optional[bool] = None,
can_write: Optional[bool] = None) -> Dict:
can_write: Optional[bool] = None,
is_default: Optional[bool] = None) -> Dict:
"""
List all destinations. By default, all non-archived destinations in the requesting user's org are returned.

Args:
archived (bool): If True, include archived destinations.
is_owner (bool): If True, include only destinations owned by the requesting user.
can_write (bool): If True, include only destinations the requesting user can modify.
is_default (bool): If True, include only the default destination.

Returns:
dict: A dictionary containing the list of destinations inside the 'destinations' key.
Expand All @@ -83,6 +85,8 @@ async def list_destinations(self,
params["is_owner"] = is_owner
if can_write is not None:
params["can_write"] = can_write
if is_default is not None:
params["is_default"] = is_default

try:
response = await self._session.request(method='GET',
Expand Down
55 changes: 53 additions & 2 deletions planet/sync/destinations.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,16 @@ def __init__(self, session: Session, base_url: Optional[str] = None):
def list_destinations(self,
archived: Optional[bool] = None,
is_owner: Optional[bool] = None,
can_write: Optional[bool] = None) -> Dict:
can_write: Optional[bool] = None,
is_default: Optional[bool] = None) -> Dict:
"""
List all destinations. By default, all non-archived destinations in the requesting user's org are returned.

Args:
archived (bool): If True, include archived destinations.
is_owner (bool): If True, include only destinations owned by the requesting user.
can_write (bool): If True, include only destinations the requesting user can modify.
is_default (bool): If True, include only the default destination.

Returns:
dict: A dictionary containing the list of destinations inside the 'destinations' key.
Expand All @@ -51,7 +53,7 @@ def list_destinations(self,
ClientError: If there is an issue with the client request.
"""
return self._client._call_sync(
self._client.list_destinations(archived, is_owner, can_write))
self._client.list_destinations(archived, is_owner, can_write, is_default))

def get_destination(self, destination_id: str) -> Dict:
"""
Expand Down Expand Up @@ -105,3 +107,52 @@ def create_destination(self, request: Dict[str, Any]) -> Dict:
"""
return self._client._call_sync(
self._client.create_destination(request))

def set_default_destination(self, destination_id: str) -> Dict:
"""
Set an existing destination as the default destination. Default destinations are globally available
to all members of an organization. An organization can have zero or one default destination at any time.
Ability to set a default destination is restricted to organization administrators and destination owners.

Args:
destination_id (str): The ID of the destination to set as default.

Returns:
dict: A dictionary containing the default destination details.

Raises:
APIError: If the API returns an error response.
ClientError: If there is an issue with the client request.
"""
return self._client._call_sync(
self._client.set_default_destination(destination_id))

def unset_default_destination(self) -> None:
"""
Unset the current default destination. Ability to unset a default destination is restricted to
organization administrators and destination owners. Returns None (HTTP 204, No Content) on success.

Returns:
None

Raises:
APIError: If the API returns an error response.
ClientError: If there is an issue with the client request.
"""
return self._client._call_sync(
self._client.unset_default_destination())

def get_default_destination(self) -> Dict:
"""
Get the current default destination. The default destination is globally available to all members of an
organization.

Returns:
dict: A dictionary containing the default destination details.

Raises:
APIError: If the API returns an error response.
ClientError: If there is an issue with the client request.
"""
return self._client._call_sync(
self._client.get_default_destination())
80 changes: 80 additions & 0 deletions tests/integration/test_destinations_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -179,3 +179,83 @@ async def test_get_destination_not_found():
await cl_async.get_destination(id)
with pytest.raises(Exception):
cl_sync.get_destination(id)


@respx.mock
async def test_set_default_destination():
id = DEST_1["id"]
url = f"{TEST_URL}/default"
mock_response(url, DEST_1, method="put")

def assertf(resp):
assert resp == DEST_1

assertf(await cl_async.set_default_destination(id))
assertf(cl_sync.set_default_destination(id))


@respx.mock
async def test_set_default_destination_bad_request():
id = "invalid_dest_id"
url = f"{TEST_URL}/default"
mock_response(url, {
"code": 400, "message": "Bad Request: Invalid destination ID"
},
method="put",
status_code=HTTPStatus.BAD_REQUEST)

with pytest.raises(Exception):
await cl_async.set_default_destination(id)
with pytest.raises(Exception):
cl_sync.set_default_destination(id)


@respx.mock
async def test_get_default_destination():
url = f"{TEST_URL}/default"
mock_response(url, DEST_1)

def assertf(resp):
assert resp == DEST_1

assertf(await cl_async.get_default_destination())
assertf(cl_sync.get_default_destination())


@respx.mock
async def test_get_default_destination_not_found():
url = f"{TEST_URL}/default"
mock_response(url, {
"code": 404, "message": "No default destination configured"
},
status_code=HTTPStatus.NOT_FOUND)

with pytest.raises(Exception):
await cl_async.get_default_destination()
with pytest.raises(Exception):
cl_sync.get_default_destination()


@respx.mock
async def test_unset_default_destination():
url = f"{TEST_URL}/default"
mock_response(url, None, method="delete", status_code=HTTPStatus.NO_CONTENT)

# unset_default_destination returns None
assert await cl_async.unset_default_destination() is None
assert cl_sync.unset_default_destination() is None


@respx.mock
async def test_unset_default_destination_unauthorized():
url = f"{TEST_URL}/default"
mock_response(url, {
"code": 401, "message": "Unauthorized: Insufficient permissions"
},
method="delete",
status_code=HTTPStatus.UNAUTHORIZED)

with pytest.raises(Exception):
await cl_async.unset_default_destination()
with pytest.raises(Exception):
cl_sync.unset_default_destination()
69 changes: 69 additions & 0 deletions tests/integration/test_destinations_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,9 @@ def test_destinations_cli_list(invoke):
result = invoke(['list', '--can-write', 'true'])
assert result.exit_code == 0

result = invoke(['list', '--is-default', 'true'])
assert result.exit_code == 0

result = invoke(['list', '--archived', 'false'])
assert result.exit_code == 0

Expand All @@ -189,6 +192,9 @@ def test_destinations_cli_list(invoke):
result = invoke(['list', '--can-write', 'false'])
assert result.exit_code == 0

result = invoke(['list', '--is-default', 'false'])
assert result.exit_code == 0

result = invoke(['list', '--archived', 'false', '--is-owner', 'true'])
assert result.exit_code == 0

Expand Down Expand Up @@ -251,3 +257,66 @@ def test_destinations_cli_update(invoke):
'--use-path-style'
])
assert result.exit_code == 0


@respx.mock
def test_destinations_cli_default_set(invoke):
url = f"{TEST_DESTINATIONS_URL}/default"
respx.put(url).return_value = httpx.Response(HTTPStatus.OK, json={})

result = invoke(['default', 'set', 'fake-dest-id'])
assert result.exit_code == 0


@respx.mock
def test_destinations_cli_default_set_bad_request(invoke):
url = f"{TEST_DESTINATIONS_URL}/default"
respx.put(url).return_value = httpx.Response(
HTTPStatus.BAD_REQUEST,
json={"code": 400, "message": "Bad Request: Invalid destination ID"})

result = invoke(['default', 'set', 'invalid-dest-id'])
assert result.exit_code != 0
assert "Failed to set default destination" in result.output


@respx.mock
def test_destinations_cli_default_get(invoke):
url = f"{TEST_DESTINATIONS_URL}/default"
respx.get(url).return_value = httpx.Response(HTTPStatus.OK, json={})

result = invoke(['default', 'get'])
assert result.exit_code == 0


@respx.mock
def test_destinations_cli_default_get_not_found(invoke):
url = f"{TEST_DESTINATIONS_URL}/default"
respx.get(url).return_value = httpx.Response(
HTTPStatus.NOT_FOUND,
json={"code": 404, "message": "No default destination configured"})

result = invoke(['default', 'get'])
assert result.exit_code != 0
assert "Failed to get default destination" in result.output


@respx.mock
def test_destinations_cli_default_unset(invoke):
url = f"{TEST_DESTINATIONS_URL}/default"
respx.delete(url).return_value = httpx.Response(HTTPStatus.NO_CONTENT)

result = invoke(['default', 'unset'])
assert result.exit_code == 0


@respx.mock
def test_destinations_cli_default_unset_unauthorized(invoke):
url = f"{TEST_DESTINATIONS_URL}/default"
respx.delete(url).return_value = httpx.Response(
HTTPStatus.UNAUTHORIZED,
json={"code": 401, "message": "Unauthorized: Insufficient permissions"})

result = invoke(['default', 'unset'])
assert result.exit_code != 0
assert "Failed to unset default destination" in result.output
47 changes: 47 additions & 0 deletions tests/unit/test_order_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -469,3 +469,50 @@ def test_fallback_bundle_invalid(bundle, fallback_bundle):
bundle,
"PSScene",
fallback_bundle=fallback_bundle)


def test_destination():
dest_config = order_request.destination('my-dest-ref')

expected = {
'destination': {
'ref': 'my-dest-ref'
}
}
assert dest_config == expected


def test_destination_path_prefix():
dest_config = order_request.destination('my-dest-ref',
path_prefix='my/prefix')

expected = {
'destination': {
'ref': 'my-dest-ref',
'path_prefix': 'my/prefix'
}
}
assert dest_config == expected


def test_default_destination():
dest_config = order_request.default_destination()

expected = {
'destination': {
'ref': 'pl:destinations/default'
}
}
assert dest_config == expected


def test_default_destination_path_prefix():
dest_config = order_request.default_destination(path_prefix='my/prefix')

expected = {
'destination': {
'ref': 'pl:destinations/default',
'path_prefix': 'my/prefix'
}
}
assert dest_config == expected
Loading
Loading