mirror of https://github.com/buster-so/buster.git
251 lines
9.4 KiB
TypeScript
251 lines
9.4 KiB
TypeScript
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
import {
|
|
canUserAccessChatCached,
|
|
getCacheStats,
|
|
resetCacheStats,
|
|
clearCache,
|
|
invalidateAccess,
|
|
invalidateUserAccess,
|
|
invalidateChatAccess,
|
|
} from '../../src/chats-cached';
|
|
|
|
// Mock the original canUserAccessChat function
|
|
vi.mock('../../src/chats', () => ({
|
|
canUserAccessChat: vi.fn(),
|
|
}));
|
|
|
|
describe('canUserAccessChatCached', () => {
|
|
let mockCanUserAccessChat: any;
|
|
|
|
beforeEach(async () => {
|
|
vi.clearAllMocks();
|
|
clearCache();
|
|
resetCacheStats();
|
|
|
|
// Get the mocked function
|
|
const chatsModule = await import('../../src/chats');
|
|
mockCanUserAccessChat = vi.mocked(chatsModule.canUserAccessChat);
|
|
});
|
|
|
|
it('should return cached result on second call', async () => {
|
|
// Setup
|
|
mockCanUserAccessChat.mockResolvedValue(true);
|
|
const userId = '123e4567-e89b-12d3-a456-426614174000';
|
|
const chatId = '223e4567-e89b-12d3-a456-426614174000';
|
|
|
|
// First call - should hit database
|
|
const result1 = await canUserAccessChatCached({ userId, chatId });
|
|
expect(result1).toBe(true);
|
|
expect(mockCanUserAccessChat).toHaveBeenCalledTimes(1);
|
|
|
|
// Second call - should hit cache
|
|
const result2 = await canUserAccessChatCached({ userId, chatId });
|
|
expect(result2).toBe(true);
|
|
expect(mockCanUserAccessChat).toHaveBeenCalledTimes(1); // Still 1, not called again
|
|
|
|
// Check cache stats
|
|
const stats = getCacheStats();
|
|
expect(stats.hits).toBe(1);
|
|
expect(stats.misses).toBe(1);
|
|
expect(stats.hitRate).toBe('50.00%');
|
|
});
|
|
|
|
it('should cache false results', async () => {
|
|
mockCanUserAccessChat.mockResolvedValue(false);
|
|
const userId = '123e4567-e89b-12d3-a456-426614174000';
|
|
const chatId = '223e4567-e89b-12d3-a456-426614174000';
|
|
|
|
// First call
|
|
const result1 = await canUserAccessChatCached({ userId, chatId });
|
|
expect(result1).toBe(false);
|
|
|
|
// Second call - should use cache
|
|
const result2 = await canUserAccessChatCached({ userId, chatId });
|
|
expect(result2).toBe(false);
|
|
expect(mockCanUserAccessChat).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it('should handle different user/chat combinations independently', async () => {
|
|
const user1 = '123e4567-e89b-12d3-a456-426614174000';
|
|
const user2 = '323e4567-e89b-12d3-a456-426614174000';
|
|
const chat1 = '423e4567-e89b-12d3-a456-426614174000';
|
|
const chat2 = '523e4567-e89b-12d3-a456-426614174000';
|
|
|
|
// Setup different responses
|
|
mockCanUserAccessChat
|
|
.mockResolvedValueOnce(true) // user1:chat1
|
|
.mockResolvedValueOnce(false) // user1:chat2
|
|
.mockResolvedValueOnce(false) // user2:chat1
|
|
.mockResolvedValueOnce(true); // user2:chat2
|
|
|
|
// Make calls
|
|
expect(await canUserAccessChatCached({ userId: user1, chatId: chat1 })).toBe(true);
|
|
expect(await canUserAccessChatCached({ userId: user1, chatId: chat2 })).toBe(false);
|
|
expect(await canUserAccessChatCached({ userId: user2, chatId: chat1 })).toBe(false);
|
|
expect(await canUserAccessChatCached({ userId: user2, chatId: chat2 })).toBe(true);
|
|
|
|
// All should be cached now
|
|
expect(await canUserAccessChatCached({ userId: user1, chatId: chat1 })).toBe(true);
|
|
expect(await canUserAccessChatCached({ userId: user1, chatId: chat2 })).toBe(false);
|
|
expect(await canUserAccessChatCached({ userId: user2, chatId: chat1 })).toBe(false);
|
|
expect(await canUserAccessChatCached({ userId: user2, chatId: chat2 })).toBe(true);
|
|
|
|
// Should have called original function 4 times (once per unique combination)
|
|
expect(mockCanUserAccessChat).toHaveBeenCalledTimes(4);
|
|
});
|
|
|
|
it('should invalidate specific user:chat combination', async () => {
|
|
mockCanUserAccessChat.mockResolvedValue(true);
|
|
const userId = '123e4567-e89b-12d3-a456-426614174000';
|
|
const chatId = '223e4567-e89b-12d3-a456-426614174000';
|
|
|
|
// Cache the result
|
|
await canUserAccessChatCached({ userId, chatId });
|
|
expect(mockCanUserAccessChat).toHaveBeenCalledTimes(1);
|
|
|
|
// Use cached result
|
|
await canUserAccessChatCached({ userId, chatId });
|
|
expect(mockCanUserAccessChat).toHaveBeenCalledTimes(1);
|
|
|
|
// Invalidate
|
|
invalidateAccess(userId, chatId);
|
|
|
|
// Should call database again
|
|
await canUserAccessChatCached({ userId, chatId });
|
|
expect(mockCanUserAccessChat).toHaveBeenCalledTimes(2);
|
|
});
|
|
|
|
it('should invalidate all entries for a user', async () => {
|
|
mockCanUserAccessChat.mockResolvedValue(true);
|
|
const userId = '123e4567-e89b-12d3-a456-426614174000';
|
|
const chat1 = '223e4567-e89b-12d3-a456-426614174000';
|
|
const chat2 = '323e4567-e89b-12d3-a456-426614174000';
|
|
const otherUserId = '423e4567-e89b-12d3-a456-426614174000';
|
|
|
|
// Cache results for multiple chats
|
|
await canUserAccessChatCached({ userId, chatId: chat1 });
|
|
await canUserAccessChatCached({ userId, chatId: chat2 });
|
|
await canUserAccessChatCached({ userId: otherUserId, chatId: chat1 });
|
|
expect(mockCanUserAccessChat).toHaveBeenCalledTimes(3);
|
|
|
|
// Use cached results
|
|
await canUserAccessChatCached({ userId, chatId: chat1 });
|
|
await canUserAccessChatCached({ userId, chatId: chat2 });
|
|
await canUserAccessChatCached({ userId: otherUserId, chatId: chat1 });
|
|
expect(mockCanUserAccessChat).toHaveBeenCalledTimes(3); // Still 3
|
|
|
|
// Invalidate all entries for userId
|
|
invalidateUserAccess(userId);
|
|
|
|
// Should call database for invalidated user
|
|
await canUserAccessChatCached({ userId, chatId: chat1 });
|
|
await canUserAccessChatCached({ userId, chatId: chat2 });
|
|
expect(mockCanUserAccessChat).toHaveBeenCalledTimes(5); // +2
|
|
|
|
// Other user should still be cached
|
|
await canUserAccessChatCached({ userId: otherUserId, chatId: chat1 });
|
|
expect(mockCanUserAccessChat).toHaveBeenCalledTimes(5); // Still 5
|
|
});
|
|
|
|
it('should invalidate all entries for a chat', async () => {
|
|
mockCanUserAccessChat.mockResolvedValue(true);
|
|
const user1 = '123e4567-e89b-12d3-a456-426614174000';
|
|
const user2 = '223e4567-e89b-12d3-a456-426614174000';
|
|
const chatId = '323e4567-e89b-12d3-a456-426614174000';
|
|
const otherChatId = '423e4567-e89b-12d3-a456-426614174000';
|
|
|
|
// Cache results for multiple users
|
|
await canUserAccessChatCached({ userId: user1, chatId });
|
|
await canUserAccessChatCached({ userId: user2, chatId });
|
|
await canUserAccessChatCached({ userId: user1, chatId: otherChatId });
|
|
expect(mockCanUserAccessChat).toHaveBeenCalledTimes(3);
|
|
|
|
// Invalidate all entries for chatId
|
|
invalidateChatAccess(chatId);
|
|
|
|
// Should call database for invalidated chat
|
|
await canUserAccessChatCached({ userId: user1, chatId });
|
|
await canUserAccessChatCached({ userId: user2, chatId });
|
|
expect(mockCanUserAccessChat).toHaveBeenCalledTimes(5); // +2
|
|
|
|
// Other chat should still be cached
|
|
await canUserAccessChatCached({ userId: user1, chatId: otherChatId });
|
|
expect(mockCanUserAccessChat).toHaveBeenCalledTimes(5); // Still 5
|
|
});
|
|
|
|
it('should clear entire cache', async () => {
|
|
mockCanUserAccessChat.mockResolvedValue(true);
|
|
const userId = '123e4567-e89b-12d3-a456-426614174000';
|
|
const chatId = '223e4567-e89b-12d3-a456-426614174000';
|
|
|
|
// Cache a result
|
|
await canUserAccessChatCached({ userId, chatId });
|
|
expect(mockCanUserAccessChat).toHaveBeenCalledTimes(1);
|
|
|
|
// Clear cache
|
|
clearCache();
|
|
|
|
// Should call database again
|
|
await canUserAccessChatCached({ userId, chatId });
|
|
expect(mockCanUserAccessChat).toHaveBeenCalledTimes(2);
|
|
});
|
|
|
|
it('should track cache statistics correctly', async () => {
|
|
mockCanUserAccessChat.mockResolvedValue(true);
|
|
const userId = '123e4567-e89b-12d3-a456-426614174000';
|
|
const chatId = '223e4567-e89b-12d3-a456-426614174000';
|
|
|
|
// Initial stats
|
|
let stats = getCacheStats();
|
|
expect(stats.hits).toBe(0);
|
|
expect(stats.misses).toBe(0);
|
|
expect(stats.total).toBe(0);
|
|
expect(stats.hitRate).toBe('0.00%');
|
|
expect(stats.size).toBe(0);
|
|
|
|
// First call - miss
|
|
await canUserAccessChatCached({ userId, chatId });
|
|
stats = getCacheStats();
|
|
expect(stats.hits).toBe(0);
|
|
expect(stats.misses).toBe(1);
|
|
expect(stats.hitRate).toBe('0.00%');
|
|
expect(stats.size).toBe(1);
|
|
|
|
// Second call - hit
|
|
await canUserAccessChatCached({ userId, chatId });
|
|
stats = getCacheStats();
|
|
expect(stats.hits).toBe(1);
|
|
expect(stats.misses).toBe(1);
|
|
expect(stats.hitRate).toBe('50.00%');
|
|
|
|
// Third call - hit
|
|
await canUserAccessChatCached({ userId, chatId });
|
|
stats = getCacheStats();
|
|
expect(stats.hits).toBe(2);
|
|
expect(stats.misses).toBe(1);
|
|
expect(stats.hitRate).toBe('66.67%');
|
|
|
|
// Reset stats
|
|
resetCacheStats();
|
|
stats = getCacheStats();
|
|
expect(stats.hits).toBe(0);
|
|
expect(stats.misses).toBe(0);
|
|
expect(stats.size).toBe(1); // Cache still has entries
|
|
});
|
|
|
|
it('should handle errors from the original function', async () => {
|
|
const error = new Error('Database error');
|
|
mockCanUserAccessChat.mockRejectedValue(error);
|
|
const userId = '123e4567-e89b-12d3-a456-426614174000';
|
|
const chatId = '223e4567-e89b-12d3-a456-426614174000';
|
|
|
|
// Should propagate the error
|
|
await expect(canUserAccessChatCached({ userId, chatId })).rejects.toThrow('Database error');
|
|
|
|
// Should not cache errors
|
|
mockCanUserAccessChat.mockResolvedValue(true);
|
|
const result = await canUserAccessChatCached({ userId, chatId });
|
|
expect(result).toBe(true);
|
|
expect(mockCanUserAccessChat).toHaveBeenCalledTimes(2); // Called again, error wasn't cached
|
|
});
|
|
}); |