Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Changelog

## 0.1.2 - 2026-07-26

- Raised a structured `DataNetError` when an authentication response succeeds
without returning a usable JWT.

## 0.1.1 - 2026-07-17

- Added `get_presence(channel)` for authoritative occupancy and member lookups.
Expand Down
2 changes: 1 addition & 1 deletion datanet/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,4 @@
"build_art_dmx_packet",
"build_dmx_frame",
]
__version__ = "0.1.1"
__version__ = "0.1.2"
14 changes: 9 additions & 5 deletions datanet/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -953,11 +953,15 @@ async def _fetch_jwt(self) -> str:
code=code,
status=resp.status,
)
payload = await resp.json()
token = payload.get("token")
if not token:
raise RuntimeError(
f"DataNet auth failed: missing token in response {payload!r}"
body = await resp.json()
token = body.get("token") if isinstance(body, dict) else None
if not isinstance(token, str) or not token.strip():
detail = body.get("error") if isinstance(body, dict) else None
raise DataNetError(
f"DataNet auth failed ({resp.status}): "
f"{detail or 'response missing token'}",
code="authentication_failed",
status=resp.status,
)
logger.debug("DataNet: JWT acquired.")
return token
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "hatchling.build"

[project]
name = "datanet-sdk"
version = "0.1.1"
version = "0.1.2"
description = "DataNet Python SDK — async realtime pub/sub client for the DataNet platform"
readme = "README.md"
license = { text = "MIT" }
Expand Down
27 changes: 27 additions & 0 deletions tests/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
AnyMessage,
BinaryMessageMeta,
DataNet,
DataNetError,
MessageMeta,
binary_to_base64,
build_art_dmx_packet,
Expand Down Expand Up @@ -63,6 +64,17 @@ def post(self, url, **kwargs):
return FakePostResponse()


class FakeMissingTokenResponse(FakePostResponse):
async def json(self):
return {"error": "invalid apiKey"}


class FakeMissingTokenSession(FakeClientSession):
def post(self, url, **kwargs):
self.calls.append((url, kwargs))
return FakeMissingTokenResponse()


class FakePresenceResponse:
status = 200

Expand Down Expand Up @@ -393,6 +405,21 @@ async def test_auth_payload_includes_device_metadata(self):
},
)

async def test_auth_response_without_token_raises_structured_error(self):
client = DataNet("ak_invalid")

with patch.object(
client_module.aiohttp,
"ClientSession",
FakeMissingTokenSession,
):
with self.assertRaises(DataNetError) as raised:
await client._fetch_jwt()

self.assertEqual(raised.exception.code, "authentication_failed")
self.assertEqual(raised.exception.status, 200)
self.assertIn("invalid apiKey", str(raised.exception))

async def test_heartbeat_sends_hb_envelope(self):
client = DataNet("ak_test")
ws = FakeWebSocket()
Expand Down
Loading