import { randomUUID } from 'node:crypto'; import { and, assetPermissions, chats, collections, collectionsToAssets, eq, getDb, organizations, users, usersToOrganizations, } from '@buster/database'; import { afterAll, beforeAll, describe, expect, test } from 'vitest'; import { canUserAccessChat } from '../../src/chats'; describe('canUserAccessChat Integration Tests', () => { const db = getDb(); // Test data IDs const testOrgId = randomUUID(); const systemUserId = randomUUID(); // System user for created_by/updated_by fields const testUserId = randomUUID(); const testAdminUserId = randomUUID(); const testChatId = randomUUID(); const testCollectionId = randomUUID(); const testChatWithCollectionId = randomUUID(); const testChatNoAccessId = randomUUID(); const testUserNoAccessId = randomUUID(); beforeAll(async () => { // Create test organization await db.insert(organizations).values({ id: testOrgId, name: 'Test Organization', createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), }); // Create system user first (needed for created_by/updated_by references) await db.insert(users).values({ id: systemUserId, email: `system-${Date.now()}@example.com`, name: 'System User', config: {}, attributes: {}, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), }); // Create test users const timestamp = Date.now(); await db.insert(users).values([ { id: testUserId, email: `test-user-${timestamp}@example.com`, name: 'Test User', config: {}, attributes: {}, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), }, { id: testAdminUserId, email: `test-admin-${timestamp}@example.com`, name: 'Test Admin', config: {}, attributes: {}, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), }, { id: testUserNoAccessId, email: `test-no-access-${timestamp}@example.com`, name: 'Test No Access', config: {}, attributes: {}, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), }, ]); // Create user-organization relationships await db.insert(usersToOrganizations).values([ { userId: testUserId, organizationId: testOrgId, role: 'querier', createdBy: systemUserId, updatedBy: systemUserId, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), }, { userId: testAdminUserId, organizationId: testOrgId, role: 'workspace_admin', createdBy: systemUserId, updatedBy: systemUserId, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), }, { userId: testUserNoAccessId, organizationId: testOrgId, role: 'viewer', createdBy: systemUserId, updatedBy: systemUserId, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), }, ]); // Create test chats await db.insert(chats).values([ { id: testChatId, title: 'Test Chat with Direct Permission', organizationId: testOrgId, createdBy: testUserId, updatedBy: testUserId, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), }, { id: testChatWithCollectionId, title: 'Test Chat with Collection Permission', organizationId: testOrgId, createdBy: testAdminUserId, updatedBy: testAdminUserId, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), }, { id: testChatNoAccessId, title: 'Test Chat No Access', organizationId: testOrgId, createdBy: testAdminUserId, updatedBy: testAdminUserId, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), }, ]); // Create test collection await db.insert(collections).values({ id: testCollectionId, name: 'Test Collection', organizationId: testOrgId, createdBy: testAdminUserId, updatedBy: testAdminUserId, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), }); // Create direct chat permission await db.insert(assetPermissions).values({ identityId: testUserId, identityType: 'user', assetId: testChatId, assetType: 'chat', role: 'can_view', createdBy: testUserId, updatedBy: testUserId, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), }); // Create collection permission for testUserId await db.insert(assetPermissions).values({ identityId: testUserId, identityType: 'user', assetId: testCollectionId, assetType: 'collection', role: 'can_view', createdBy: testAdminUserId, updatedBy: testAdminUserId, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), }); // Add chat to collection await db.insert(collectionsToAssets).values({ collectionId: testCollectionId, assetId: testChatWithCollectionId, assetType: 'chat', createdBy: testAdminUserId, updatedBy: testAdminUserId, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), }); }); afterAll(async () => { // Clean up test data in reverse order of creation // Delete collections_to_assets await db .delete(collectionsToAssets) .where(eq(collectionsToAssets.collectionId, testCollectionId)); // Delete asset_permissions await db .delete(assetPermissions) .where( and(eq(assetPermissions.assetId, testChatId), eq(assetPermissions.identityId, testUserId)) ); await db .delete(assetPermissions) .where( and( eq(assetPermissions.assetId, testCollectionId), eq(assetPermissions.identityId, testUserId) ) ); // Delete collections await db.delete(collections).where(eq(collections.id, testCollectionId)); // Delete chats await db.delete(chats).where(eq(chats.organizationId, testOrgId)); // Delete users_to_organizations await db.delete(usersToOrganizations).where(eq(usersToOrganizations.organizationId, testOrgId)); // Delete users await db.delete(users).where(eq(users.id, testUserId)); await db.delete(users).where(eq(users.id, testAdminUserId)); await db.delete(users).where(eq(users.id, testUserNoAccessId)); await db.delete(users).where(eq(users.id, systemUserId)); // Delete organization await db.delete(organizations).where(eq(organizations.id, testOrgId)); }); test('should return true when user has direct permission', async () => { const result = await canUserAccessChat({ userId: testUserId, chatId: testChatId, }); expect(result).toBe(true); }); test('should return true when user has collection permission', async () => { const result = await canUserAccessChat({ userId: testUserId, chatId: testChatWithCollectionId, }); expect(result).toBe(true); }); test('should return true when user is the creator', async () => { const result = await canUserAccessChat({ userId: testUserId, chatId: testChatId, }); expect(result).toBe(true); }); test('should return true when user is workspace_admin', async () => { const result = await canUserAccessChat({ userId: testAdminUserId, chatId: testChatNoAccessId, }); expect(result).toBe(true); }); test('should return false when user has no access', async () => { const result = await canUserAccessChat({ userId: testUserNoAccessId, chatId: testChatNoAccessId, }); expect(result).toBe(false); }); test('should return false for non-existent chat', async () => { const result = await canUserAccessChat({ userId: testUserId, chatId: randomUUID(), }); expect(result).toBe(false); }); test('should return false for non-existent user', async () => { const result = await canUserAccessChat({ userId: randomUUID(), chatId: testChatId, }); expect(result).toBe(false); }); test('should handle deleted chat gracefully', async () => { const deletedChatId = randomUUID(); // Create a deleted chat await db.insert(chats).values({ id: deletedChatId, title: 'Deleted Chat', organizationId: testOrgId, createdBy: testUserId, updatedBy: testUserId, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), deletedAt: new Date().toISOString(), }); const result = await canUserAccessChat({ userId: testUserId, chatId: deletedChatId, }); expect(result).toBe(false); // Clean up await db.delete(chats).where(eq(chats.id, deletedChatId)); }); test('should perform all checks concurrently', async () => { const startTime = Date.now(); const result = await canUserAccessChat({ userId: testUserId, chatId: testChatId, }); const endTime = Date.now(); const duration = endTime - startTime; expect(result).toBe(true); // Assuming each query takes at least 10ms, 4 sequential queries would take 40ms+ // Concurrent execution should be significantly faster expect(duration).toBeLessThan(100); // Generous threshold for CI environments }); });