Use async rest api in SamsungTV (#67369)

Co-authored-by: epenet <epenet@users.noreply.github.com>
This commit is contained in:
epenet 2022-02-28 20:53:42 +01:00 committed by GitHub
parent 508ed257d4
commit 1556868d56
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 121 additions and 133 deletions

View File

@ -2,13 +2,14 @@
from __future__ import annotations from __future__ import annotations
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from asyncio.exceptions import TimeoutError as AsyncioTimeoutError
import contextlib import contextlib
from typing import Any from typing import Any
from requests.exceptions import Timeout as RequestsTimeout
from samsungctl import Remote from samsungctl import Remote
from samsungctl.exceptions import AccessDenied, ConnectionClosed, UnhandledResponse from samsungctl.exceptions import AccessDenied, ConnectionClosed, UnhandledResponse
from samsungtvws import SamsungTVWS from samsungtvws import SamsungTVWS
from samsungtvws.async_rest import SamsungTVAsyncRest
from samsungtvws.exceptions import ConnectionFailure, HttpApiError from samsungtvws.exceptions import ConnectionFailure, HttpApiError
from websocket import WebSocketException from websocket import WebSocketException
@ -21,6 +22,7 @@ from homeassistant.const import (
CONF_TIMEOUT, CONF_TIMEOUT,
) )
from homeassistant.core import CALLBACK_TYPE, HomeAssistant from homeassistant.core import CALLBACK_TYPE, HomeAssistant
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.device_registry import format_mac from homeassistant.helpers.device_registry import format_mac
from .const import ( from .const import (
@ -294,6 +296,7 @@ class SamsungTVWSBridge(SamsungTVBridge):
"""Initialize Bridge.""" """Initialize Bridge."""
super().__init__(hass, method, host, port) super().__init__(hass, method, host, port)
self.token = token self.token = token
self._rest_api: SamsungTVAsyncRest | None = None
self._app_list: dict[str, str] | None = None self._app_list: dict[str, str] | None = None
self._remote: SamsungTVWS | None = None self._remote: SamsungTVWS | None = None
@ -375,14 +378,21 @@ class SamsungTVWSBridge(SamsungTVBridge):
async def async_device_info(self) -> dict[str, Any] | None: async def async_device_info(self) -> dict[str, Any] | None:
"""Try to gather infos of this TV.""" """Try to gather infos of this TV."""
return await self.hass.async_add_executor_job(self._device_info) if not self.port:
return None
def _device_info(self) -> dict[str, Any] | None: if self._rest_api is None:
"""Try to gather infos of this TV.""" self._rest_api = SamsungTVAsyncRest(
if remote := self._get_remote(avoid_open=True): host=self.host,
with contextlib.suppress(HttpApiError, RequestsTimeout): session=async_get_clientsession(self.hass),
device_info: dict[str, Any] = remote.rest_device_info() port=self.port,
return device_info timeout=TIMEOUT_WEBSOCKET,
)
with contextlib.suppress(HttpApiError, AsyncioTimeoutError):
device_info: dict[str, Any] = await self._rest_api.rest_device_info()
LOGGER.debug("Device info on %s is: %s", self.host, device_info)
return device_info
return None return None
@ -416,7 +426,7 @@ class SamsungTVWSBridge(SamsungTVBridge):
# Different reasons, e.g. hostname not resolveable # Different reasons, e.g. hostname not resolveable
pass pass
def _get_remote(self, avoid_open: bool = False) -> SamsungTVWS: def _get_remote(self) -> SamsungTVWS:
"""Create or return a remote control instance.""" """Create or return a remote control instance."""
if self._remote is None: if self._remote is None:
# We need to create a new instance to reconnect. # We need to create a new instance to reconnect.
@ -431,8 +441,7 @@ class SamsungTVWSBridge(SamsungTVBridge):
timeout=TIMEOUT_WEBSOCKET, timeout=TIMEOUT_WEBSOCKET,
name=VALUE_CONF_NAME, name=VALUE_CONF_NAME,
) )
if not avoid_open: self._remote.open()
self._remote.open()
# This is only happening when the auth was switched to DENY # This is only happening when the auth was switched to DENY
# A removed auth will lead to socket timeout because waiting for auth popup is just an open socket # A removed auth will lead to socket timeout because waiting for auth popup is just an open socket
except ConnectionFailure as err: except ConnectionFailure as err:

