All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 24s
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>
67 lines
2.7 KiB
Python
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"
|