diff --git a/homeassistant/components/portainer/coordinator.py b/homeassistant/components/portainer/coordinator.py index 2fe29413ec1..1b84409dbde 100644 --- a/homeassistant/components/portainer/coordinator.py +++ b/homeassistant/components/portainer/coordinator.py @@ -196,6 +196,7 @@ class PortainerCoordinator(DataUpdateCoordinator[dict[int, PortainerCoordinatorD # Check if container belongs to a stack via docker compose label stack_name: str | None = ( container.labels.get("com.docker.compose.project") + or container.labels.get("com.docker.stack.namespace") if container.labels else None ) diff --git a/tests/components/portainer/fixtures/containers.json b/tests/components/portainer/fixtures/containers.json index 8094b8bcccd..3728db9fbb0 100644 --- a/tests/components/portainer/fixtures/containers.json +++ b/tests/components/portainer/fixtures/containers.json @@ -168,5 +168,31 @@ ], "State": "running", "Status": "Up 6 hours" + }, + { + "Id": "ff31facfb3b3ed4cd362c1e88fc89a53908ad05fb3a4103bca3f9b28292d14bf", + "Names": ["/dashy_dashy.1.qgza68hnz4n1qvyz3iohynx05"], + "Image": "docker.io/lissy93/dashy:latest", + "ImageID": "sha256:7f8a4339a5c5d5d37d1262630108ab308d3e9ec7ed1c3286a32fe09856619a782", + "Command": "node server", + "Created": "1739816096", + "Ports": [ + { + "PrivatePort": 8080, + "PublicPort": 4000, + "Type": "tcp" + } + ], + "Labels": { + "com.docker.stack.namespace": "dashy", + "com.docker.swarm.node.id": "nggd3w8ntk2ivzkka6ecprjkh", + "com.docker.swarm.service.id": "nk8zud67mpr6nyln0vhko65zb", + "com.docker.swarm.service.name": "dashy_dashy", + "com.docker.swarm.task": "", + "com.docker.swarm.task.id": "qgza68hnz4n1qvyz3iohynx05", + "com.docker.swarm.task.name": "dashy_dashy.1.qgza68hnz4n1qvyz3iohynx05" + }, + "State": "running", + "Status": "Up 3 hours" } ] diff --git a/tests/components/portainer/fixtures/stacks.json b/tests/components/portainer/fixtures/stacks.json index 90cea90b5cd..3b07af9bbe8 100644 --- a/tests/components/portainer/fixtures/stacks.json +++ b/tests/components/portainer/fixtures/stacks.json @@ -10,5 +10,17 @@ "CreatedBy": "admin", "CreationDate": 1739700000, "FromAppTemplate": false + }, + { + "Id": 2, + "Name": "dashy", + "Type": 1, + "EndpointId": 1, + "Status": 1, + "EntryPoint": "docker-stack.yml", + "ProjectPath": "/data/compose/dashy", + "CreatedBy": "admin", + "CreationDate": 1739710000, + "FromAppTemplate": false } ] diff --git a/tests/components/portainer/snapshots/test_binary_sensor.ambr b/tests/components/portainer/snapshots/test_binary_sensor.ambr index df8ee5a6273..06704a2e8e0 100644 --- a/tests/components/portainer/snapshots/test_binary_sensor.ambr +++ b/tests/components/portainer/snapshots/test_binary_sensor.ambr @@ -1,4 +1,104 @@ # serializer version: 1 +# name: test_all_entities[binary_sensor.dashy_dashy_1_qgza68hnz4n1qvyz3iohynx05_status-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'binary_sensor', + 'entity_category': , + 'entity_id': 'binary_sensor.dashy_dashy_1_qgza68hnz4n1qvyz3iohynx05_status', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Status', + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Status', + 'platform': 'portainer', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'status', + 'unique_id': 'portainer_test_entry_123_dashy_dashy.1.qgza68hnz4n1qvyz3iohynx05_status', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[binary_sensor.dashy_dashy_1_qgza68hnz4n1qvyz3iohynx05_status-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'running', + 'friendly_name': 'dashy_dashy.1.qgza68hnz4n1qvyz3iohynx05 Status', + }), + 'context': , + 'entity_id': 'binary_sensor.dashy_dashy_1_qgza68hnz4n1qvyz3iohynx05_status', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'on', + }) +# --- +# name: test_all_entities[binary_sensor.dashy_status-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'binary_sensor', + 'entity_category': , + 'entity_id': 'binary_sensor.dashy_status', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Status', + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Status', + 'platform': 'portainer', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'status', + 'unique_id': 'portainer_test_entry_123_2_stack_status', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[binary_sensor.dashy_status-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'running', + 'friendly_name': 'dashy Status', + }), + 'context': , + 'entity_id': 'binary_sensor.dashy_status', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'on', + }) +# --- # name: test_all_entities[binary_sensor.focused_einstein_status-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ diff --git a/tests/components/portainer/snapshots/test_button.ambr b/tests/components/portainer/snapshots/test_button.ambr index 1d05a4d4f95..dce090b2699 100644 --- a/tests/components/portainer/snapshots/test_button.ambr +++ b/tests/components/portainer/snapshots/test_button.ambr @@ -1,4 +1,54 @@ # serializer version: 1 +# name: test_all_button_entities_snapshot[button.dashy_dashy_1_qgza68hnz4n1qvyz3iohynx05_restart_container-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'button', + 'entity_category': , + 'entity_id': 'button.dashy_dashy_1_qgza68hnz4n1qvyz3iohynx05_restart_container', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Restart container', + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Restart container', + 'platform': 'portainer', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'restart_container', + 'unique_id': 'portainer_test_entry_123_dashy_dashy.1.qgza68hnz4n1qvyz3iohynx05_restart', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_button_entities_snapshot[button.dashy_dashy_1_qgza68hnz4n1qvyz3iohynx05_restart_container-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'restart', + 'friendly_name': 'dashy_dashy.1.qgza68hnz4n1qvyz3iohynx05 Restart container', + }), + 'context': , + 'entity_id': 'button.dashy_dashy_1_qgza68hnz4n1qvyz3iohynx05_restart_container', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unknown', + }) +# --- # name: test_all_button_entities_snapshot[button.focused_einstein_restart_container-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ diff --git a/tests/components/portainer/snapshots/test_diagnostics.ambr b/tests/components/portainer/snapshots/test_diagnostics.ambr index 7059ae76119..b1067e64c90 100644 --- a/tests/components/portainer/snapshots/test_diagnostics.ambr +++ b/tests/components/portainer/snapshots/test_diagnostics.ambr @@ -73,6 +73,15 @@ 'state': 'running', 'status': 'Up 6 hours', }), + dict({ + 'id': 'ff31facfb3b3ed4cd362c1e88fc89a53908ad05fb3a4103bca3f9b28292d14bf', + 'image': 'docker.io/lissy93/dashy:latest', + 'names': list([ + '/dashy_dashy.1.qgza68hnz4n1qvyz3iohynx05', + ]), + 'state': 'running', + 'status': 'Up 3 hours', + }), ]), 'endpoint': dict({ 'public_url': 'docker.mydomain.tld:2375', diff --git a/tests/components/portainer/snapshots/test_init.ambr b/tests/components/portainer/snapshots/test_init.ambr index 5166906493a..75ff06d882f 100644 --- a/tests/components/portainer/snapshots/test_init.ambr +++ b/tests/components/portainer/snapshots/test_init.ambr @@ -146,6 +146,35 @@ 'sw_version': None, 'via_device_id': , }), + DeviceRegistryEntrySnapshot({ + 'area_id': None, + 'config_entries': , + 'config_entries_subentries': , + 'configuration_url': 'https://127.0.0.1:9000/#!/1/docker/stacks/dashy', + 'connections': set({ + }), + 'disabled_by': None, + 'entry_type': None, + 'hw_version': None, + 'id': , + 'identifiers': set({ + tuple( + 'portainer', + 'portainer_test_entry_123_1_stack_2', + ), + }), + 'labels': set({ + }), + 'manufacturer': 'Portainer', + 'model': 'Stack', + 'model_id': None, + 'name': 'dashy', + 'name_by_user': None, + 'primary_config_entry': , + 'serial_number': None, + 'sw_version': None, + 'via_device_id': , + }), DeviceRegistryEntrySnapshot({ 'area_id': None, 'config_entries': , @@ -204,5 +233,34 @@ 'sw_version': None, 'via_device_id': , }), + DeviceRegistryEntrySnapshot({ + 'area_id': None, + 'config_entries': , + 'config_entries_subentries': , + 'configuration_url': 'https://127.0.0.1:9000/#!/1/docker/containers/ff31facfb3b3ed4cd362c1e88fc89a53908ad05fb3a4103bca3f9b28292d14bf', + 'connections': set({ + }), + 'disabled_by': None, + 'entry_type': , + 'hw_version': None, + 'id': , + 'identifiers': set({ + tuple( + 'portainer', + 'portainer_test_entry_123_1_dashy_dashy.1.qgza68hnz4n1qvyz3iohynx05', + ), + }), + 'labels': set({ + }), + 'manufacturer': 'Portainer', + 'model': 'Container', + 'model_id': None, + 'name': 'dashy_dashy.1.qgza68hnz4n1qvyz3iohynx05', + 'name_by_user': None, + 'primary_config_entry': , + 'serial_number': None, + 'sw_version': None, + 'via_device_id': , + }), ]) # --- diff --git a/tests/components/portainer/snapshots/test_sensor.ambr b/tests/components/portainer/snapshots/test_sensor.ambr index 3523a371f2e..dc5e07e2f6a 100644 --- a/tests/components/portainer/snapshots/test_sensor.ambr +++ b/tests/components/portainer/snapshots/test_sensor.ambr @@ -1,4 +1,466 @@ # serializer version: 1 +# name: test_all_entities[sensor.dashy_containers-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.dashy_containers', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Containers', + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Containers', + 'platform': 'portainer', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'stack_containers_count', + 'unique_id': 'portainer_test_entry_123_2_stack_containers_count', + 'unit_of_measurement': 'containers', + }) +# --- +# name: test_all_entities[sensor.dashy_containers-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'dashy Containers', + 'state_class': , + 'unit_of_measurement': 'containers', + }), + 'context': , + 'entity_id': 'sensor.dashy_containers', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '1', + }) +# --- +# name: test_all_entities[sensor.dashy_dashy_1_qgza68hnz4n1qvyz3iohynx05_cpu_usage_total-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.dashy_dashy_1_qgza68hnz4n1qvyz3iohynx05_cpu_usage_total', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'CPU usage total', + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 2, + }), + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'CPU usage total', + 'platform': 'portainer', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'cpu_usage_total', + 'unique_id': 'portainer_test_entry_123_dashy_dashy.1.qgza68hnz4n1qvyz3iohynx05_cpu_usage_total', + 'unit_of_measurement': '%', + }) +# --- +# name: test_all_entities[sensor.dashy_dashy_1_qgza68hnz4n1qvyz3iohynx05_cpu_usage_total-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'dashy_dashy.1.qgza68hnz4n1qvyz3iohynx05 CPU usage total', + 'state_class': , + 'unit_of_measurement': '%', + }), + 'context': , + 'entity_id': 'sensor.dashy_dashy_1_qgza68hnz4n1qvyz3iohynx05_cpu_usage_total', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '0.0', + }) +# --- +# name: test_all_entities[sensor.dashy_dashy_1_qgza68hnz4n1qvyz3iohynx05_image-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.dashy_dashy_1_qgza68hnz4n1qvyz3iohynx05_image', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Image', + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Image', + 'platform': 'portainer', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'image', + 'unique_id': 'portainer_test_entry_123_dashy_dashy.1.qgza68hnz4n1qvyz3iohynx05_image', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[sensor.dashy_dashy_1_qgza68hnz4n1qvyz3iohynx05_image-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'dashy_dashy.1.qgza68hnz4n1qvyz3iohynx05 Image', + }), + 'context': , + 'entity_id': 'sensor.dashy_dashy_1_qgza68hnz4n1qvyz3iohynx05_image', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'docker.io/lissy93/dashy:latest', + }) +# --- +# name: test_all_entities[sensor.dashy_dashy_1_qgza68hnz4n1qvyz3iohynx05_memory_limit-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.dashy_dashy_1_qgza68hnz4n1qvyz3iohynx05_memory_limit', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Memory limit', + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 1, + }), + 'sensor.private': dict({ + 'suggested_unit_of_measurement': , + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Memory limit', + 'platform': 'portainer', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'memory_limit', + 'unique_id': 'portainer_test_entry_123_dashy_dashy.1.qgza68hnz4n1qvyz3iohynx05_memory_limit', + 'unit_of_measurement': , + }) +# --- +# name: test_all_entities[sensor.dashy_dashy_1_qgza68hnz4n1qvyz3iohynx05_memory_limit-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'data_size', + 'friendly_name': 'dashy_dashy.1.qgza68hnz4n1qvyz3iohynx05 Memory limit', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.dashy_dashy_1_qgza68hnz4n1qvyz3iohynx05_memory_limit', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '67.108864', + }) +# --- +# name: test_all_entities[sensor.dashy_dashy_1_qgza68hnz4n1qvyz3iohynx05_memory_usage-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.dashy_dashy_1_qgza68hnz4n1qvyz3iohynx05_memory_usage', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Memory usage', + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 1, + }), + 'sensor.private': dict({ + 'suggested_unit_of_measurement': , + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Memory usage', + 'platform': 'portainer', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'memory_usage', + 'unique_id': 'portainer_test_entry_123_dashy_dashy.1.qgza68hnz4n1qvyz3iohynx05_memory_usage', + 'unit_of_measurement': , + }) +# --- +# name: test_all_entities[sensor.dashy_dashy_1_qgza68hnz4n1qvyz3iohynx05_memory_usage-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'data_size', + 'friendly_name': 'dashy_dashy.1.qgza68hnz4n1qvyz3iohynx05 Memory usage', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.dashy_dashy_1_qgza68hnz4n1qvyz3iohynx05_memory_usage', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '6.537216', + }) +# --- +# name: test_all_entities[sensor.dashy_dashy_1_qgza68hnz4n1qvyz3iohynx05_memory_usage_percentage-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.dashy_dashy_1_qgza68hnz4n1qvyz3iohynx05_memory_usage_percentage', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Memory usage percentage', + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 2, + }), + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Memory usage percentage', + 'platform': 'portainer', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'memory_usage_percentage', + 'unique_id': 'portainer_test_entry_123_dashy_dashy.1.qgza68hnz4n1qvyz3iohynx05_memory_usage_percentage', + 'unit_of_measurement': '%', + }) +# --- +# name: test_all_entities[sensor.dashy_dashy_1_qgza68hnz4n1qvyz3iohynx05_memory_usage_percentage-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'dashy_dashy.1.qgza68hnz4n1qvyz3iohynx05 Memory usage percentage', + 'state_class': , + 'unit_of_measurement': '%', + }), + 'context': , + 'entity_id': 'sensor.dashy_dashy_1_qgza68hnz4n1qvyz3iohynx05_memory_usage_percentage', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '9.7412109375', + }) +# --- +# name: test_all_entities[sensor.dashy_dashy_1_qgza68hnz4n1qvyz3iohynx05_state-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'options': list([ + 'running', + 'exited', + 'paused', + 'restarting', + 'created', + 'dead', + ]), + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.dashy_dashy_1_qgza68hnz4n1qvyz3iohynx05_state', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'State', + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'State', + 'platform': 'portainer', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'container_state', + 'unique_id': 'portainer_test_entry_123_dashy_dashy.1.qgza68hnz4n1qvyz3iohynx05_container_state', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[sensor.dashy_dashy_1_qgza68hnz4n1qvyz3iohynx05_state-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'enum', + 'friendly_name': 'dashy_dashy.1.qgza68hnz4n1qvyz3iohynx05 State', + 'options': list([ + 'running', + 'exited', + 'paused', + 'restarting', + 'created', + 'dead', + ]), + }), + 'context': , + 'entity_id': 'sensor.dashy_dashy_1_qgza68hnz4n1qvyz3iohynx05_state', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'running', + }) +# --- +# name: test_all_entities[sensor.dashy_type-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'options': list([ + 'swarm', + 'compose', + 'kubernetes', + ]), + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.dashy_type', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Type', + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Type', + 'platform': 'portainer', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'stack_type', + 'unique_id': 'portainer_test_entry_123_2_stack_type', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[sensor.dashy_type-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'enum', + 'friendly_name': 'dashy Type', + 'options': list([ + 'swarm', + 'compose', + 'kubernetes', + ]), + }), + 'context': , + 'entity_id': 'sensor.dashy_type', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'swarm', + }) +# --- # name: test_all_entities[sensor.focused_einstein_cpu_usage_total-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ diff --git a/tests/components/portainer/snapshots/test_switch.ambr b/tests/components/portainer/snapshots/test_switch.ambr index d2deca741ce..9d8f49d4ab5 100644 --- a/tests/components/portainer/snapshots/test_switch.ambr +++ b/tests/components/portainer/snapshots/test_switch.ambr @@ -1,4 +1,104 @@ # serializer version: 1 +# name: test_all_switch_entities_snapshot[switch.dashy_dashy_1_qgza68hnz4n1qvyz3iohynx05_container-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'switch', + 'entity_category': None, + 'entity_id': 'switch.dashy_dashy_1_qgza68hnz4n1qvyz3iohynx05_container', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Container', + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Container', + 'platform': 'portainer', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'container', + 'unique_id': 'portainer_test_entry_123_dashy_dashy.1.qgza68hnz4n1qvyz3iohynx05_container', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_switch_entities_snapshot[switch.dashy_dashy_1_qgza68hnz4n1qvyz3iohynx05_container-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'switch', + 'friendly_name': 'dashy_dashy.1.qgza68hnz4n1qvyz3iohynx05 Container', + }), + 'context': , + 'entity_id': 'switch.dashy_dashy_1_qgza68hnz4n1qvyz3iohynx05_container', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'on', + }) +# --- +# name: test_all_switch_entities_snapshot[switch.dashy_stack-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'switch', + 'entity_category': None, + 'entity_id': 'switch.dashy_stack', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Stack', + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Stack', + 'platform': 'portainer', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'stack', + 'unique_id': 'portainer_test_entry_123_2_stack', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_switch_entities_snapshot[switch.dashy_stack-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'switch', + 'friendly_name': 'dashy Stack', + }), + 'context': , + 'entity_id': 'switch.dashy_stack', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'on', + }) +# --- # name: test_all_switch_entities_snapshot[switch.focused_einstein_container-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ diff --git a/tests/components/portainer/test_init.py b/tests/components/portainer/test_init.py index ef1a6caa67c..b19595f4b02 100644 --- a/tests/components/portainer/test_init.py +++ b/tests/components/portainer/test_init.py @@ -183,3 +183,54 @@ async def test_device_registry( device_registry, mock_config_entry.entry_id ) assert device_entries == snapshot + + +async def test_container_stack_device_links( + hass: HomeAssistant, + mock_portainer_client: AsyncMock, + mock_config_entry: MockConfigEntry, + device_registry: dr.DeviceRegistry, +) -> None: + """Test that stack-linked containers are nested under the correct stack device.""" + await setup_integration(hass, mock_config_entry) + + endpoint_device = device_registry.async_get_device( + identifiers={(DOMAIN, f"{mock_config_entry.entry_id}_1")} + ) + assert endpoint_device is not None + + dashy_stack_device = device_registry.async_get_device( + identifiers={(DOMAIN, f"{mock_config_entry.entry_id}_1_stack_2")} + ) + assert dashy_stack_device is not None + assert dashy_stack_device.via_device_id == endpoint_device.id + + webstack_device = device_registry.async_get_device( + identifiers={(DOMAIN, f"{mock_config_entry.entry_id}_1_stack_1")} + ) + assert webstack_device is not None + assert webstack_device.via_device_id == endpoint_device.id + + swarm_container_device = device_registry.async_get_device( + identifiers={ + ( + DOMAIN, + f"{mock_config_entry.entry_id}_1_dashy_dashy.1.qgza68hnz4n1qvyz3iohynx05", + ) + } + ) + assert swarm_container_device is not None + assert swarm_container_device.via_device_id == dashy_stack_device.id + + compose_container_device = device_registry.async_get_device( + identifiers={(DOMAIN, f"{mock_config_entry.entry_id}_1_serene_banach")} + ) + assert compose_container_device is not None + assert compose_container_device.via_device_id == webstack_device.id + + standalone_container_device = device_registry.async_get_device( + identifiers={(DOMAIN, f"{mock_config_entry.entry_id}_1_focused_einstein")} + ) + + assert standalone_container_device is not None + assert standalone_container_device.via_device_id == endpoint_device.id