View File

@ -32,16 +32,14 @@ def remote_fixture() -> Mock:
yield remote yield remote
@pytest.fixture(name="remotews") @pytest.fixture(name="rest_api", autouse=True)
def remotews_fixture() -> Mock: def rest_api_fixture() -> Mock:
"""Patch the samsungtvws SamsungTVWS.""" """Patch the samsungtvws SamsungTVAsyncRest."""
with patch( with patch(
"homeassistant.components.samsungtv.bridge.SamsungTVWS" "homeassistant.components.samsungtv.bridge.SamsungTVAsyncRest",
) as remotews_class: autospec=True,
remotews = Mock(SamsungTVWS) ) as rest_api_class:
remotews.__enter__ = Mock(return_value=remotews) rest_api_class.return_value.rest_device_info.return_value = {
remotews.__exit__ = Mock()
remotews.rest_device_info.return_value = {
"id": "uuid:be9554b9-c9fb-41f4-8920-22da015376a4", "id": "uuid:be9554b9-c9fb-41f4-8920-22da015376a4",
"device": { "device": {
"modelName": "82GXARRS", "modelName": "82GXARRS",
@ -51,51 +49,24 @@ def remotews_fixture() -> Mock:
"networkType": "wireless", "networkType": "wireless",
}, },
} }
yield rest_api_class.return_value
@pytest.fixture(name="remotews")
def remotews_fixture() -> Mock:
"""Patch the samsungtvws SamsungTVWS."""
with patch(
"homeassistant.components.samsungtv.bridge.SamsungTVWS"
) as remotews_class:
remotews = Mock(SamsungTVWS)
remotews.__enter__ = Mock(return_value=remotews)
remotews.__exit__ = Mock()
remotews.app_list.return_value = SAMPLE_APP_LIST remotews.app_list.return_value = SAMPLE_APP_LIST
remotews.token = "FAKE_TOKEN" remotews.token = "FAKE_TOKEN"
remotews_class.return_value = remotews remotews_class.return_value = remotews
yield remotews yield remotews
@pytest.fixture(name="remotews_no_device_info")
def remotews_no_device_info_fixture() -> Mock:
"""Patch the samsungtvws SamsungTVWS."""
with patch(
"homeassistant.components.samsungtv.bridge.SamsungTVWS"
) as remotews_class:
remotews = Mock(SamsungTVWS)
remotews.__enter__ = Mock(return_value=remotews)
remotews.__exit__ = Mock()
remotews.rest_device_info.return_value = None
remotews.token = "FAKE_TOKEN"
remotews_class.return_value = remotews
yield remotews
@pytest.fixture(name="remotews_soundbar")
def remotews_soundbar_fixture() -> Mock:
"""Patch the samsungtvws SamsungTVWS."""
with patch(
"homeassistant.components.samsungtv.bridge.SamsungTVWS"
) as remotews_class:
remotews = Mock(SamsungTVWS)
remotews.__enter__ = Mock(return_value=remotews)
remotews.__exit__ = Mock()
remotews.rest_device_info.return_value = {
"id": "uuid:be9554b9-c9fb-41f4-8920-22da015376a4",
"device": {
"modelName": "82GXARRS",
"wifiMac": "aa:bb:cc:dd:ee:ff",
"mac": "aa:bb:cc:dd:ee:ff",
"name": "[TV] Living Room",
"type": "Samsung SoundBar",
},
}
remotews.token = "FAKE_TOKEN"
remotews_class.return_value = remotews
yield remotews
@pytest.fixture(name="delay") @pytest.fixture(name="delay")
def delay_fixture() -> Mock: def delay_fixture() -> Mock:
"""Patch the delay script function.""" """Patch the delay script function."""

View File

