import { randomUUID } from 'node:crypto'; import { dashboardFiles, db, inArray, metricFiles } from '@buster/database'; import { afterEach, beforeEach, describe } from 'vitest'; //import { modifyDashboardsFileTool } from '../../../src/tools/visualization-tools/modify-dashboards-file-tool'; describe('Modify Dashboards File Tool Integration Tests', () => { let mockRuntimeContext: Record; let testDataSourceId: string; let testUserId: string; let testOrgId: string; let createdMetricIds: string[] = []; let createdDashboardIds: string[] = []; beforeEach(() => { // Use real test environment IDs testDataSourceId = 'cc3ef3bc-44ec-4a43-8dc4-681cae5c996a'; testUserId = '1fe85021-e799-471b-8837-953e9ae06e4c'; testOrgId = 'bf58d19a-8bb9-4f1d-a257-2d2105e7f1ce'; mockRuntimeContext = { get: (key: string) => { const values: Record = { user_id: testUserId, organization_id: testOrgId, }; return values[key]; }, }; // Reset created IDs arrays createdMetricIds = []; createdDashboardIds = []; }); afterEach(async () => { // Clean up created dashboards and metrics try { if (createdDashboardIds.length > 0) { await db .delete(dashboardFiles) .where(inArray(dashboardFiles.id, createdDashboardIds)) .execute(); } if (createdMetricIds.length > 0) { await db.delete(metricFiles).where(inArray(metricFiles.id, createdMetricIds)).execute(); } } catch (error) { // Ignore cleanup errors } }); // Helper function to create test metrics for dashboard testing async function createTestMetrics(count = 1): Promise { const metricIds: string[] = []; for (let i = 1; i <= count; i++) { const metricId = randomUUID(); const metricYml = { name: `Test Metric ${i}`, description: `A test metric ${i} for dashboard testing`, timeFrame: 'Last 30 days', sql: `SELECT COUNT(*) as count_${i} FROM test_table_${i}`, chartConfig: { selectedChartType: 'table', columnLabelFormats: { [`count_${i}`]: { columnType: 'number', style: 'number', numberSeparatorStyle: ',', replaceMissingDataWith: 0, }, }, }, }; await db .insert(metricFiles) .values({ id: metricId, name: `Test Metric ${i}`, fileName: `test-metric-${i}`, content: metricYml, verification: 'notRequested', organizationId: testOrgId, createdBy: testUserId, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), versionHistory: { versions: [ { versionNumber: 1, content: metricYml, createdAt: new Date().toISOString(), }, ], }, dataSourceId: testDataSourceId, }) .execute(); metricIds.push(metricId); createdMetricIds.push(metricId); } return metricIds; } // Helper function to create test dashboard for modification testing async function createTestDashboard(metricIds: string[]): Promise { const dashboardId = randomUUID(); const dashboardYml = { name: 'Original Test Dashboard', description: 'Original dashboard for modification testing', rows: [ { id: 1, items: metricIds.slice(0, Math.min(metricIds.length, 2)).map((id) => ({ id })), columnSizes: metricIds.length === 1 ? [12] : [6, 6], }, ], }; await db .insert(dashboardFiles) .values({ id: dashboardId, name: 'Original Test Dashboard', fileName: 'original-test-dashboard', content: dashboardYml, filter: null, organizationId: testOrgId, createdBy: testUserId, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), deletedAt: null, publiclyAccessible: false, publiclyEnabledBy: null, publicExpiryDate: null, versionHistory: { versions: [ { versionNumber: 1, content: dashboardYml, createdAt: new Date().toISOString(), }, ], }, publicPassword: null, }) .execute(); createdDashboardIds.push(dashboardId); return dashboardId; } /* test('should have correct tool configuration', () => { expect(modifyDashboardsFileTool.id).toBe('modify-dashboards-file'); expect(modifyDashboardsFileTool.description).toContain( 'Updates existing dashboard configuration files' ); expect(modifyDashboardsFileTool.inputSchema).toBeDefined(); expect(modifyDashboardsFileTool.outputSchema).toBeDefined(); expect(modifyDashboardsFileTool.execute).toBeDefined(); }); test('should validate tool input schema', () => { const validInput = { files: [ { id: randomUUID(), yml_content: ` name: Updated Sales Dashboard description: An updated comprehensive view of sales metrics rows: - id: 1 items: - id: f47ac10b-58cc-4372-a567-0e02b2c3d479 columnSizes: - 12 `, }, ], }; const result = modifyDashboardsFileTool.inputSchema.safeParse(validInput); expect(result.success).toBe(true); }); test('should validate tool output schema', () => { const validOutput = { message: 'Successfully modified 1 dashboard file.', duration: 1000, files: [ { id: randomUUID(), name: 'Updated Test Dashboard', file_type: 'dashboard', yml_content: 'name: Updated Test Dashboard', created_at: new Date().toISOString(), updated_at: new Date().toISOString(), version_number: 2, }, ], failed_files: [], }; const result = modifyDashboardsFileTool.outputSchema.safeParse(validOutput); expect(result.success).toBe(true); }); test('should handle runtime context requirements', async () => { const contextWithoutUserId = { get: (key: string) => { if (key === 'user_id') return undefined; return 'test-value'; }, }; const validYaml = ` name: Test Updated Dashboard description: Test updated dashboard rows: - id: 1 items: - id: f47ac10b-58cc-4372-a567-0e02b2c3d479 columnSizes: - 12 `; const input = { files: [{ id: randomUUID(), yml_content: validYaml }], runtimeContext: contextWithoutUserId, }; await expect(modifyDashboardsFileTool.execute({ context: input })).rejects.toThrow( 'User ID not found in runtime context' ); }); test('should reject modification of non-existent dashboard', async () => { const nonExistentId = randomUUID(); const validYaml = ` name: Updated Dashboard description: Updated dashboard rows: - id: 1 items: - id: f47ac10b-58cc-4372-a567-0e02b2c3d479 columnSizes: - 12 `; const input = { files: [{ id: nonExistentId, yml_content: validYaml }], runtimeContext: mockRuntimeContext, }; const result = await modifyDashboardsFileTool.execute({ context: input }); expect(result.files).toHaveLength(0); expect(result.failed_files).toHaveLength(1); expect(result.failed_files[0].file_name).toBe(`Dashboard ${nonExistentId}`); expect(result.failed_files[0].error).toContain('Dashboard file not found'); }); test('should reject dashboard modification with invalid YAML', async () => { // Create test metrics and dashboard const metricIds = await createTestMetrics(1); const dashboardId = await createTestDashboard(metricIds); const invalidYaml = ` name: Invalid Dashboard description: Invalid dashboard # Missing rows `; const input = { files: [{ id: dashboardId, yml_content: invalidYaml }], runtimeContext: mockRuntimeContext, }; const result = await modifyDashboardsFileTool.execute({ context: input }); expect(result.files).toHaveLength(0); expect(result.failed_files).toHaveLength(1); expect(result.failed_files[0].error).toContain('Failed to validate modified YAML'); }); test('should reject dashboard modification with invalid column sizes', async () => { // Create test metrics and dashboard const metricIds = await createTestMetrics(1); const dashboardId = await createTestDashboard(metricIds); const invalidColumnSizesYaml = ` name: Invalid Column Dashboard description: Dashboard with invalid column sizes rows: - id: 1 items: - id: ${metricIds[0]} columnSizes: - 10 `; const input = { files: [{ id: dashboardId, yml_content: invalidColumnSizesYaml }], runtimeContext: mockRuntimeContext, }; const result = await modifyDashboardsFileTool.execute({ context: input }); expect(result.files).toHaveLength(0); expect(result.failed_files).toHaveLength(1); expect(result.failed_files[0].error).toContain('Column sizes must sum to exactly 12'); }); test('should reject dashboard modification with non-existent metric IDs', async () => { // Create test metrics and dashboard const metricIds = await createTestMetrics(1); const dashboardId = await createTestDashboard(metricIds); const nonExistentMetricYaml = ` name: Non-existent Metric Dashboard description: Dashboard referencing non-existent metrics rows: - id: 1 items: - id: 00000000-0000-0000-0000-000000000000 columnSizes: - 12 `; const input = { files: [{ id: dashboardId, yml_content: nonExistentMetricYaml }], runtimeContext: mockRuntimeContext, }; const result = await modifyDashboardsFileTool.execute({ context: input }); expect(result.files).toHaveLength(0); expect(result.failed_files).toHaveLength(1); expect(result.failed_files[0].error).toContain('Invalid metric references'); }); test('should successfully modify dashboard with valid changes', async () => { // Create test metrics and dashboard const metricIds = await createTestMetrics(3); const dashboardId = await createTestDashboard(metricIds); const updatedDashboardYaml = ` name: Updated Valid Dashboard description: An updated dashboard with valid changes rows: - id: 1 items: - id: ${metricIds[0]} - id: ${metricIds[1]} columnSizes: - 6 - 6 - id: 2 items: - id: ${metricIds[2]} columnSizes: - 12 `; const input = { files: [{ id: dashboardId, yml_content: updatedDashboardYaml }], runtimeContext: mockRuntimeContext, }; const result = await modifyDashboardsFileTool.execute({ context: input }); expect(result.files).toHaveLength(1); expect(result.failed_files).toHaveLength(0); expect(result.files[0].name).toBe('Updated Valid Dashboard'); expect(result.files[0].file_type).toBe('dashboard'); expect(result.files[0].version_number).toBe(2); // Should be incremented expect(result.message).toBe('Successfully modified 1 dashboard file.'); // Verify database was updated const updatedDashboard = await db .select() .from(dashboardFiles) .where(eq(dashboardFiles.id, dashboardId)) .execute(); expect(updatedDashboard).toHaveLength(1); expect(updatedDashboard[0].name).toBe('Updated Valid Dashboard'); expect(updatedDashboard[0].content.rows).toHaveLength(2); // Verify version history const versionHistory = updatedDashboard[0].versionHistory as never; expect(versionHistory.versions).toHaveLength(2); expect(versionHistory.versions[1].versionNumber).toBe(2); expect(versionHistory.versions[1].content.name).toBe('Updated Valid Dashboard'); }); test('should handle mixed success and failure scenarios', async () => { // Create test metrics and dashboards const metricIds = await createTestMetrics(2); const validDashboardId = await createTestDashboard(metricIds); const invalidDashboardId = randomUUID(); // Non-existent dashboard const validDashboardYaml = ` name: Valid Updated Dashboard description: This should succeed rows: - id: 1 items: - id: ${metricIds[0]} columnSizes: - 12 `; const invalidDashboardYaml = ` name: Invalid Dashboard description: This should fail because dashboard doesn't exist rows: - id: 1 items: - id: ${metricIds[1]} columnSizes: - 12 `; const input = { files: [ { id: validDashboardId, yml_content: validDashboardYaml }, { id: invalidDashboardId, yml_content: invalidDashboardYaml }, ], runtimeContext: mockRuntimeContext, }; const result = await modifyDashboardsFileTool.execute({ context: input }); expect(result.files).toHaveLength(1); expect(result.failed_files).toHaveLength(1); // The success should be the valid dashboard expect(result.files[0].name).toBe('Valid Updated Dashboard'); // The failure should be due to non-existent dashboard const failure = result.failed_files.find( (f) => f.file_name === `Dashboard ${invalidDashboardId}` ); expect(failure?.error).toContain('Dashboard file not found'); }); test('should properly handle version number increments', async () => { // Create test metrics and dashboard const metricIds = await createTestMetrics(1); const dashboardId = await createTestDashboard(metricIds); // First modification const firstUpdateYaml = ` name: First Update description: First update to the dashboard rows: - id: 1 items: - id: ${metricIds[0]} columnSizes: - 12 `; const firstInput = { files: [{ id: dashboardId, yml_content: firstUpdateYaml }], runtimeContext: mockRuntimeContext, }; const firstResult = await modifyDashboardsFileTool.execute({ context: firstInput }); expect(firstResult.files[0].version_number).toBe(2); // Second modification const secondUpdateYaml = ` name: Second Update description: Second update to the dashboard rows: - id: 1 items: - id: ${metricIds[0]} columnSizes: - 12 `; const secondInput = { files: [{ id: dashboardId, yml_content: secondUpdateYaml }], runtimeContext: mockRuntimeContext, }; const secondResult = await modifyDashboardsFileTool.execute({ context: secondInput }); expect(secondResult.files[0].version_number).toBe(3); // Verify final database state const finalDashboard = await db .select() .from(dashboardFiles) .where(eq(dashboardFiles.id, dashboardId)) .execute(); const versionHistory = finalDashboard[0].versionHistory as never; expect(versionHistory.versions).toHaveLength(3); expect(versionHistory.versions[0].versionNumber).toBe(1); expect(versionHistory.versions[1].versionNumber).toBe(2); expect(versionHistory.versions[2].versionNumber).toBe(3); }); test('should properly format response timing', async () => { // Create test metrics and dashboard const metricIds = await createTestMetrics(1); const dashboardId = await createTestDashboard(metricIds); const validYaml = ` name: Timing Test Dashboard description: Test response timing rows: - id: 1 items: - id: ${metricIds[0]} columnSizes: - 12 `; const input = { files: [{ id: dashboardId, yml_content: validYaml }], runtimeContext: mockRuntimeContext, }; const result = await modifyDashboardsFileTool.execute({ context: input }); expect(result.duration).toBeGreaterThan(0); expect(typeof result.duration).toBe('number'); expect(result.duration).toBeLessThan(10000); // Should complete within 10 seconds }); test('should handle bulk dashboard modifications correctly', async () => { // Create test metrics and dashboards const metricIds = await createTestMetrics(3); const dashboardIds = await Promise.all([ createTestDashboard([metricIds[0]]), createTestDashboard([metricIds[1]]), createTestDashboard([metricIds[2]]), ]); const createDashboardYaml = (index: number) => ` name: Bulk Updated Dashboard ${index} description: Dashboard ${index} for bulk modification testing rows: - id: 1 items: - id: ${metricIds[index - 1]} columnSizes: - 12 `; const files = dashboardIds.map((id, i) => ({ id, yml_content: createDashboardYaml(i + 1), })); const input = { files, runtimeContext: mockRuntimeContext, }; const result = await modifyDashboardsFileTool.execute({ context: input }); expect(result.files).toHaveLength(3); expect(result.failed_files).toHaveLength(0); expect(result.message).toBe('Successfully modified 3 dashboard files.'); // Verify all files have proper structure result.files.forEach((file, index) => { expect(file.file_type).toBe('dashboard'); expect(file.version_number).toBe(2); // All should be version 2 expect(file.name).toContain('Bulk Updated Dashboard'); expect(file.yml_content).toContain(`Bulk Updated Dashboard ${index + 1}`); }); }); test('should handle complex dashboard modifications with multiple rows', async () => { // Create test metrics and dashboard const metricIds = await createTestMetrics(4); const dashboardId = await createTestDashboard(metricIds.slice(0, 2)); const complexDashboardYaml = ` name: Complex Updated Dashboard description: Dashboard with updated multiple rows and different layouts rows: - id: 1 items: - id: ${metricIds[0]} columnSizes: - 12 - id: 2 items: - id: ${metricIds[1]} - id: ${metricIds[2]} columnSizes: - 6 - 6 - id: 3 items: - id: ${metricIds[3]} columnSizes: - 12 `; const input = { files: [{ id: dashboardId, yml_content: complexDashboardYaml }], runtimeContext: mockRuntimeContext, }; const result = await modifyDashboardsFileTool.execute({ context: input }); expect(result.files).toHaveLength(1); expect(result.failed_files).toHaveLength(0); expect(result.files[0].name).toBe('Complex Updated Dashboard'); // Verify dashboard content in database const updatedDashboard = await db .select() .from(dashboardFiles) .where(eq(dashboardFiles.id, dashboardId)) .execute(); expect(updatedDashboard).toHaveLength(1); expect(updatedDashboard[0].content.rows).toHaveLength(3); expect(updatedDashboard[0].content.rows[0].items).toHaveLength(1); expect(updatedDashboard[0].content.rows[1].items).toHaveLength(2); expect(updatedDashboard[0].content.rows[2].items).toHaveLength(1); }); test('should generate appropriate success and error messages', async () => { // Test success message const metricIds = await createTestMetrics(1); const dashboardId = await createTestDashboard(metricIds); const validYaml = ` name: Success Message Test description: Test success message generation rows: - id: 1 items: - id: ${metricIds[0]} columnSizes: - 12 `; const successInput = { files: [{ id: dashboardId, yml_content: validYaml }], runtimeContext: mockRuntimeContext, }; const successResult = await modifyDashboardsFileTool.execute({ context: successInput }); expect(successResult.message).toBe('Successfully modified 1 dashboard file.'); // Test failure message const nonExistentId = randomUUID(); const failureInput = { files: [{ id: nonExistentId, yml_content: validYaml }], runtimeContext: mockRuntimeContext, }; const failureResult = await modifyDashboardsFileTool.execute({ context: failureInput }); expect(failureResult.message).toBe('Failed to modify 1 dashboard file.'); }); test('should preserve dashboard structure when modifying with same metric count', async () => { // Create test metrics and dashboard const metricIds = await createTestMetrics(2); const dashboardId = await createTestDashboard(metricIds); const preserveStructureYaml = ` name: Structure Preserved Dashboard description: Dashboard with preserved structure but updated content rows: - id: 1 items: - id: ${metricIds[0]} - id: ${metricIds[1]} columnSizes: - 6 - 6 `; const input = { files: [{ id: dashboardId, yml_content: preserveStructureYaml }], runtimeContext: mockRuntimeContext, }; const result = await modifyDashboardsFileTool.execute({ context: input }); expect(result.files).toHaveLength(1); expect(result.failed_files).toHaveLength(0); expect(result.files[0].name).toBe('Structure Preserved Dashboard'); // Verify database content const updatedDashboard = await db .select() .from(dashboardFiles) .where(eq(dashboardFiles.id, dashboardId)) .execute(); expect(updatedDashboard[0].content.rows[0].items).toHaveLength(2); expect(updatedDashboard[0].content.rows[0].columnSizes).toEqual([6, 6]); }); */ });