test_redis_token_store.py 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  1. import json
  2. import os
  3. import tempfile
  4. import unittest
  5. from app import GatewayApp
  6. from config import GatewayConfig
  7. from services.auth_client import AuthClient
  8. from services.token_store import RedisTokenStore
  9. class FakeRedisClient:
  10. def __init__(self):
  11. self.values = {}
  12. self.expiries = {}
  13. self.deleted = []
  14. def set(self, key, value, ex=None):
  15. self.values[key] = value
  16. self.expiries[key] = ex
  17. return True
  18. def get(self, key):
  19. return self.values.get(key)
  20. def delete(self, key):
  21. self.deleted.append(key)
  22. self.values.pop(key, None)
  23. return 1
  24. class DummyTransport:
  25. def __init__(self):
  26. self.calls = []
  27. def post_json(self, url, payload, headers, timeout):
  28. self.calls.append(
  29. {
  30. 'url': url,
  31. 'payload': payload,
  32. 'headers': headers,
  33. 'timeout': timeout,
  34. }
  35. )
  36. return {
  37. 'code': 'MCP_0000',
  38. 'msg': 'success',
  39. 'data': {
  40. 'mcp_token': 'MT_session',
  41. 'expire_time': '2099-01-01T00:00:00',
  42. },
  43. }
  44. class RedisTokenStoreTest(unittest.TestCase):
  45. def test_gateway_config_reads_redis_store_settings_from_dotenv(self):
  46. with tempfile.TemporaryDirectory() as tmp_dir:
  47. dotenv_path = os.path.join(tmp_dir, '.env')
  48. with open(dotenv_path, 'w', encoding='utf-8') as file:
  49. file.write('FMS_AUTH_BASE=http://auth.example.test\n')
  50. file.write('FMS_TOOLS_BASE=http://tools.example.test\n')
  51. file.write('FMS_TOKEN_STORE=redis\n')
  52. file.write('FMS_REDIS_HOST=192.168.1.241\n')
  53. file.write('FMS_REDIS_PORT=6380\n')
  54. file.write('FMS_REDIS_DB=2\n')
  55. file.write('FMS_REDIS_PASSWORD=secret\n')
  56. file.write('FMS_REDIS_PREFIX=fms:mcp:workbuddy:\n')
  57. file.write('FMS_SESSION_KEY=pc-a:user-a\n')
  58. config = GatewayConfig.from_env(env={}, dotenv_path=dotenv_path)
  59. self.assertEqual('redis', config.token_store_type)
  60. self.assertEqual('192.168.1.241', config.redis_host)
  61. self.assertEqual(6380, config.redis_port)
  62. self.assertEqual(2, config.redis_db)
  63. self.assertEqual('secret', config.redis_password)
  64. self.assertEqual('fms:mcp:workbuddy:', config.redis_prefix)
  65. self.assertEqual('pc-a:user-a', config.session_key)
  66. def test_redis_token_store_isolates_tokens_by_session_key(self):
  67. redis = FakeRedisClient()
  68. first = RedisTokenStore(redis, prefix='fms:mcp:workbuddy:', session_key='pc-a:user-a')
  69. second = RedisTokenStore(redis, prefix='fms:mcp:workbuddy:', session_key='pc-b:user-b')
  70. first.save('MT_first', '2099-01-01T00:00:00')
  71. second.save('MT_second', '2099-01-01T00:00:00')
  72. self.assertEqual('MT_first', first.get()['token'])
  73. self.assertEqual('MT_second', second.get()['token'])
  74. self.assertIn('fms:mcp:workbuddy:pc-a:user-a', redis.values)
  75. self.assertIn('fms:mcp:workbuddy:pc-b:user-b', redis.values)
  76. self.assertNotEqual(redis.values['fms:mcp:workbuddy:pc-a:user-a'], redis.values['fms:mcp:workbuddy:pc-b:user-b'])
  77. first.clear()
  78. self.assertIsNone(first.get())
  79. self.assertEqual('MT_second', second.get()['token'])
  80. self.assertEqual(['fms:mcp:workbuddy:pc-a:user-a'], redis.deleted)
  81. def test_auth_client_exchange_sends_session_key(self):
  82. transport = DummyTransport()
  83. redis = FakeRedisClient()
  84. store = RedisTokenStore(redis, prefix='fms:mcp:workbuddy:', session_key='pc-a:user-a')
  85. client = AuthClient(
  86. base_url='http://auth.example.test',
  87. client_type='workbuddy',
  88. token_store=store,
  89. transport=transport,
  90. timeout=9,
  91. session_key='pc-a:user-a',
  92. )
  93. client.exchange('AUTH123')
  94. self.assertEqual(
  95. {
  96. 'auth_code': 'AUTH123',
  97. 'client_type': 'workbuddy',
  98. 'session_key': 'pc-a:user-a',
  99. },
  100. transport.calls[0]['payload'],
  101. )
  102. self.assertEqual('MT_session', store.get()['token'])
  103. def test_gateway_from_config_uses_redis_token_store_when_configured(self):
  104. config = GatewayConfig(
  105. auth_base_url='http://auth.example.test',
  106. tools_base_url='http://tools.example.test',
  107. token_store_type='redis',
  108. redis_host='127.0.0.1',
  109. redis_port=6379,
  110. redis_db=0,
  111. redis_password='',
  112. redis_prefix='fms:mcp:workbuddy:',
  113. session_key='pc-a:user-a',
  114. )
  115. app = GatewayApp.from_config(config, redis_client=FakeRedisClient())
  116. self.assertIsInstance(app.token_store, RedisTokenStore)
  117. self.assertEqual('fms:mcp:workbuddy:pc-a:user-a', app.token_store.key)
  118. self.assertEqual('pc-a:user-a', app.auth_client.session_key)
  119. if __name__ == '__main__':
  120. unittest.main()