"""库存数据看板旧缓存立即返回与后台刷新回归测试。""" import importlib import json import unittest from datetime import date, datetime, timedelta from pathlib import Path from unittest import mock class InventoryBoardStaleCacheTest(unittest.TestCase): def test_expired_shared_cache_returns_immediately_and_starts_refresh(self): from routes import dashboard stale = { 'month': '2026-08', 'latest_date': '2026-08-31', 'all_amount': 123.0, } old_time = datetime.now() - timedelta(hours=2) def fake_query(sql, params=None): normalized = ' '.join(str(sql).split()).lower() if normalized.startswith('select count(*) as row_count'): return [{'row_count': 10, 'qty_sum': 20, 'updated_signature': 'sig'}] if normalized.startswith('select warehouse_code, warehouse_property, disabled from warehouses'): return [{'warehouse_code': '01', 'warehouse_property': '云仓', 'disabled': '否'}] if normalized.startswith('create table if not exists inventory_board_system_cache'): return 0 if normalized.startswith('select payload, updated_at from inventory_board_system_cache'): return [{'payload': json.dumps(stale), 'updated_at': old_time}] raise AssertionError('旧缓存命中后不应继续执行冷计算SQL: ' + normalized[:120]) with mock.patch.object(dashboard, 'query', side_effect=fake_query), \ mock.patch.object(dashboard, 'cache_get', return_value=None), \ mock.patch.object(dashboard, 'cache_set') as cache_set, \ mock.patch.object(dashboard, '_start_inventory_board_system_refresh', return_value=True) as refresh: result = dashboard._inventory_board_system('2026-08', date(2026, 8, 31)) self.assertEqual(123.0, result['all_amount']) self.assertEqual('stale_refreshing', result['system_cache_state']) self.assertEqual(old_time.strftime('%Y-%m-%d %H:%M:%S'), result['data_updated_at']) refresh.assert_called_once() cache_set.assert_called_once() self.assertEqual(60, cache_set.call_args.kwargs['ttl']) def test_fresh_process_cache_does_not_start_refresh(self): from routes import dashboard fresh = {'all_amount': 456.0, 'system_cache_state': 'fresh'} with mock.patch.object(dashboard, 'query', return_value=[{ 'row_count': 10, 'qty_sum': 20, 'updated_signature': 'sig'}]), \ mock.patch.object(dashboard, 'cache_get', return_value=fresh), \ mock.patch.object(dashboard, '_start_inventory_board_system_refresh') as refresh: result = dashboard._inventory_board_system('2026-08', date(2026, 8, 31)) self.assertIs(fresh, result) refresh.assert_not_called() def test_force_refresh_bypasses_process_and_shared_cache_checks(self): source = Path(__file__).resolve().parents[1].joinpath('routes', 'dashboard.py').read_text(encoding='utf-8') self.assertIn('def _inventory_board_system(month, latest_date, force_refresh=False):', source) self.assertIn('if not force_refresh:\n _cached = cache_get(_cache_key)', source) self.assertIn('return inventory_board_stats(force_system_refresh=True)', source) def test_frontend_shows_refresh_state_and_polls_silently(self): app_js = Path(__file__).resolve().parents[1].joinpath('static', 'js', 'app.js').read_text(encoding='utf-8') self.assertIn('invBoardCacheStatus', app_js) self.assertIn('正在后台更新,当前先展示上次统计', app_js) self.assertIn('scheduleInventoryBoardFreshPoll(d)', app_js) self.assertIn('window._inventoryBoardRefreshAttempts > 12', app_js) if __name__ == '__main__': unittest.main()