From 24df84f80951d4cbf1ce08d7d38213fbdbb5f1d0 Mon Sep 17 00:00:00 2001 From: Emmanuel Levijarvi Date: Sun, 25 Jan 2026 13:41:27 -0800 Subject: [PATCH 1/6] fix: Remove stale token check from NavienMqttClient.__init__() The strict token validity check in __init__() prevents creating MQTT clients with restored tokens that may have expired between application restarts. However, NavienMqttClient.connect() already handles token refresh automatically, making the check in __init__() redundant and overly restrictive. This change: - Removes the has_valid_tokens check from __init__() - Relies on connect() to validate and refresh tokens when needed - Allows integrations to create MQTT clients with expired tokens - Enables proper handling of restored authentication sessions - Simplifies integration code by removing duplicate token refresh calls Fixes: MQTT connection failures when using stored tokens across restarts --- src/nwp500/mqtt/client.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/nwp500/mqtt/client.py b/src/nwp500/mqtt/client.py index b09c22c..bdf316b 100644 --- a/src/nwp500/mqtt/client.py +++ b/src/nwp500/mqtt/client.py @@ -150,8 +150,8 @@ def __init__( - None: Auto-detect from device (default) Raises: - MqttCredentialsError: If auth client is not authenticated, tokens - are stale/expired, or AWS credentials are not available + MqttCredentialsError: If auth client is not authenticated or AWS + credentials are not available """ if not auth_client.is_authenticated: raise MqttCredentialsError( @@ -159,11 +159,9 @@ def __init__( "creating MQTT client. Call auth_client.sign_in() first." ) - if not auth_client.has_valid_tokens: - raise MqttCredentialsError( - "Tokens are stale/expired. " - "Call ensure_valid_token() or re_authenticate() first." - ) + # Note: Token validity is checked in connect() which also refreshes + # stale tokens automatically. This allows creating MQTT clients with + # restored tokens that may have expired between sessions. if not auth_client.current_tokens: raise MqttCredentialsError("No tokens available from auth client") From 19a5e1dea686681fa1328c58c0a9dc18ae6839ca Mon Sep 17 00:00:00 2001 From: Emmanuel Levijarvi Date: Sun, 25 Jan 2026 13:43:51 -0800 Subject: [PATCH 2/6] test: Update MQTT client init tests for removed token validity check Tests now verify that MQTT clients can be created with expired tokens, since token validation and refresh has moved to connect(). --- tests/test_mqtt_client_init.py | 65 +++++++++++++++++----------------- 1 file changed, 32 insertions(+), 33 deletions(-) diff --git a/tests/test_mqtt_client_init.py b/tests/test_mqtt_client_init.py index e17d5c2..4a5991e 100644 --- a/tests/test_mqtt_client_init.py +++ b/tests/test_mqtt_client_init.py @@ -108,44 +108,40 @@ def test_mqtt_client_init_rejects_not_authenticated( assert "must be authenticated" in str(exc_info.value).lower() - def test_mqtt_client_init_rejects_expired_jwt( + def test_mqtt_client_init_accepts_expired_jwt( self, auth_client_with_expired_jwt ): - """Test MQTT client rejects auth client with expired JWT tokens.""" - with pytest.raises(MqttCredentialsError) as exc_info: - NavienMqttClient(auth_client_with_expired_jwt) - - error_msg = str(exc_info.value).lower() - assert "stale/expired" in error_msg - assert ( - "ensure_valid_token" in error_msg or "re_authenticate" in error_msg - ) + """Test MQTT client can be created with expired JWT tokens. + + Token refresh happens in connect(), not in __init__(). + """ + # Should not raise - token refresh happens during connect() + mqtt_client = NavienMqttClient(auth_client_with_expired_jwt) + assert mqtt_client is not None + assert not mqtt_client.is_connected - def test_mqtt_client_init_rejects_expired_aws_credentials( + def test_mqtt_client_init_accepts_expired_aws_credentials( self, auth_client_with_expired_aws_credentials ): - """Test MQTT client rejects auth client with expired AWS credentials.""" - with pytest.raises(MqttCredentialsError) as exc_info: - NavienMqttClient(auth_client_with_expired_aws_credentials) - - error_msg = str(exc_info.value).lower() - assert "stale/expired" in error_msg - assert ( - "ensure_valid_token" in error_msg or "re_authenticate" in error_msg - ) + """Test MQTT client can be created with expired AWS credentials. + + Token refresh happens in connect(), not in __init__(). + """ + # Should not raise - token refresh happens during connect() + mqtt_client = NavienMqttClient(auth_client_with_expired_aws_credentials) + assert mqtt_client is not None + assert not mqtt_client.is_connected - def test_mqtt_client_init_error_message_guidance( + def test_mqtt_client_init_no_error_message_for_expired_tokens( self, auth_client_with_expired_jwt ): - """Test MQTT client init error provides clear guidance on recovery.""" - with pytest.raises(MqttCredentialsError) as exc_info: - NavienMqttClient(auth_client_with_expired_jwt) - - error_msg = str(exc_info.value) - # Should mention recovery methods - assert ( - "ensure_valid_token" in error_msg or "re_authenticate" in error_msg - ), f"Error message should mention recovery methods: {error_msg}" + """Test MQTT client init does not reject expired tokens. + + Token validation moved to connect() which handles refresh automatically. + """ + # Should not raise any error about stale/expired tokens + mqtt_client = NavienMqttClient(auth_client_with_expired_jwt) + assert mqtt_client is not None class TestHasValidTokensProperty: @@ -198,9 +194,12 @@ def test_has_valid_tokens_true_with_no_aws_expiration(self): def test_has_valid_tokens_integration_with_mqtt_init( self, auth_client_with_valid_tokens ): - """Test that has_valid_tokens integrates correctly with MQTT init.""" - # When has_valid_tokens is True, MQTT init should succeed - assert auth_client_with_valid_tokens.has_valid_tokens is True + """Test that MQTT client works with valid tokens. + + MQTT init no longer checks token validity - that happens in connect(). + """ + # MQTT init should succeed regardless of token validity + # Token validation is deferred to connect() mqtt_client = NavienMqttClient(auth_client_with_valid_tokens) assert mqtt_client is not None From 14bded7cff8cc2ea85adba9fd4b9653a291782ff Mon Sep 17 00:00:00 2001 From: Emmanuel Levijarvi Date: Sun, 25 Jan 2026 13:52:41 -0800 Subject: [PATCH 3/6] test: Fix test_expired_jwt_near_expiry_buffer for removed token check Updated test to reflect that MQTT init no longer rejects expired tokens. Token validation has been moved to connect() method. --- tests/test_mqtt_client_init.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/tests/test_mqtt_client_init.py b/tests/test_mqtt_client_init.py index 4a5991e..bd57e2b 100644 --- a/tests/test_mqtt_client_init.py +++ b/tests/test_mqtt_client_init.py @@ -474,7 +474,10 @@ class TestTokenValidationEdgeCases: """Test edge cases in token validation.""" def test_expired_jwt_near_expiry_buffer(self): - """Test token considered expired within 5-minute buffer.""" + """Test token considered expired within 5-minute buffer. + + MQTT init no longer rejects expired tokens - validation happens in connect(). + """ auth_client = NavienAuthClient("test@example.com", "password") # Token expires in 3 minutes - should be considered expired near_expiry = datetime.now() - timedelta(seconds=3420) @@ -496,11 +499,10 @@ def test_expired_jwt_near_expiry_buffer(self): assert tokens.is_expired is True assert auth_client.has_valid_tokens is False - # MQTT init should reject it - with pytest.raises(MqttCredentialsError) as exc_info: - NavienMqttClient(auth_client) - - assert "stale/expired" in str(exc_info.value).lower() + # MQTT init should NOT reject it - token validation moved to connect() + mqtt_client = NavienMqttClient(auth_client) + assert mqtt_client is not None + assert not mqtt_client.is_connected def test_multiple_validation_checks_mqtt_init( self, auth_client_with_valid_tokens From 6de6bf309a48ddc5574eb72c5451cdc6f064594a Mon Sep 17 00:00:00 2001 From: Emmanuel Levijarvi Date: Sun, 25 Jan 2026 13:58:43 -0800 Subject: [PATCH 4/6] test: Fix line length lint issues - Break docstring on line 479 to stay within 80 char limit - Break comment on line 502 to stay within 80 char limit --- tests/test_mqtt_client_init.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/test_mqtt_client_init.py b/tests/test_mqtt_client_init.py index bd57e2b..7730aa3 100644 --- a/tests/test_mqtt_client_init.py +++ b/tests/test_mqtt_client_init.py @@ -476,7 +476,8 @@ class TestTokenValidationEdgeCases: def test_expired_jwt_near_expiry_buffer(self): """Test token considered expired within 5-minute buffer. - MQTT init no longer rejects expired tokens - validation happens in connect(). + MQTT init no longer rejects expired tokens since validation + happens in connect(). """ auth_client = NavienAuthClient("test@example.com", "password") # Token expires in 3 minutes - should be considered expired @@ -499,7 +500,8 @@ def test_expired_jwt_near_expiry_buffer(self): assert tokens.is_expired is True assert auth_client.has_valid_tokens is False - # MQTT init should NOT reject it - token validation moved to connect() + # MQTT init should NOT reject it - token validation moved to + # connect() mqtt_client = NavienMqttClient(auth_client) assert mqtt_client is not None assert not mqtt_client.is_connected From c487268bd572edf3fa1fe7a1604defa49f4e62be Mon Sep 17 00:00:00 2001 From: Emmanuel Levijarvi Date: Sun, 25 Jan 2026 14:01:35 -0800 Subject: [PATCH 5/6] test: Remove whitespace from blank lines in docstrings Fixes W293 lint warnings - blank lines in docstrings had trailing whitespace. --- tests/test_mqtt_client_init.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/test_mqtt_client_init.py b/tests/test_mqtt_client_init.py index 7730aa3..f62f554 100644 --- a/tests/test_mqtt_client_init.py +++ b/tests/test_mqtt_client_init.py @@ -112,7 +112,7 @@ def test_mqtt_client_init_accepts_expired_jwt( self, auth_client_with_expired_jwt ): """Test MQTT client can be created with expired JWT tokens. - + Token refresh happens in connect(), not in __init__(). """ # Should not raise - token refresh happens during connect() @@ -124,7 +124,7 @@ def test_mqtt_client_init_accepts_expired_aws_credentials( self, auth_client_with_expired_aws_credentials ): """Test MQTT client can be created with expired AWS credentials. - + Token refresh happens in connect(), not in __init__(). """ # Should not raise - token refresh happens during connect() @@ -136,7 +136,7 @@ def test_mqtt_client_init_no_error_message_for_expired_tokens( self, auth_client_with_expired_jwt ): """Test MQTT client init does not reject expired tokens. - + Token validation moved to connect() which handles refresh automatically. """ # Should not raise any error about stale/expired tokens @@ -195,7 +195,7 @@ def test_has_valid_tokens_integration_with_mqtt_init( self, auth_client_with_valid_tokens ): """Test that MQTT client works with valid tokens. - + MQTT init no longer checks token validity - that happens in connect(). """ # MQTT init should succeed regardless of token validity @@ -475,7 +475,7 @@ class TestTokenValidationEdgeCases: def test_expired_jwt_near_expiry_buffer(self): """Test token considered expired within 5-minute buffer. - + MQTT init no longer rejects expired tokens since validation happens in connect(). """ From ba5f6483b75e2abe6bb01ee7732a9123fb778464 Mon Sep 17 00:00:00 2001 From: Emmanuel Levijarvi Date: Sun, 25 Jan 2026 14:08:55 -0800 Subject: [PATCH 6/6] Address PR review comments - Expand comment in client.py to explain behavior when connect() is not called - Rename test_mqtt_client_init_no_error_message_for_expired_tokens to test_mqtt_client_init_accepts_expired_tokens_without_error for clarity - Rename test_has_valid_tokens_integration_with_mqtt_init to test_mqtt_client_creation_succeeds_with_valid_tokens with improved docstring --- src/nwp500/mqtt/client.py | 9 ++++++--- tests/test_mqtt_client_init.py | 11 ++++++----- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/nwp500/mqtt/client.py b/src/nwp500/mqtt/client.py index bdf316b..fa9d725 100644 --- a/src/nwp500/mqtt/client.py +++ b/src/nwp500/mqtt/client.py @@ -159,9 +159,12 @@ def __init__( "creating MQTT client. Call auth_client.sign_in() first." ) - # Note: Token validity is checked in connect() which also refreshes - # stale tokens automatically. This allows creating MQTT clients with - # restored tokens that may have expired between sessions. + # Token validity is checked in connect() which also refreshes stale + # tokens automatically. This allows creating MQTT clients with + # restored tokens that may have expired between sessions. Token + # validation and refresh are deferred until connect() is called; if + # connect() is never called, tokens are not revalidated/refreshed + # and no MQTT connection is established. if not auth_client.current_tokens: raise MqttCredentialsError("No tokens available from auth client") diff --git a/tests/test_mqtt_client_init.py b/tests/test_mqtt_client_init.py index f62f554..724e88b 100644 --- a/tests/test_mqtt_client_init.py +++ b/tests/test_mqtt_client_init.py @@ -132,10 +132,10 @@ def test_mqtt_client_init_accepts_expired_aws_credentials( assert mqtt_client is not None assert not mqtt_client.is_connected - def test_mqtt_client_init_no_error_message_for_expired_tokens( + def test_mqtt_client_init_accepts_expired_tokens_without_error( self, auth_client_with_expired_jwt ): - """Test MQTT client init does not reject expired tokens. + """Test MQTT client accepts expired tokens without error. Token validation moved to connect() which handles refresh automatically. """ @@ -191,12 +191,13 @@ def test_has_valid_tokens_true_with_no_aws_expiration(self): # Should be True: JWT valid and AWS credentials have no expiration assert auth_client.has_valid_tokens is True - def test_has_valid_tokens_integration_with_mqtt_init( + def test_mqtt_client_creation_succeeds_with_valid_tokens( self, auth_client_with_valid_tokens ): - """Test that MQTT client works with valid tokens. + """Test that MQTT client creation succeeds with valid tokens. - MQTT init no longer checks token validity - that happens in connect(). + Verifies MQTT client can be created successfully when auth_client has + valid tokens. Token validation is deferred to connect(). """ # MQTT init should succeed regardless of token validity # Token validation is deferred to connect()