buster/packages/ai/tests/tools/unit/modify-dashboards-file-tool...

681 lines
21 KiB
TypeScript

import { describe, expect, test } from 'vitest';
import * as yaml from 'yaml';
import { z } from 'zod';
// Import the schemas we want to test (extracted from the tool file)
const dashboardItemSchema = z.object({
id: z.string().uuid('Must be a valid UUID for an existing metric'),
});
const dashboardRowSchema = z
.object({
id: z.number().int().positive('Row ID must be a positive integer'),
items: z
.array(dashboardItemSchema)
.min(1, 'Each row must have at least 1 item')
.max(4, 'Each row can have at most 4 items'),
columnSizes: z
.array(
z
.number()
.int()
.min(3, 'Each column size must be at least 3')
.max(12, 'Each column size cannot exceed 12')
)
.min(1, 'columnSizes array cannot be empty')
.refine((sizes) => sizes.reduce((sum, size) => sum + size, 0) === 12, {
message: 'Column sizes must sum to exactly 12',
}),
})
.refine((row) => row.items.length === row.columnSizes.length, {
message: 'Number of items must match number of column sizes',
});
const dashboardYmlSchema = z.object({
name: z.string().min(1, 'Dashboard name is required'),
description: z.string().min(1, 'Dashboard description is required'),
rows: z
.array(dashboardRowSchema)
.min(1, 'Dashboard must have at least one row')
.refine(
(rows) => {
const ids = rows.map((row) => row.id);
const uniqueIds = new Set(ids);
return ids.length === uniqueIds.size;
},
{
message: 'All row IDs must be unique',
}
),
});
// Parse and validate dashboard YAML content
function parseAndValidateYaml(ymlContent: string): {
success: boolean;
error?: string;
data?: z.infer<typeof dashboardYmlSchema>;
} {
try {
const parsedYml = yaml.parse(ymlContent);
const validationResult = dashboardYmlSchema.safeParse(parsedYml);
if (!validationResult.success) {
return {
success: false,
error: `Invalid YAML structure: ${validationResult.error.errors.map((e) => `${e.path.join('.')}: ${e.message}`).join(', ')}`,
};
}
return { success: true, data: validationResult.data };
} catch (error) {
return {
success: false,
error: error instanceof Error ? error.message : 'YAML parsing failed',
};
}
}
// Mock metric ID validation function for testing
function validateMetricIds(metricIds: string[]): {
success: boolean;
missingIds?: string[];
error?: string;
} {
// Mock implementation for unit testing
const validUUIDs = [
'f47ac10b-58cc-4372-a567-0e02b2c3d479',
'a47ac10b-58cc-4372-a567-0e02b2c3d480',
'550e8400-e29b-41d4-a716-446655440000',
'6ba7b810-9dad-11d1-80b4-00c04fd430c8',
];
const missingIds = metricIds.filter((id) => !validUUIDs.includes(id));
if (missingIds.length > 0) {
return { success: false, missingIds };
}
return { success: true };
}
describe('Modify Dashboards File Tool Unit Tests', () => {
describe('Dashboard YAML Schema Validation', () => {
test('should validate correct dashboard YAML for modification', () => {
const validDashboardYaml = `
name: Updated Sales Dashboard
description: An updated comprehensive view of sales metrics and performance
rows:
- id: 1
items:
- id: f47ac10b-58cc-4372-a567-0e02b2c3d479
columnSizes:
- 12
`;
const result = parseAndValidateYaml(validDashboardYaml);
expect(result.success).toBe(true);
expect(result.data?.name).toBe('Updated Sales Dashboard');
expect(result.data?.rows).toHaveLength(1);
expect(result.data?.rows?.[0]?.columnSizes).toEqual([12]);
});
test('should validate multi-row dashboard YAML modifications', () => {
const validMultiRowYaml = `
name: Updated Executive Dashboard
description: Enhanced high-level metrics for executive team
rows:
- id: 1
items:
- id: f47ac10b-58cc-4372-a567-0e02b2c3d479
- id: a47ac10b-58cc-4372-a567-0e02b2c3d480
columnSizes:
- 6
- 6
- id: 2
items:
- id: 550e8400-e29b-41d4-a716-446655440000
columnSizes:
- 12
`;
const result = parseAndValidateYaml(validMultiRowYaml);
expect(result.success).toBe(true);
expect(result.data?.rows).toHaveLength(2);
expect(result.data?.rows?.[0]?.items).toHaveLength(2);
expect(result.data?.rows?.[1]?.items).toHaveLength(1);
});
test('should validate dashboard with modified layout to maximum 4 items per row', () => {
const maxItemsYaml = `
name: Updated Detailed Dashboard
description: Modified dashboard with maximum items per row
rows:
- id: 1
items:
- id: f47ac10b-58cc-4372-a567-0e02b2c3d479
- id: a47ac10b-58cc-4372-a567-0e02b2c3d480
- id: 550e8400-e29b-41d4-a716-446655440000
- id: 6ba7b810-9dad-11d1-80b4-00c04fd430c8
columnSizes:
- 3
- 3
- 3
- 3
`;
const result = parseAndValidateYaml(maxItemsYaml);
expect(result.success).toBe(true);
expect(result.data?.rows?.[0]?.items).toHaveLength(4);
expect(result.data?.rows?.[0]?.columnSizes).toEqual([3, 3, 3, 3]);
});
test('should reject modified dashboard with missing required fields', () => {
const missingFieldsYaml = `
name: Incomplete Modified Dashboard
# Missing description and rows
`;
const result = parseAndValidateYaml(missingFieldsYaml);
expect(result.success).toBe(false);
expect(result.error).toContain('Invalid YAML structure');
});
test('should reject modified dashboard with invalid column sizes', () => {
const invalidColumnSizesYaml = `
name: Invalid Modified Dashboard
description: Modified dashboard with invalid column sizes
rows:
- id: 1
items:
- id: f47ac10b-58cc-4372-a567-0e02b2c3d479
- id: a47ac10b-58cc-4372-a567-0e02b2c3d480
columnSizes:
- 4
- 4
`;
const result = parseAndValidateYaml(invalidColumnSizesYaml);
expect(result.success).toBe(false);
expect(result.error).toContain('Column sizes must sum to exactly 12');
});
test('should reject modified dashboard with column size less than 3', () => {
const tooSmallColumnYaml = `
name: Updated Small Column Dashboard
description: Modified dashboard with column size less than 3
rows:
- id: 1
items:
- id: f47ac10b-58cc-4372-a567-0e02b2c3d479
- id: a47ac10b-58cc-4372-a567-0e02b2c3d480
columnSizes:
- 2
- 10
`;
const result = parseAndValidateYaml(tooSmallColumnYaml);
expect(result.success).toBe(false);
expect(result.error).toContain('Each column size must be at least 3');
});
test('should reject modified dashboard with more than 4 items per row', () => {
const tooManyItemsYaml = `
name: Updated Too Many Items Dashboard
description: Modified dashboard with more than 4 items per row
rows:
- id: 1
items:
- id: f47ac10b-58cc-4372-a567-0e02b2c3d479
- id: a47ac10b-58cc-4372-a567-0e02b2c3d480
- id: 550e8400-e29b-41d4-a716-446655440000
- id: 6ba7b810-9dad-11d1-80b4-00c04fd430c8
- id: f47ac10b-58cc-4372-a567-0e02b2c3d555
columnSizes:
- 3
- 3
- 2
- 2
- 2
`;
const result = parseAndValidateYaml(tooManyItemsYaml);
expect(result.success).toBe(false);
expect(result.error).toContain('Each row can have at most 4 items');
});
test('should reject modified dashboard with mismatched items and column sizes', () => {
const mismatchedCountYaml = `
name: Updated Mismatched Count Dashboard
description: Modified dashboard with mismatched items and column sizes
rows:
- id: 1
items:
- id: f47ac10b-58cc-4372-a567-0e02b2c3d479
- id: a47ac10b-58cc-4372-a567-0e02b2c3d480
columnSizes:
- 4
- 4
- 4
`;
const result = parseAndValidateYaml(mismatchedCountYaml);
expect(result.success).toBe(false);
expect(result.error).toContain('Number of items must match number of column sizes');
});
test('should reject modified dashboard with duplicate row IDs', () => {
const duplicateRowIdsYaml = `
name: Updated Duplicate Row IDs Dashboard
description: Modified dashboard with duplicate row IDs
rows:
- id: 1
items:
- id: f47ac10b-58cc-4372-a567-0e02b2c3d479
columnSizes:
- 12
- id: 1
items:
- id: a47ac10b-58cc-4372-a567-0e02b2c3d480
columnSizes:
- 12
`;
const result = parseAndValidateYaml(duplicateRowIdsYaml);
expect(result.success).toBe(false);
expect(result.error).toContain('All row IDs must be unique');
});
test('should reject modified dashboard with invalid metric UUID format', () => {
const invalidUuidYaml = `
name: Updated Invalid UUID Dashboard
description: Modified dashboard with invalid UUID format
rows:
- id: 1
items:
- id: not-a-valid-uuid
columnSizes:
- 12
`;
const result = parseAndValidateYaml(invalidUuidYaml);
expect(result.success).toBe(false);
expect(result.error).toContain('Must be a valid UUID');
});
test('should reject modified dashboard with non-positive row ID', () => {
const invalidRowIdYaml = `
name: Updated Invalid Row ID Dashboard
description: Modified dashboard with invalid row ID
rows:
- id: 0
items:
- id: f47ac10b-58cc-4372-a567-0e02b2c3d479
columnSizes:
- 12
`;
const result = parseAndValidateYaml(invalidRowIdYaml);
expect(result.success).toBe(false);
expect(result.error).toContain('Row ID must be a positive integer');
});
test('should reject modified dashboard with no rows', () => {
const noRowsYaml = `
name: Updated No Rows Dashboard
description: Modified dashboard with no rows
rows: []
`;
const result = parseAndValidateYaml(noRowsYaml);
expect(result.success).toBe(false);
expect(result.error).toContain('Dashboard must have at least one row');
});
});
describe('Metric ID Validation for Modifications', () => {
test('should accept valid metric IDs in modified dashboard', () => {
const validIds = [
'f47ac10b-58cc-4372-a567-0e02b2c3d479',
'a47ac10b-58cc-4372-a567-0e02b2c3d480',
];
const result = validateMetricIds(validIds);
expect(result.success).toBe(true);
});
test('should reject invalid metric IDs in modified dashboard', () => {
const invalidIds = ['f47ac10b-58cc-4372-a567-0e02b2c3d479', 'non-existent-id'];
const result = validateMetricIds(invalidIds);
expect(result.success).toBe(false);
expect(result.missingIds).toEqual(['non-existent-id']);
});
test('should handle empty metric IDs array in modified dashboard', () => {
const result = validateMetricIds([]);
expect(result.success).toBe(true);
});
test('should identify multiple missing IDs in modified dashboard', () => {
const invalidIds = ['missing-1', 'missing-2', 'f47ac10b-58cc-4372-a567-0e02b2c3d479'];
const result = validateMetricIds(invalidIds);
expect(result.success).toBe(false);
expect(result.missingIds).toEqual(['missing-1', 'missing-2']);
});
});
describe('Input Schema Validation for Updates', () => {
test('should validate correct update input format', () => {
const validInput = {
files: [
{
id: 'f47ac10b-58cc-4372-a567-0e02b2c3d479',
yml_content:
'name: Updated Dashboard\\ndescription: Updated Test\\nrows:\\n - id: 1\\n items:\\n - id: f47ac10b-58cc-4372-a567-0e02b2c3d479\\n columnSizes:\\n - 12',
},
],
};
// Basic validation that files array exists and has proper structure
expect(validInput.files).toHaveLength(1);
expect(validInput.files[0]?.id).toBe('f47ac10b-58cc-4372-a567-0e02b2c3d479');
expect(typeof validInput.files[0]?.yml_content).toBe('string');
});
test('should reject empty files array for updates', () => {
const invalidInput = { files: [] };
// This would fail our minimum length validation
expect(invalidInput.files).toHaveLength(0);
});
test('should reject update input without ID', () => {
const invalidInput = {
files: [
{
// Missing id
yml_content: 'name: Updated Test',
},
],
};
expect(invalidInput.files?.[0]).not.toHaveProperty('id');
});
test('should reject update input without yml_content', () => {
const invalidInput = {
files: [
{
id: 'f47ac10b-58cc-4372-a567-0e02b2c3d479',
// Missing yml_content
},
],
};
expect(invalidInput.files?.[0]).not.toHaveProperty('yml_content');
});
test('should validate bulk update input format', () => {
const bulkInput = {
files: [
{
id: 'f47ac10b-58cc-4372-a567-0e02b2c3d479',
yml_content:
'name: First Updated Dashboard\\ndescription: First Update\\nrows:\\n - id: 1\\n items:\\n - id: f47ac10b-58cc-4372-a567-0e02b2c3d479\\n columnSizes:\\n - 12',
},
{
id: 'a47ac10b-58cc-4372-a567-0e02b2c3d480',
yml_content:
'name: Second Updated Dashboard\\ndescription: Second Update\\nrows:\\n - id: 1\\n items:\\n - id: a47ac10b-58cc-4372-a567-0e02b2c3d480\\n columnSizes:\\n - 12',
},
],
};
expect(bulkInput.files).toHaveLength(2);
expect(bulkInput.files.every((f) => f.id && f.yml_content)).toBe(true);
});
test('should reject invalid UUID format in ID field', () => {
const invalidUuidInput = {
files: [
{
id: 'not-a-valid-uuid',
yml_content: 'name: Test Dashboard',
},
],
};
// This would fail UUID validation
expect(invalidUuidInput.files[0]?.id).toBe('not-a-valid-uuid');
});
});
describe('Dashboard Modification Schema Validation', () => {
test('should validate modified single item row', () => {
const singleItemRow = {
id: 1,
items: [{ id: 'f47ac10b-58cc-4372-a567-0e02b2c3d479' }],
columnSizes: [12],
};
const result = dashboardRowSchema.safeParse(singleItemRow);
expect(result.success).toBe(true);
});
test('should validate modified two item row', () => {
const twoItemRow = {
id: 2,
items: [
{ id: 'f47ac10b-58cc-4372-a567-0e02b2c3d479' },
{ id: 'a47ac10b-58cc-4372-a567-0e02b2c3d480' },
],
columnSizes: [6, 6],
};
const result = dashboardRowSchema.safeParse(twoItemRow);
expect(result.success).toBe(true);
});
test('should validate modified three item row', () => {
const threeItemRow = {
id: 3,
items: [
{ id: 'f47ac10b-58cc-4372-a567-0e02b2c3d479' },
{ id: 'a47ac10b-58cc-4372-a567-0e02b2c3d480' },
{ id: '550e8400-e29b-41d4-a716-446655440000' },
],
columnSizes: [4, 4, 4],
};
const result = dashboardRowSchema.safeParse(threeItemRow);
expect(result.success).toBe(true);
});
test('should validate modified four item row', () => {
const fourItemRow = {
id: 4,
items: [
{ id: 'f47ac10b-58cc-4372-a567-0e02b2c3d479' },
{ id: 'a47ac10b-58cc-4372-a567-0e02b2c3d480' },
{ id: '550e8400-e29b-41d4-a716-446655440000' },
{ id: '6ba7b810-9dad-11d1-80b4-00c04fd430c8' },
],
columnSizes: [3, 3, 3, 3],
};
const result = dashboardRowSchema.safeParse(fourItemRow);
expect(result.success).toBe(true);
});
test('should reject modified row with invalid column size sum', () => {
const invalidSumRow = {
id: 1,
items: [{ id: 'f47ac10b-58cc-4372-a567-0e02b2c3d479' }],
columnSizes: [10], // Should be 12
};
const result = dashboardRowSchema.safeParse(invalidSumRow);
expect(result.success).toBe(false);
});
});
describe('Error Message Generation for Modifications', () => {
test('should generate appropriate error message for invalid YAML in modification', () => {
const invalidYaml = 'invalid: yaml: [structure';
const result = parseAndValidateYaml(invalidYaml);
expect(result.success).toBe(false);
expect(result.error).toBeDefined();
expect(typeof result.error).toBe('string');
});
test('should generate appropriate error message for metric validation in modification', () => {
const invalidIds = ['missing-metric-id'];
const result = validateMetricIds(invalidIds);
expect(result.success).toBe(false);
expect(result.missingIds).toEqual(['missing-metric-id']);
});
test('should handle complex validation errors in modification', () => {
const complexInvalidYaml = `
name: Complex Invalid Modified Dashboard
description: Modified dashboard with multiple validation errors
rows:
- id: 1
items:
- id: invalid-uuid
- id: f47ac10b-58cc-4372-a567-0e02b2c3d479
columnSizes:
- 2
- 8
`;
const result = parseAndValidateYaml(complexInvalidYaml);
expect(result.success).toBe(false);
expect(result.error).toContain('Invalid YAML structure');
});
});
describe('Dashboard Item Schema Validation for Modifications', () => {
test('should validate correct dashboard item in modification', () => {
const validItem = {
id: 'f47ac10b-58cc-4372-a567-0e02b2c3d479',
};
const result = dashboardItemSchema.safeParse(validItem);
expect(result.success).toBe(true);
});
test('should reject dashboard item with invalid UUID in modification', () => {
const invalidItem = {
id: 'not-a-uuid',
};
const result = dashboardItemSchema.safeParse(invalidItem);
expect(result.success).toBe(false);
});
test('should reject dashboard item without ID in modification', () => {
const itemWithoutId = {};
const result = dashboardItemSchema.safeParse(itemWithoutId);
expect(result.success).toBe(false);
});
});
describe('Column Size Edge Cases for Modifications', () => {
test('should accept valid column size combinations in modifications', () => {
const validCombinations = [[12], [6, 6], [4, 4, 4], [3, 3, 3, 3], [3, 9], [4, 8], [5, 7]];
for (const columnSizes of validCombinations) {
const sum = columnSizes.reduce((a, b) => a + b, 0);
expect(sum).toBe(12);
const allValid = columnSizes.every((size) => size >= 3 && size <= 12);
expect(allValid).toBe(true);
}
});
test('should reject invalid column size combinations in modifications', () => {
const invalidCombinations = [
[13], // Too large
[11], // Sum not 12
[2, 10], // Size too small
[6, 6, 1], // Size too small, sum not 12
[1, 1, 10], // Sizes too small
[15], // Size too large
];
for (const columnSizes of invalidCombinations) {
const sum = columnSizes.reduce((a, b) => a + b, 0);
const hasInvalidSize = columnSizes.some((size) => size < 3 || size > 12);
const invalidSum = sum !== 12;
expect(hasInvalidSize || invalidSum).toBe(true);
}
});
});
describe('Version History and Modification Context', () => {
test('should handle modification scenarios where name changes', () => {
const originalName = 'Original Dashboard';
const updatedYaml = `
name: Completely Renamed Dashboard
description: This dashboard has been renamed
rows:
- id: 1
items:
- id: f47ac10b-58cc-4372-a567-0e02b2c3d479
columnSizes:
- 12
`;
const result = parseAndValidateYaml(updatedYaml);
expect(result.success).toBe(true);
expect(result.data?.name).toBe('Completely Renamed Dashboard');
expect(result.data?.name).not.toBe(originalName);
});
test('should handle modification scenarios where description changes', () => {
const updatedYaml = `
name: Sales Dashboard
description: Updated and enhanced view of sales metrics with new features
rows:
- id: 1
items:
- id: f47ac10b-58cc-4372-a567-0e02b2c3d479
columnSizes:
- 12
`;
const result = parseAndValidateYaml(updatedYaml);
expect(result.success).toBe(true);
expect(result.data?.description).toContain('Updated and enhanced');
});
test('should handle modification scenarios where row structure changes', () => {
const restructuredYaml = `
name: Restructured Dashboard
description: Dashboard with completely new row structure
rows:
- id: 1
items:
- id: f47ac10b-58cc-4372-a567-0e02b2c3d479
- id: a47ac10b-58cc-4372-a567-0e02b2c3d480
columnSizes:
- 4
- 8
- id: 2
items:
- id: 550e8400-e29b-41d4-a716-446655440000
columnSizes:
- 12
`;
const result = parseAndValidateYaml(restructuredYaml);
expect(result.success).toBe(true);
expect(result.data?.rows).toHaveLength(2);
expect(result.data?.rows?.[0]?.columnSizes).toEqual([4, 8]);
expect(result.data?.rows?.[1]?.columnSizes).toEqual([12]);
});
});
});