@camstack/addon-import-alexa 0.2.181 → 0.2.183

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-import-alexa",
3
- "version": "0.2.181",
3
+ "version": "0.2.183",
4
4
  "description": "Alexa device-import provider for CamStack — imports the smart-home devices in a user's Alexa account via the unofficial alexa-remote2 cookie/token client (the inverse of the Alexa exporter)",
5
5
  "keywords": [
6
6
  "camstack",
@@ -35,6 +35,7 @@
35
35
  "addons": [
36
36
  {
37
37
  "id": "import-alexa",
38
+ "integrationRole": "both",
38
39
  "category": "providers",
39
40
  "name": "Alexa",
40
41
  "version": "0.1.0",
@@ -52,6 +53,9 @@
52
53
  {
53
54
  "name": "broker"
54
55
  },
56
+ {
57
+ "name": "integration-instances"
58
+ },
55
59
  {
56
60
  "name": "device-adoption"
57
61
  },
@@ -68,7 +72,7 @@
68
72
  }
69
73
  ],
70
74
  "requires": {
71
- "server": ">=1.2.750"
75
+ "server": ">=1.2.761"
72
76
  }
73
77
  },
74
78
  "files": [
@@ -322,8 +322,20 @@ class AlexaPyGateway:
322
322
  if is_rate_limited(err):
323
323
  raise RateLimitedError("amazon rate limit (429) on the device volumes") from err
324
324
  raise
325
+ # `_static_request` hands back the raw response (None on status >= 400): parse it here.
326
+ if body is None:
327
+ raise RuntimeError("amazon returned no response for the device volume list (HTTP >= 400)")
328
+ status = getattr(body, "status", None)
329
+ try:
330
+ body = await body.json(content_type=None)
331
+ except Exception as err: # noqa: BLE001 — typed below
332
+ raise RuntimeError(
333
+ f"amazon returned an unexpected body for the device volume list (HTTP {status}, unparseable)"
334
+ ) from err
325
335
  if not isinstance(body, dict):
326
- raise RuntimeError("amazon returned no device volume list")
336
+ raise RuntimeError(
337
+ f"amazon returned an unexpected body for the device volume list (HTTP {status}, not an object)"
338
+ )
327
339
  rows = body.get("volumes")
328
340
  devices = []
329
341
  for row in rows if isinstance(rows, list) else []:
@@ -18,6 +18,20 @@ class AlexapyTooManyRequestsError(Exception):
18
18
  pass
19
19
 
20
20
 
21
+ class FakeResponse:
22
+ """What alexapy's `_static_request` really returns: an aiohttp-like response, parsed by the caller."""
23
+
24
+ def __init__(self, status, payload=None, bad_json=False):
25
+ self.status = status
26
+ self._payload = payload
27
+ self._bad_json = bad_json
28
+
29
+ async def json(self, content_type=None):
30
+ if self._bad_json:
31
+ raise ValueError("not json")
32
+ return self._payload
33
+
34
+
21
35
  def run(coro):
22
36
  return asyncio.run(coro)
23
37
 
@@ -143,13 +157,16 @@ class EchoMediaTests(unittest.TestCase):
143
157
 
144
158
  def test_device_volumes_is_one_call_keeping_only_typed_readings(self) -> None:
145
159
  gateway, calls = install_fake(
146
- volumes={
147
- "volumes": [
148
- {"dsn": "s1", "deviceType": "T1", "speakerVolume": 46, "speakerMuted": False},
149
- {"dsn": "s2", "speakerVolume": None, "speakerMuted": True},
150
- {"deviceType": "T1", "speakerVolume": 10},
151
- ]
152
- }
160
+ volumes=FakeResponse(
161
+ 200,
162
+ {
163
+ "volumes": [
164
+ {"dsn": "s1", "deviceType": "T1", "speakerVolume": 46, "speakerMuted": False},
165
+ {"dsn": "s2", "speakerVolume": None, "speakerMuted": True},
166
+ {"deviceType": "T1", "speakerVolume": 10},
167
+ ]
168
+ },
169
+ )
153
170
  )
154
171
  result = run(gateway.device_volumes())
155
172
  self.assertEqual(
@@ -160,6 +177,17 @@ class EchoMediaTests(unittest.TestCase):
160
177
  calls["static"], [("get", "/api/devices/deviceType/dsn/audio/v1/allDeviceVolumes")]
161
178
  )
162
179
 
180
+ def test_device_volumes_http_failure_is_told_apart_from_a_bad_body(self) -> None:
181
+ gateway, _ = install_fake(volumes=None)
182
+ with self.assertRaisesRegex(RuntimeError, "no response"):
183
+ run(gateway.device_volumes())
184
+ gateway, _ = install_fake(volumes=FakeResponse(200, ["not", "a", "dict"]))
185
+ with self.assertRaisesRegex(RuntimeError, "unexpected body"):
186
+ run(gateway.device_volumes())
187
+ gateway, _ = install_fake(volumes=FakeResponse(200, bad_json=True))
188
+ with self.assertRaisesRegex(RuntimeError, "unexpected body"):
189
+ run(gateway.device_volumes())
190
+
163
191
  def test_device_volumes_429_is_typed(self) -> None:
164
192
  gateway, _ = install_fake(raises=AlexapyTooManyRequestsError("Too Many Requests"))
165
193
  with self.assertRaises(RateLimitedError):