Skip to main content
Glama
validators.test.ts18 kB
import { describe, it, expect } from 'vitest'; import { EntityType, RelationshipType, type DatasetSchema } from '../../../src/types/schema.js'; import { isValidEmail, isValidUrl, isValidDate, isDateInPast, isValidPhone, isValidString, isInRange, isValidRegex, isNonEmptyArray, } from '../../../src/utils/validators.js'; /** * Unit tests for validation utilities */ describe('validators', () => { describe('existing validators', () => { describe('isValidEmail', () => { it('should validate correct email addresses', () => { expect(isValidEmail('test@example.com')).toBe(true); expect(isValidEmail('user.name@domain.co.uk')).toBe(true); expect(isValidEmail('first+last@company.com')).toBe(true); }); it('should reject invalid email addresses', () => { expect(isValidEmail('invalid')).toBe(false); expect(isValidEmail('@example.com')).toBe(false); expect(isValidEmail('test@')).toBe(false); expect(isValidEmail('test@.com')).toBe(false); }); }); describe('isValidUrl', () => { it('should validate correct URLs', () => { expect(isValidUrl('https://example.com')).toBe(true); expect(isValidUrl('http://test.com')).toBe(true); expect(isValidUrl('https://sub.domain.com/path')).toBe(true); }); it('should reject invalid URLs', () => { expect(isValidUrl('invalid')).toBe(false); expect(isValidUrl('ftp://example.com')).toBe(false); expect(isValidUrl('http://')).toBe(false); }); }); describe('isValidDate', () => { it('should validate correct date strings', () => { expect(isValidDate('2024-01-01')).toBe(true); expect(isValidDate('2024-12-31T23:59:59Z')).toBe(true); }); it('should reject invalid date strings', () => { expect(isValidDate('invalid')).toBe(false); expect(isValidDate('2024-13-01')).toBe(false); }); }); describe('isDateInPast', () => { it('should validate dates in the past', () => { expect(isDateInPast('2020-01-01')).toBe(true); expect(isDateInPast('1990-01-01')).toBe(true); }); it('should reject future dates', () => { expect(isDateInPast('2099-01-01')).toBe(false); }); }); describe('isValidPhone', () => { it('should validate phone numbers', () => { expect(isValidPhone('+1234567890')).toBe(true); expect(isValidPhone('123-456-7890')).toBe(true); expect(isValidPhone('(123) 456-7890')).toBe(true); }); it('should reject invalid phone numbers', () => { expect(isValidPhone('123')).toBe(false); expect(isValidPhone('abc')).toBe(false); }); }); describe('isValidString', () => { it('should validate non-empty strings', () => { expect(isValidString('test')).toBe(true); expect(isValidString('a')).toBe(true); }); it('should reject empty strings', () => { expect(isValidString('')).toBe(false); }); it('should respect max length', () => { expect(isValidString('test', 10)).toBe(true); expect(isValidString('test', 3)).toBe(false); }); }); describe('isInRange', () => { it('should validate numbers in range', () => { expect(isInRange(5, 0, 10)).toBe(true); expect(isInRange(0, 0, 10)).toBe(true); expect(isInRange(10, 0, 10)).toBe(true); }); it('should reject numbers out of range', () => { expect(isInRange(-1, 0, 10)).toBe(false); expect(isInRange(11, 0, 10)).toBe(false); }); }); describe('isValidRegex', () => { it('should validate correct regex patterns', () => { expect(isValidRegex('[a-z]+')).toBe(true); expect(isValidRegex('\\d{3}-\\d{3}-\\d{4}')).toBe(true); }); it('should reject invalid regex patterns', () => { expect(isValidRegex('[')).toBe(false); expect(isValidRegex('(unclosed')).toBe(false); }); }); describe('isNonEmptyArray', () => { it('should validate non-empty arrays', () => { expect(isNonEmptyArray([1, 2, 3])).toBe(true); expect(isNonEmptyArray(['a'])).toBe(true); }); it('should reject empty arrays', () => { expect(isNonEmptyArray([])).toBe(false); }); }); }); describe('dataset schema validation', () => { describe('validateDatasetSchema', () => { it('should validate schema with valid entities', () => { const schema: DatasetSchema = { entities: { users: { count: 10, type: EntityType.PERSON, }, }, }; // Will be implemented in T042 expect(schema).toBeDefined(); }); it('should validate schema with relationships', () => { const schema: DatasetSchema = { entities: { users: { count: 10, type: EntityType.PERSON, }, orders: { count: 50, type: EntityType.CUSTOM, fields: ['id', 'userId'], relationships: { userId: { references: 'users', type: RelationshipType.ONE_TO_MANY, }, }, }, }, }; expect(schema).toBeDefined(); }); it('should reject schema with non-existent entity references', () => { const schema: DatasetSchema = { entities: { orders: { count: 50, type: EntityType.CUSTOM, fields: ['id', 'userId'], relationships: { userId: { references: 'users', // users entity doesn't exist type: RelationshipType.ONE_TO_MANY, }, }, }, }, }; // Should throw error when validateDatasetSchema is called expect(schema).toBeDefined(); }); it('should reject schema with invalid entity counts', () => { const invalidSchemas = [ { entities: { users: { count: 0, // Too low type: EntityType.PERSON, }, }, }, { entities: { users: { count: 10001, // Too high type: EntityType.PERSON, }, }, }, { entities: { users: { count: -1, // Negative type: EntityType.PERSON, }, }, }, ]; invalidSchemas.forEach((schema) => { expect(schema).toBeDefined(); }); }); it('should validate empty entities object', () => { const schema: DatasetSchema = { entities: {}, }; // Should be rejected (no entities) expect(schema).toBeDefined(); }); it('should validate custom entities with fields', () => { const schema: DatasetSchema = { entities: { products: { count: 20, type: EntityType.CUSTOM, fields: ['id', 'name', 'price'], }, }, }; expect(schema).toBeDefined(); }); it('should reject custom entities without fields', () => { const schema: DatasetSchema = { entities: { products: { count: 20, type: EntityType.CUSTOM, // Missing fields }, }, }; // Should be rejected expect(schema).toBeDefined(); }); }); describe('detectCircularDependencies', () => { it('should detect simple circular dependency', () => { const schema: DatasetSchema = { entities: { users: { count: 10, type: EntityType.PERSON, relationships: { profileId: { references: 'profiles', type: RelationshipType.ONE_TO_MANY, }, }, }, profiles: { count: 10, type: EntityType.CUSTOM, fields: ['id', 'userId'], relationships: { userId: { references: 'users', type: RelationshipType.ONE_TO_MANY, }, }, }, }, }; // Should detect: users -> profiles -> users expect(schema).toBeDefined(); }); it('should detect complex circular dependency', () => { const schema: DatasetSchema = { entities: { a: { count: 10, type: EntityType.CUSTOM, fields: ['id', 'bId'], relationships: { bId: { references: 'b', type: RelationshipType.ONE_TO_MANY, }, }, }, b: { count: 10, type: EntityType.CUSTOM, fields: ['id', 'cId'], relationships: { cId: { references: 'c', type: RelationshipType.ONE_TO_MANY, }, }, }, c: { count: 10, type: EntityType.CUSTOM, fields: ['id', 'aId'], relationships: { aId: { references: 'a', type: RelationshipType.ONE_TO_MANY, }, }, }, }, }; // Should detect: a -> b -> c -> a expect(schema).toBeDefined(); }); it('should allow self-referential relationships', () => { const schema: DatasetSchema = { entities: { users: { count: 10, type: EntityType.PERSON, relationships: { managerId: { references: 'users', type: RelationshipType.ONE_TO_MANY, nullable: true, }, }, }, }, }; // Self-reference should be allowed with nullable expect(schema).toBeDefined(); }); it('should not detect false circular dependencies', () => { const schema: DatasetSchema = { entities: { users: { count: 10, type: EntityType.PERSON, }, orders: { count: 50, type: EntityType.CUSTOM, fields: ['id', 'userId'], relationships: { userId: { references: 'users', type: RelationshipType.ONE_TO_MANY, }, }, }, orderItems: { count: 100, type: EntityType.CUSTOM, fields: ['id', 'orderId'], relationships: { orderId: { references: 'orders', type: RelationshipType.ONE_TO_MANY, }, }, }, }, }; // No circular dependency: users -> orders -> orderItems expect(schema).toBeDefined(); }); it('should handle entities with no relationships', () => { const schema: DatasetSchema = { entities: { users: { count: 10, type: EntityType.PERSON, }, companies: { count: 5, type: EntityType.COMPANY, }, }, }; // No relationships, no circular dependencies expect(schema).toBeDefined(); }); it('should handle multiple relationships per entity', () => { const schema: DatasetSchema = { entities: { users: { count: 10, type: EntityType.PERSON, }, products: { count: 20, type: EntityType.CUSTOM, fields: ['id', 'name'], }, orders: { count: 50, type: EntityType.CUSTOM, fields: ['id', 'userId', 'productId'], relationships: { userId: { references: 'users', type: RelationshipType.ONE_TO_MANY, }, productId: { references: 'products', type: RelationshipType.ONE_TO_MANY, }, }, }, }, }; // No circular dependency expect(schema).toBeDefined(); }); }); describe('validateEntityCounts', () => { it('should validate count within range (1-10000)', () => { const validCounts = [1, 10, 100, 1000, 10000]; validCounts.forEach((count) => { expect(count).toBeGreaterThanOrEqual(1); expect(count).toBeLessThanOrEqual(10000); }); }); it('should reject count below minimum', () => { const invalidCounts = [0, -1, -100]; invalidCounts.forEach((count) => { expect(count).toBeLessThan(1); }); }); it('should reject count above maximum', () => { const invalidCounts = [10001, 20000, 100000]; invalidCounts.forEach((count) => { expect(count).toBeGreaterThan(10000); }); }); it('should validate all entity counts in schema', () => { const schema: DatasetSchema = { entities: { users: { count: 10, type: EntityType.PERSON, }, companies: { count: 5, type: EntityType.COMPANY, }, orders: { count: 50, type: EntityType.CUSTOM, fields: ['id'], }, }, }; // All counts should be valid Object.values(schema.entities).forEach((entity) => { expect(entity.count).toBeGreaterThanOrEqual(1); expect(entity.count).toBeLessThanOrEqual(10000); }); }); it('should handle entity count edge cases', () => { const edgeCases = [ { count: 1, valid: true }, { count: 10000, valid: true }, { count: 0, valid: false }, { count: 10001, valid: false }, ]; edgeCases.forEach(({ count, valid }) => { const isValid = count >= 1 && count <= 10000; expect(isValid).toBe(valid); }); }); }); }); describe('relationship validation', () => { it('should validate relationship references existing entities', () => { const schema: DatasetSchema = { entities: { users: { count: 10, type: EntityType.PERSON, }, orders: { count: 50, type: EntityType.CUSTOM, fields: ['id', 'userId'], relationships: { userId: { references: 'users', type: RelationshipType.ONE_TO_MANY, }, }, }, }, }; // users entity exists expect(schema.entities['users']).toBeDefined(); expect(schema.entities['orders']?.relationships?.['userId']?.references).toBe('users'); }); it('should validate relationship types', () => { const validTypes = [RelationshipType.ONE_TO_MANY, RelationshipType.MANY_TO_MANY]; validTypes.forEach((type) => { expect(Object.values(RelationshipType)).toContain(type); }); }); it('should validate nullable flag', () => { const schema: DatasetSchema = { entities: { users: { count: 10, type: EntityType.PERSON, }, orders: { count: 50, type: EntityType.CUSTOM, fields: ['id', 'userId'], relationships: { userId: { references: 'users', type: RelationshipType.ONE_TO_MANY, nullable: true, }, }, }, }, }; const nullable = schema.entities['orders']?.relationships?.['userId']?.nullable; expect(typeof nullable).toBe('boolean'); }); }); describe('entity type validation', () => { it('should validate entity type is valid enum value', () => { const validTypes = [EntityType.PERSON, EntityType.COMPANY, EntityType.CUSTOM]; validTypes.forEach((type) => { expect(Object.values(EntityType)).toContain(type); }); }); it('should validate CUSTOM entities have fields', () => { const schema: DatasetSchema = { entities: { products: { count: 20, type: EntityType.CUSTOM, fields: ['id', 'name', 'price'], }, }, }; const entity = schema.entities['products']; if (entity?.type === EntityType.CUSTOM) { expect(entity.fields).toBeDefined(); expect(Array.isArray(entity.fields)).toBe(true); } }); it('should allow PERSON and COMPANY entities without fields', () => { const schema: DatasetSchema = { entities: { users: { count: 10, type: EntityType.PERSON, // No fields required }, companies: { count: 5, type: EntityType.COMPANY, // No fields required }, }, }; expect(schema.entities['users']?.fields).toBeUndefined(); expect(schema.entities['companies']?.fields).toBeUndefined(); }); }); });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/funsjanssen/faker-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server