feat(kis): cache tokens in sqlite and add inspector
This commit is contained in:
@@ -4,6 +4,9 @@ import sys
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch
|
||||
import warnings
|
||||
|
||||
warnings.simplefilter("ignore", ResourceWarning)
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
if str(ROOT) not in sys.path:
|
||||
@@ -154,6 +157,101 @@ class TestKisApiClientV1(unittest.TestCase):
|
||||
|
||||
shutil.rmtree(tmp_dir, ignore_errors=True)
|
||||
|
||||
def test_issue_or_reuse_token_honors_token_db_override(self):
|
||||
import tempfile
|
||||
import shutil
|
||||
import sqlite3
|
||||
from src.quant_engine.kis_api_client_v1 import _issue_or_reuse_token, KisCredentials
|
||||
|
||||
tmp_dir = tempfile.mkdtemp()
|
||||
override_db = Path(tmp_dir) / "custom_kis_tokens.db"
|
||||
|
||||
with patch.dict("os.environ", {"KIS_TOKEN_DB_PATH": str(override_db)}), \
|
||||
patch("src.quant_engine.kis_api_client_v1._requests") as mock_requests:
|
||||
creds = KisCredentials(app_key="k", app_secret="s", account="mock")
|
||||
mock_resp = mock_requests.return_value.post.return_value
|
||||
mock_resp.raise_for_status.return_value = None
|
||||
mock_resp.json.return_value = {
|
||||
"access_token": "override-token-789",
|
||||
"expires_in": "3600",
|
||||
}
|
||||
|
||||
token = _issue_or_reuse_token(creds)
|
||||
self.assertEqual(token, "override-token-789")
|
||||
self.assertTrue(override_db.exists())
|
||||
|
||||
with sqlite3.connect(override_db) as conn:
|
||||
row = conn.execute(
|
||||
"SELECT access_token FROM kis_tokens WHERE account = 'mock'"
|
||||
).fetchone()
|
||||
self.assertIsNotNone(row)
|
||||
self.assertEqual(row[0], "override-token-789")
|
||||
|
||||
shutil.rmtree(tmp_dir, ignore_errors=True)
|
||||
|
||||
def test_issue_or_reuse_token_serializes_concurrent_refresh(self):
|
||||
import tempfile
|
||||
import shutil
|
||||
import sqlite3
|
||||
import threading
|
||||
import time
|
||||
from src.quant_engine.kis_api_client_v1 import _issue_or_reuse_token, KisCredentials
|
||||
|
||||
tmp_dir = tempfile.mkdtemp()
|
||||
override_db = Path(tmp_dir) / "kis_tokens.db"
|
||||
barrier = threading.Barrier(2)
|
||||
post_calls = []
|
||||
|
||||
class _Response:
|
||||
def raise_for_status(self):
|
||||
return None
|
||||
|
||||
def json(self):
|
||||
return {
|
||||
"access_token": "concurrent-token-001",
|
||||
"expires_in": "3600",
|
||||
}
|
||||
|
||||
def _post(*args, **kwargs):
|
||||
post_calls.append(time.time())
|
||||
time.sleep(0.2)
|
||||
return _Response()
|
||||
|
||||
with patch.dict("os.environ", {"KIS_TOKEN_DB_PATH": str(override_db)}), \
|
||||
patch("src.quant_engine.kis_api_client_v1._requests") as mock_requests:
|
||||
mock_requests.return_value.post.side_effect = _post
|
||||
creds = KisCredentials(app_key="k", app_secret="s", account="mock")
|
||||
|
||||
results: list[str] = []
|
||||
errors: list[BaseException] = []
|
||||
|
||||
def worker() -> None:
|
||||
try:
|
||||
barrier.wait(timeout=5)
|
||||
results.append(_issue_or_reuse_token(creds))
|
||||
except BaseException as exc: # pragma: no cover - test harness diagnostic
|
||||
errors.append(exc)
|
||||
|
||||
t1 = threading.Thread(target=worker)
|
||||
t2 = threading.Thread(target=worker)
|
||||
t1.start()
|
||||
t2.start()
|
||||
t1.join(timeout=10)
|
||||
t2.join(timeout=10)
|
||||
|
||||
self.assertEqual(errors, [])
|
||||
self.assertEqual(results, ["concurrent-token-001", "concurrent-token-001"])
|
||||
self.assertEqual(len(post_calls), 1)
|
||||
|
||||
with sqlite3.connect(override_db) as conn:
|
||||
row = conn.execute(
|
||||
"SELECT access_token FROM kis_tokens WHERE account = 'mock'"
|
||||
).fetchone()
|
||||
self.assertIsNotNone(row)
|
||||
self.assertEqual(row[0], "concurrent-token-001")
|
||||
|
||||
shutil.rmtree(tmp_dir, ignore_errors=True)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user