Files
SMANAGE/gvsds/tests/test_redis_lock.py
TermiNexus 31b0ab885f refactor(orm): 手写 ORM 下沉 gvsdsdk Manager,通用工具改为 SDK 转发
P1 通用查询:viewsets / app / menu / mail / gitea / mc / stats / sync_apps 等视图与命令的裸查询改委托 SDK Manager(Role/Company/Department/Position/Menu/Tenant/App)。

P2 App 体系:app_manager.py 与 app_manifests.py 实现零裸 ORM,职务多值/虚拟职务/公司下拉全部走 SDK。

P3 工具下沉:money/json_encoder/celery_utils/exception_handler/redis_lock 改为纯转发展开;ip_filter/storage 转发并注入 app.ini 配置源;common 下 DateTime/String/Naming/UUID 转发 SDK。

删除死代码 commission/strategies/engine.py(SDK 已有同物且零引用)与 payment/models.py 的 FromTenantAndPlatform;单测 patch 目标同步为 gvsdsdk.*,test_attack_detect fixture 补齐限频去重状态。
2026-09-13 21:58:06 +08:00

167 lines
5.7 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""模块:redis_lock.py 单元测试
无 Redis 环境下验证降级行为:acquire 永远返回 True,
release 为 no-op,上下文管理器与装饰器均正常工作。
有 Redis 时由集成测试覆盖。
"""
import unittest
from unittest import mock
from django.test import SimpleTestCase
from gvsdsdk import redis_lock
class HasRedisDetectionTestCase(SimpleTestCase):
"""_HasRedis 探测逻辑。"""
def testHasRedis_CachesResult(self):
# 重置缓存
redis_lock._REDIS_OK = None
result1 = redis_lock._HasRedis()
result2 = redis_lock._HasRedis()
self.assertEqual(result1, result2)
# 结果应被缓存(_REDIS_OK 非 None)
self.assertIsNotNone(redis_lock._REDIS_OK)
def testHasRedis_NoRedis_ReturnsFalse(self):
redis_lock._REDIS_OK = None
with mock.patch.object(redis_lock, '_is_redis_backend', return_value=False):
self.assertFalse(redis_lock._HasRedis())
def testHasRedis_WithRedis_ReturnsTrue(self):
redis_lock._REDIS_OK = None
with mock.patch.object(redis_lock, '_is_redis_backend', return_value=True):
self.assertTrue(redis_lock._HasRedis())
class RedisLockNoRedisTestCase(SimpleTestCase):
"""无 Redis 时降级为 no-op。"""
def setUp(self):
# 强制无 Redis 模式
self._orig = redis_lock._REDIS_OK
redis_lock._REDIS_OK = False
def tearDown(self):
redis_lock._REDIS_OK = self._orig
def testAcquire_NoRedis_ReturnsTrue(self):
lock = redis_lock.RedisLock('test-key')
self.assertTrue(lock.acquire())
self.assertTrue(lock.acquired)
def testRelease_NoRedis_NoOp(self):
lock = redis_lock.RedisLock('test-key')
lock.acquire()
# 无 Redis 时 release 为 no-op,不应抛异常
lock.release()
def testContextManager_NoRedis_AlwaysAcquired(self):
with redis_lock.RedisLock('ctx-key') as acquired:
self.assertTrue(acquired)
def testContextManager_NoRedis_NoExceptionOnExit(self):
# 即使块内抛异常,__exit__ 也不应传播
with self.assertRaises(ValueError):
with redis_lock.RedisLock('ctx-key') as acquired:
self.assertTrue(acquired)
raise ValueError('boom')
class RedisLockedDecoratorTestCase(SimpleTestCase):
"""redis_locked 装饰器。"""
def setUp(self):
self._orig = redis_lock._REDIS_OK
redis_lock._REDIS_OK = False
def tearDown(self):
redis_lock._REDIS_OK = self._orig
def testDecorator_StaticKey_ExecutesFunc(self):
@redis_lock.redis_locked('static-key', timeout=10)
def Task():
return 'done'
self.assertEqual(Task(), 'done')
def testDecorator_KeyArgByIndex(self):
@redis_lock.redis_locked('order', timeout=10, key_arg=0)
def PayOrder(order_uuid):
return f'paid:{order_uuid}'
self.assertEqual(PayOrder('abc-123'), 'paid:abc-123')
def testDecorator_KeyArgByName(self):
@redis_lock.redis_locked('order', timeout=10, key_arg='order_uuid')
def PayOrder(order_uuid=None):
return f'paid:{order_uuid}'
self.assertEqual(PayOrder(order_uuid='xyz'), 'paid:xyz')
def testDecorator_KeyArgMissing_FallsBackToStaticKey(self):
@redis_lock.redis_locked('order', timeout=10, key_arg='missing')
def PayOrder(order_uuid):
return f'paid:{order_uuid}'
# missing 参数不存在 → 使用静态 key 'order'
self.assertEqual(PayOrder('abc'), 'paid:abc')
def testDecorator_PreservesFuncMetadata(self):
@redis_lock.redis_locked('meta-key')
def ImportantTask():
"""Important docstring."""
self.assertEqual(ImportantTask.__name__, 'ImportantTask')
self.assertEqual(ImportantTask.__doc__, 'Important docstring.')
class RedisLockWithRedisTestCase(SimpleTestCase):
"""有 Redis 时通过 mock cache 验证核心逻辑。"""
def setUp(self):
self._orig = redis_lock._REDIS_OK
redis_lock._REDIS_OK = True
def tearDown(self):
redis_lock._REDIS_OK = self._orig
def testAcquire_CacheAddSuccess_ReturnsTrue(self):
lock = redis_lock.RedisLock('win-key', timeout=5)
with mock.patch('gvsdsdk.redis_lock.cache') as mock_cache:
mock_cache.add.return_value = True
self.assertTrue(lock.acquire())
mock_cache.add.assert_called_once()
args, kwargs = mock_cache.add.call_args
self.assertIn('win-key', args[0])
self.assertEqual(kwargs['timeout'], 5)
def testAcquire_CacheAddFail_NoWait_ReturnsFalse(self):
lock = redis_lock.RedisLock('lose-key', wait=False)
with mock.patch('gvsdsdk.redis_lock.cache') as mock_cache:
mock_cache.add.return_value = False
self.assertFalse(lock.acquire())
def testRelease_TokenMatch_Deletes(self):
lock = redis_lock.RedisLock('rel-key')
lock._acquired = True
with mock.patch('gvsdsdk.redis_lock.cache') as mock_cache:
mock_cache.get.return_value = lock.token
lock.release()
mock_cache.delete.assert_called_once()
def testRelease_TokenMismatch_DoesNotDelete(self):
lock = redis_lock.RedisLock('rel-key')
lock._acquired = True
with mock.patch('gvsdsdk.redis_lock.cache') as mock_cache:
mock_cache.get.return_value = 'other-token'
lock.release()
mock_cache.delete.assert_not_called()
def testRelease_NotAcquired_NoOp(self):
lock = redis_lock.RedisLock('rel-key')
with mock.patch('gvsdsdk.redis_lock.cache') as mock_cache:
lock.release()
mock_cache.get.assert_not_called()