@ -1,6 +1,6 @@
"""Tests for Samsung TV config flow.""" """Tests for Samsung TV config flow."""
import socket import socket
from unittest.mock import Mock, call, patch from unittest.mock import ANY, AsyncMock, Mock, call, patch
import pytest import pytest
from samsungctl.exceptions import AccessDenied, UnhandledResponse from samsungctl.exceptions import AccessDenied, UnhandledResponse
@ -178,10 +178,9 @@ AUTODETECT_WEBSOCKET_SSL = {
} }
DEVICEINFO_WEBSOCKET_SSL = { DEVICEINFO_WEBSOCKET_SSL = {
"host": "fake_host", "host": "fake_host",
"name": "HomeAssistant", "session": ANY,
"port": 8002, "port": 8002,
"timeout": TIMEOUT_WEBSOCKET, "timeout": TIMEOUT_WEBSOCKET,
"token": "123456789",
} }
@ -456,8 +455,11 @@ async def test_ssdp_websocket_success_populates_mac_address(
assert result["result"].unique_id == "0d1cef00-00dc-1000-9c80-4844f7b172de" assert result["result"].unique_id == "0d1cef00-00dc-1000-9c80-4844f7b172de"
async def test_ssdp_websocket_not_supported(hass: HomeAssistant) -> None: async def test_ssdp_websocket_not_supported(
hass: HomeAssistant, rest_api: Mock
) -> None:
"""Test starting a flow from discovery for not supported device.""" """Test starting a flow from discovery for not supported device."""
rest_api.rest_device_info.return_value = None
with patch( with patch(
"homeassistant.components.samsungtv.bridge.Remote", "homeassistant.components.samsungtv.bridge.Remote",
side_effect=OSError("Boom"), side_effect=OSError("Boom"),
@ -630,9 +632,10 @@ async def test_import_legacy(hass: HomeAssistant, no_mac_address: Mock) -> None:
assert entries[0].data[CONF_PORT] == LEGACY_PORT assert entries[0].data[CONF_PORT] == LEGACY_PORT
@pytest.mark.usefixtures("remote", "remotews_no_device_info", "no_mac_address") @pytest.mark.usefixtures("remote", "remotews", "no_mac_address")
async def test_import_legacy_without_name(hass: HomeAssistant) -> None: async def test_import_legacy_without_name(hass: HomeAssistant, rest_api: Mock) -> None:
"""Test importing from yaml without a name.""" """Test importing from yaml without a name."""
rest_api.rest_device_info.return_value = None
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
DOMAIN, DOMAIN,
context={"source": config_entries.SOURCE_IMPORT}, context={"source": config_entries.SOURCE_IMPORT},
@ -762,9 +765,19 @@ async def test_zeroconf(hass: HomeAssistant) -> None:
assert result["result"].unique_id == "be9554b9-c9fb-41f4-8920-22da015376a4" assert result["result"].unique_id == "be9554b9-c9fb-41f4-8920-22da015376a4"
@pytest.mark.usefixtures("remotews_soundbar") @pytest.mark.usefixtures("remotews")
async def test_zeroconf_ignores_soundbar(hass: HomeAssistant) -> None: async def test_zeroconf_ignores_soundbar(hass: HomeAssistant, rest_api: Mock) -> None:
"""Test starting a flow from zeroconf where the device is actually a soundbar.""" """Test starting a flow from zeroconf where the device is actually a soundbar."""
rest_api.rest_device_info.return_value = {
"id": "uuid:be9554b9-c9fb-41f4-8920-22da015376a4",
"device": {
"modelName": "82GXARRS",
"wifiMac": "aa:bb:cc:dd:ee:ff",
"mac": "aa:bb:cc:dd:ee:ff",
"name": "[TV] Living Room",
"type": "Samsung SoundBar",
},
}
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
DOMAIN, DOMAIN,
context={"source": config_entries.SOURCE_ZEROCONF}, context={"source": config_entries.SOURCE_ZEROCONF},
@ -775,9 +788,10 @@ async def test_zeroconf_ignores_soundbar(hass: HomeAssistant) -> None:
assert result["reason"] == "not_supported" assert result["reason"] == "not_supported"
@pytest.mark.usefixtures("remote", "remotews_no_device_info") @pytest.mark.usefixtures("remote", "remotews")
async def test_zeroconf_no_device_info(hass: HomeAssistant) -> None: async def test_zeroconf_no_device_info(hass: HomeAssistant, rest_api: Mock) -> None:
"""Test starting a flow from zeroconf where device_info returns None.""" """Test starting a flow from zeroconf where device_info returns None."""
rest_api.rest_device_info.return_value = None
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
DOMAIN, DOMAIN,
context={"source": config_entries.SOURCE_ZEROCONF}, context={"source": config_entries.SOURCE_ZEROCONF},
@ -815,23 +829,29 @@ async def test_autodetect_websocket(hass: HomeAssistant) -> None:
with patch( with patch(
"homeassistant.components.samsungtv.bridge.Remote", "homeassistant.components.samsungtv.bridge.Remote",
side_effect=OSError("Boom"), side_effect=OSError("Boom"),
), patch("homeassistant.components.samsungtv.bridge.SamsungTVWS") as remotews: ), patch(
"homeassistant.components.samsungtv.bridge.SamsungTVWS"
) as remotews, patch(
"homeassistant.components.samsungtv.bridge.SamsungTVAsyncRest",
) as rest_api_class:
remote = Mock(SamsungTVWS) remote = Mock(SamsungTVWS)
remote.__enter__ = Mock(return_value=remote) remote.__enter__ = Mock(return_value=remote)
remote.__exit__ = Mock(return_value=False) remote.__exit__ = Mock(return_value=False)
remote.app_list.return_value = SAMPLE_APP_LIST remote.app_list.return_value = SAMPLE_APP_LIST
remote.rest_device_info.return_value = { rest_api_class.return_value.rest_device_info = AsyncMock(
"id": "uuid:be9554b9-c9fb-41f4-8920-22da015376a4", return_value={
"device": { "id": "uuid:be9554b9-c9fb-41f4-8920-22da015376a4",
"modelName": "82GXARRS", "device": {
"networkType": "wireless", "modelName": "82GXARRS",
"wifiMac": "aa:bb:cc:dd:ee:ff", "networkType": "wireless",
"udn": "uuid:be9554b9-c9fb-41f4-8920-22da015376a4", "wifiMac": "aa:bb:cc:dd:ee:ff",
"mac": "aa:bb:cc:dd:ee:ff", "udn": "uuid:be9554b9-c9fb-41f4-8920-22da015376a4",
"name": "[TV] Living Room", "mac": "aa:bb:cc:dd:ee:ff",
"type": "Samsung SmartTV", "name": "[TV] Living Room",
}, "type": "Samsung SmartTV",
} },
}
)
remote.token = "123456789" remote.token = "123456789"
remotews.return_value = remote remotews.return_value = remote
@ -841,11 +861,8 @@ async def test_autodetect_websocket(hass: HomeAssistant) -> None:
assert result["type"] == "create_entry" assert result["type"] == "create_entry"
assert result["data"][CONF_METHOD] == "websocket" assert result["data"][CONF_METHOD] == "websocket"
assert result["data"][CONF_TOKEN] == "123456789" assert result["data"][CONF_TOKEN] == "123456789"
assert remotews.call_count == 2 remotews.assert_called_once_with(**AUTODETECT_WEBSOCKET_SSL)
assert remotews.call_args_list == [ rest_api_class.assert_called_once_with(**DEVICEINFO_WEBSOCKET_SSL)
call(**AUTODETECT_WEBSOCKET_SSL),
call(**DEVICEINFO_WEBSOCKET_SSL),
]
await hass.async_block_till_done() await hass.async_block_till_done()
entries = hass.config_entries.async_entries(DOMAIN) entries = hass.config_entries.async_entries(DOMAIN)
@ -861,22 +878,27 @@ async def test_websocket_no_mac(hass: HomeAssistant) -> None:
), patch( ), patch(
"homeassistant.components.samsungtv.bridge.SamsungTVWS" "homeassistant.components.samsungtv.bridge.SamsungTVWS"
) as remotews, patch( ) as remotews, patch(
"homeassistant.components.samsungtv.bridge.SamsungTVAsyncRest",
) as rest_api_class, patch(
"getmac.get_mac_address", return_value="gg:hh:ii:ll:mm:nn" "getmac.get_mac_address", return_value="gg:hh:ii:ll:mm:nn"
): ):
remote = Mock(SamsungTVWS) remote = Mock(SamsungTVWS)
remote.__enter__ = Mock(return_value=remote) remote.__enter__ = Mock(return_value=remote)
remote.__exit__ = Mock(return_value=False) remote.__exit__ = Mock(return_value=False)
remote.app_list.return_value = SAMPLE_APP_LIST remote.app_list.return_value = SAMPLE_APP_LIST
remote.rest_device_info.return_value = { rest_api_class.return_value.rest_device_info = AsyncMock(
"id": "uuid:be9554b9-c9fb-41f4-8920-22da015376a4", return_value={
"device": { "id": "uuid:be9554b9-c9fb-41f4-8920-22da015376a4",
"modelName": "82GXARRS", "device": {
"networkType": "lan", "modelName": "82GXARRS",
"udn": "uuid:be9554b9-c9fb-41f4-8920-22da015376a4", "networkType": "lan",
"name": "[TV] Living Room", "udn": "uuid:be9554b9-c9fb-41f4-8920-22da015376a4",
"type": "Samsung SmartTV", "name": "[TV] Living Room",
}, "type": "Samsung SmartTV",
} },
}
)
remote.token = "123456789" remote.token = "123456789"
remotews.return_value = remote remotews.return_value = remote
@ -887,11 +909,8 @@ async def test_websocket_no_mac(hass: HomeAssistant) -> None:
assert result["data"][CONF_METHOD] == "websocket" assert result["data"][CONF_METHOD] == "websocket"
assert result["data"][CONF_TOKEN] == "123456789" assert result["data"][CONF_TOKEN] == "123456789"
assert result["data"][CONF_MAC] == "gg:hh:ii:ll:mm:nn" assert result["data"][CONF_MAC] == "gg:hh:ii:ll:mm:nn"
assert remotews.call_count == 2 remotews.assert_called_once_with(**AUTODETECT_WEBSOCKET_SSL)
assert remotews.call_args_list == [ rest_api_class.assert_called_once_with(**DEVICEINFO_WEBSOCKET_SSL)
call(**AUTODETECT_WEBSOCKET_SSL),
call(**DEVICEINFO_WEBSOCKET_SSL),
]
await hass.async_block_till_done() await hass.async_block_till_done()
entries = hass.config_entries.async_entries(DOMAIN) entries = hass.config_entries.async_entries(DOMAIN)
@ -1166,18 +1185,16 @@ async def test_update_legacy_missing_mac_from_dhcp(hass: HomeAssistant) -> None:
@pytest.mark.usefixtures("remote") @pytest.mark.usefixtures("remote")
async def test_update_legacy_missing_mac_from_dhcp_no_unique_id( async def test_update_legacy_missing_mac_from_dhcp_no_unique_id(
hass: HomeAssistant, hass: HomeAssistant, rest_api: Mock
) -> None: ) -> None:
"""Test missing mac added when there is no unique id.""" """Test missing mac added when there is no unique id."""
rest_api.rest_device_info.side_effect = HttpApiError
entry = MockConfigEntry( entry = MockConfigEntry(
domain=DOMAIN, domain=DOMAIN,
data=MOCK_LEGACY_ENTRY, data=MOCK_LEGACY_ENTRY,
) )
entry.add_to_hass(hass) entry.add_to_hass(hass)
with patch( with patch(
"homeassistant.components.samsungtv.bridge.SamsungTVWS.rest_device_info",
side_effect=HttpApiError,
), patch(
"homeassistant.components.samsungtv.bridge.Remote.__enter__", "homeassistant.components.samsungtv.bridge.Remote.__enter__",
return_value=True, return_value=True,
), patch( ), patch(

View File

@ -159,31 +159,20 @@ async def test_setup_without_turnon(hass: HomeAssistant) -> None:
@pytest.mark.usefixtures("remotews") @pytest.mark.usefixtures("remotews")
async def test_setup_websocket(hass: HomeAssistant) -> None: async def test_setup_websocket(hass: HomeAssistant) -> None:
"""Test setup of platform.""" """Test setup of platform."""
with patch("homeassistant.components.samsungtv.bridge.SamsungTVWS") as remote_class: with patch("homeassistant.components.samsungtv.bridge.SamsungTVWS") as remote_class:
remote = Mock(SamsungTVWS) remote = Mock(SamsungTVWS)
remote.__enter__ = Mock(return_value=remote) remote.__enter__ = Mock(return_value=remote)
remote.__exit__ = Mock() remote.__exit__ = Mock()
remote.app_list.return_value = SAMPLE_APP_LIST remote.app_list.return_value = SAMPLE_APP_LIST
remote.rest_device_info.return_value = {
"id": "uuid:be9554b9-c9fb-41f4-8920-22da015376a4",
"device": {
"modelName": "82GXARRS",
"wifiMac": "aa:bb:cc:dd:ee:ff",
"name": "[TV] Living Room",
"type": "Samsung SmartTV",
"networkType": "wireless",
},
}
remote.token = "123456789" remote.token = "123456789"
remote_class.return_value = remote remote_class.return_value = remote
await setup_samsungtv(hass, MOCK_CONFIGWS) await setup_samsungtv(hass, MOCK_CONFIGWS)
assert remote_class.call_count == 2 assert remote_class.call_count == 1
assert remote_class.call_args_list == [ assert remote_class.call_args_list == [call(**MOCK_CALLS_WS)]
call(**MOCK_CALLS_WS),
call(**MOCK_CALLS_WS),
]
assert hass.states.get(ENTITY_ID) assert hass.states.get(ENTITY_ID)
await hass.async_block_till_done() await hass.async_block_till_done()
@ -193,7 +182,9 @@ async def test_setup_websocket(hass: HomeAssistant) -> None:
assert config_entries[0].data[CONF_MAC] == "aa:bb:cc:dd:ee:ff" assert config_entries[0].data[CONF_MAC] == "aa:bb:cc:dd:ee:ff"
async def test_setup_websocket_2(hass: HomeAssistant, mock_now: datetime) -> None: async def test_setup_websocket_2(
hass: HomeAssistant, mock_now: datetime, rest_api: Mock
) -> None:
"""Test setup of platform from config entry.""" """Test setup of platform from config entry."""
entity_id = f"{DOMAIN}.fake" entity_id = f"{DOMAIN}.fake"
@ -208,21 +199,21 @@ async def test_setup_websocket_2(hass: HomeAssistant, mock_now: datetime) -> Non
assert len(config_entries) == 1 assert len(config_entries) == 1
assert entry is config_entries[0] assert entry is config_entries[0]
rest_api.rest_device_info.return_value = {
"id": "uuid:be9554b9-c9fb-41f4-8920-22da015376a4",
"device": {
"modelName": "82GXARRS",
"wifiMac": "aa:bb:cc:dd:ee:ff",
"name": "[TV] Living Room",
"type": "Samsung SmartTV",
"networkType": "wireless",
},
}
with patch("homeassistant.components.samsungtv.bridge.SamsungTVWS") as remote_class: with patch("homeassistant.components.samsungtv.bridge.SamsungTVWS") as remote_class:
remote = Mock(SamsungTVWS) remote = Mock(SamsungTVWS)
remote.__enter__ = Mock(return_value=remote) remote.__enter__ = Mock(return_value=remote)
remote.__exit__ = Mock() remote.__exit__ = Mock()
remote.app_list.return_value = SAMPLE_APP_LIST remote.app_list.return_value = SAMPLE_APP_LIST
remote.rest_device_info.return_value = {
"id": "uuid:be9554b9-c9fb-41f4-8920-22da015376a4",
"device": {
"modelName": "82GXARRS",
"wifiMac": "aa:bb:cc:dd:ee:ff",
"name": "[TV] Living Room",
"type": "Samsung SmartTV",
"networkType": "wireless",
},
}
remote.token = "987654321" remote.token = "987654321"
remote_class.return_value = remote remote_class.return_value = remote
assert await async_setup_component(hass, SAMSUNGTV_DOMAIN, {}) assert await async_setup_component(hass, SAMSUNGTV_DOMAIN, {})
@ -237,7 +228,7 @@ async def test_setup_websocket_2(hass: HomeAssistant, mock_now: datetime) -> Non
state = hass.states.get(entity_id) state = hass.states.get(entity_id)
assert state assert state
assert remote_class.call_count == 3 assert remote_class.call_count == 2
assert remote_class.call_args_list[0] == call(**MOCK_CALLS_WS) assert remote_class.call_args_list[0] == call(**MOCK_CALLS_WS)