power-compare/app/isolarcloud/tests/test_shared_token_store.py
dlawler489 1dbfe3007b
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 24s
Share one iSolarCloud OAuth token across both household instances
Sungrow's OAuth server keeps only one active token session per (app,
account), not per plant -- with Kallangur and McDowall each managing their
own independent token against the same account, whichever one refreshed
last was silently invalidating the other's refresh token. Both instances
now share one token file (a common bind-mounted volume) instead of each
holding their own.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-13 08:04:30 +10:00

67 lines
2.7 KiB
Python

import time
import pytest
from app.config import settings
from app.isolarcloud import shared_token_store
from app.isolarcloud.oauth_client import ISolarCloudOAuthClient
@pytest.fixture
def shared_path(tmp_path, monkeypatch):
path = tmp_path / "isolarcloud_oauth.json"
monkeypatch.setattr(settings, "isolarcloud_shared_token_path", str(path))
return path
def test_disabled_by_default(monkeypatch):
monkeypatch.setattr(settings, "isolarcloud_shared_token_path", "")
assert shared_token_store.enabled() is False
def test_read_write_roundtrip(shared_path):
assert shared_token_store.enabled() is True
with shared_token_store.locked() as fd:
assert shared_token_store.read(fd) is None # file just created, empty
shared_token_store.write(fd, {"access_token": "a", "refresh_token": "r", "expires_at": 123})
with shared_token_store.locked() as fd:
assert shared_token_store.read(fd) == {"access_token": "a", "refresh_token": "r", "expires_at": 123}
def test_second_instance_reuses_token_written_by_first(shared_path):
"""The whole point: two ISolarCloudOAuthClient instances (standing in
for two separate Docker containers) pointed at the same shared path
see each other's tokens rather than needing their own independent
authorization."""
client_a = ISolarCloudOAuthClient()
client_a._store_tokens({"access_token": "tok-a", "refresh_token": "ref-a", "expires_in": 3600})
client_b = ISolarCloudOAuthClient()
# Not expired yet, so this must return client_a's token without
# attempting any HTTP refresh call.
client_b._post = lambda *a, **k: pytest.fail("should not need to refresh a still-valid token")
assert client_b._get_access_token() == "tok-a"
def test_expired_token_refreshes_once_and_shares_result(shared_path):
client_a = ISolarCloudOAuthClient()
with shared_token_store.locked() as fd:
shared_token_store.write(fd, {"access_token": "stale", "refresh_token": "ref-old", "expires_at": int(time.time()) - 10})
refresh_calls = []
def fake_post(path, payload, access_token=None):
refresh_calls.append(payload)
return {"result_data": {"access_token": "fresh", "refresh_token": "ref-new", "expires_in": 3600}}
client_a._post = fake_post
assert client_a._get_access_token() == "fresh"
assert len(refresh_calls) == 1
assert refresh_calls[0] == {"refresh_token": "ref-old"}
# A second instance reads the already-refreshed token and doesn't
# trigger a second refresh call of its own.
client_b = ISolarCloudOAuthClient()
client_b._post = lambda *a, **k: pytest.fail("token was already refreshed by client_a")
assert client_b._get_access_token() == "fresh"