mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-08-11 09:13:36 +08:00
🐛 fix(redis): 修复过滤扫描空页导致 Key 搜索漏项
- 自动沿非零游标继续扫描空结果页 - 在游标结束或重复时安全终止续扫 - 增加过滤搜索跨空页命中 Key 的回归测试
This commit is contained in:
@@ -359,6 +359,52 @@ describe('RedisViewer tree interactions', () => {
|
||||
renderer!.unmount();
|
||||
});
|
||||
|
||||
it('continues a filtered scan when the first cursor page has no matching keys', async () => {
|
||||
redisBackend.RedisScanKeys.mockReset();
|
||||
redisBackend.RedisScanKeys
|
||||
.mockResolvedValueOnce({
|
||||
success: true,
|
||||
data: {
|
||||
cursor: '0',
|
||||
keys: [{ key: 'app:user:1', type: 'string', ttl: -1 }],
|
||||
},
|
||||
})
|
||||
.mockResolvedValueOnce({
|
||||
success: true,
|
||||
data: { cursor: '27', keys: [] },
|
||||
})
|
||||
.mockResolvedValueOnce({
|
||||
success: true,
|
||||
data: {
|
||||
cursor: '0',
|
||||
keys: [{ key: 'sub:v2:lock', type: 'string', ttl: 2400 }],
|
||||
},
|
||||
});
|
||||
|
||||
let renderer: ReactTestRenderer;
|
||||
await act(async () => {
|
||||
renderer = create(<RedisViewer connectionId="redis-1" redisDB={0} />);
|
||||
});
|
||||
await flushEffects();
|
||||
|
||||
const searchInput = renderer!.root.findAllByType('input')
|
||||
.find((node) => typeof node.props.onSearch === 'function');
|
||||
expect(searchInput).toBeTruthy();
|
||||
|
||||
await act(async () => {
|
||||
searchInput!.props.onSearch('sub:v2');
|
||||
});
|
||||
await flushEffects();
|
||||
|
||||
expect(redisBackend.RedisScanKeys).toHaveBeenCalledTimes(3);
|
||||
expect(redisBackend.RedisScanKeys.mock.calls[1]?.[2]).toBe('0');
|
||||
expect(redisBackend.RedisScanKeys.mock.calls[2]?.[2]).toBe('27');
|
||||
expect(countLeafNodes(antdState.treeProps.treeData)).toBe(1);
|
||||
expect(findFirstLeafNode(antdState.treeProps.treeData)?.rawKey).toBe('sub:v2:lock');
|
||||
|
||||
renderer!.unmount();
|
||||
});
|
||||
|
||||
it('loads every key page when the load-all action is clicked', async () => {
|
||||
redisBackend.RedisScanKeys.mockReset();
|
||||
redisBackend.RedisScanKeys
|
||||
|
||||
@@ -345,10 +345,36 @@ const RedisViewer: React.FC<RedisViewerProps> = ({ connectionId, redisDB }) => {
|
||||
|
||||
setLoading(true);
|
||||
try {
|
||||
const { scannedKeys, nextCursor } = await scanRedisKeysPage(config, normalizedPattern, fromCursor, effectiveTargetCount);
|
||||
if (requestId !== latestLoadRequestIdRef.current) {
|
||||
return;
|
||||
let scanCursor = normalizeRedisCursor(fromCursor);
|
||||
let scannedKeys: RedisKeyInfo[] = [];
|
||||
let nextCursor = scanCursor;
|
||||
const visitedCursors = new Set<string>();
|
||||
|
||||
while (true) {
|
||||
if (visitedCursors.has(scanCursor)) {
|
||||
nextCursor = '0';
|
||||
break;
|
||||
}
|
||||
visitedCursors.add(scanCursor);
|
||||
|
||||
const page = await scanRedisKeysPage(
|
||||
config,
|
||||
normalizedPattern,
|
||||
scanCursor,
|
||||
effectiveTargetCount
|
||||
);
|
||||
if (requestId !== latestLoadRequestIdRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
scannedKeys = page.scannedKeys;
|
||||
nextCursor = page.nextCursor;
|
||||
if (scannedKeys.length > 0 || nextCursor === '0') {
|
||||
break;
|
||||
}
|
||||
scanCursor = nextCursor;
|
||||
}
|
||||
|
||||
if (append) {
|
||||
setKeys(prev => mergeRedisKeyInfoLists(prev, scannedKeys));
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user