diff --git a/apps/docs/next.config.mjs b/apps/docs/next.config.mjs index 62d896a..622c082 100644 --- a/apps/docs/next.config.mjs +++ b/apps/docs/next.config.mjs @@ -18,12 +18,20 @@ const config = { }, ], }, + experimental: { + turbo: { + resolveAlias: { + 'fumadocs-ui/components/callout': 'fumadocs-ui/dist/components/callout.js', + }, + }, + }, webpack: (config, { isServer }) => { // Resolve the fumadocs virtual collection import to the local .source directory config.resolve = config.resolve || {}; config.resolve.alias = { ...(config.resolve.alias || {}), 'fumadocs-mdx:collections': path.resolve(__dirname, '.source'), + 'fumadocs-ui/components/callout$': path.resolve(__dirname, '../../node_modules/fumadocs-ui/dist/components/callout.js'), }; return config; }, diff --git a/apps/docs/package.json b/apps/docs/package.json index 8f9e1f4..b9e62e5 100644 --- a/apps/docs/package.json +++ b/apps/docs/package.json @@ -5,7 +5,7 @@ "description": "ObjectStack Protocol Documentation Site", "scripts": { "site:dev": "next dev", - "build": "next build", + "build": "NEXT_PRIVATE_BUILD_WORKER=1 next build", "site:start": "next start", "site:lint": "next lint" }, diff --git a/content/docs/guides/field-types.cn.mdx b/content/docs/guides/field-types.cn.mdx index df623d3..d149ad6 100644 --- a/content/docs/guides/field-types.cn.mdx +++ b/content/docs/guides/field-types.cn.mdx @@ -3,7 +3,6 @@ title: 字段类型参考 description: ObjectStack 所有字段类型的完整指南,包含示例和配置选项 --- -import { Callout } from 'fumadocs-ui/components/callout'; # 字段类型参考 @@ -66,9 +65,9 @@ email: Field.email({ }) ``` - +> ℹ️ **Info:** 自动验证邮箱格式:`user@domain.com` - + --- @@ -107,9 +106,9 @@ api_key: Field.password({ }) ``` - +> ⚠️ **Warning:** 密码字段在 UI 中自动掩码,如果 `encryption: true` 则加密存储 - + --- @@ -137,9 +136,9 @@ html_content: Field.html({ }) ``` - +> ⚠️ **Warning:** 渲染前务必净化 HTML 内容,防止 XSS 攻击 - + --- @@ -370,11 +369,11 @@ account: Field.masterDetail('account', { }) ``` - +> ℹ️ **Info:** **主从关系 vs 查找:** - **主从关系:** 紧密耦合,级联删除,子记录继承安全性 - **查找:** 松散耦合,删除时置空,独立安全性 - + --- @@ -451,9 +450,9 @@ days_open: Field.formula({ }) ``` - +> ℹ️ **Info:** 公式字段自动计算且只读。可用函数请参见 [公式函数](/docs/references/data/formulas)。 - + --- diff --git a/content/docs/guides/field-types.mdx b/content/docs/guides/field-types.mdx index be8a9c3..b088c75 100644 --- a/content/docs/guides/field-types.mdx +++ b/content/docs/guides/field-types.mdx @@ -3,7 +3,6 @@ title: Field Types Reference description: Complete guide to all ObjectStack field types with examples and configuration options --- -import { Callout } from 'fumadocs-ui/components/callout'; # Field Types Reference @@ -66,9 +65,9 @@ email: Field.email({ }) ``` - +> ℹ️ **Info:** Automatically validates email format: `user@domain.com` - + --- @@ -107,9 +106,9 @@ api_key: Field.password({ }) ``` - +> ⚠️ **Warning:** Password fields are automatically masked in UI and encrypted if `encryption: true` - + --- @@ -137,9 +136,9 @@ html_content: Field.html({ }) ``` - +> ⚠️ **Warning:** Always sanitize HTML content before rendering to prevent XSS attacks - + --- @@ -370,11 +369,11 @@ account: Field.masterDetail('account', { }) ``` - +> ℹ️ **Info:** **Master-Detail vs Lookup:** - **Master-Detail:** Tight coupling, cascade deletes, child inherits security - **Lookup:** Loose coupling, set null on delete, independent security - + --- @@ -451,9 +450,9 @@ days_open: Field.formula({ }) ``` - +> ℹ️ **Info:** Formula fields are automatically calculated and readonly. See [Formula Functions](/docs/references/data/formulas) for available functions. - + --- diff --git a/content/docs/guides/view-configuration.mdx b/content/docs/guides/view-configuration.mdx index dad717a..b8fd77b 100644 --- a/content/docs/guides/view-configuration.mdx +++ b/content/docs/guides/view-configuration.mdx @@ -3,7 +3,6 @@ title: View Configuration description: Complete guide to configuring Grid, Kanban, Calendar, Gantt views and forms in ObjectStack --- -import { Callout } from 'fumadocs-ui/components/callout'; # View Configuration Guide diff --git a/content/docs/guides/workflows-validation.mdx b/content/docs/guides/workflows-validation.mdx index 8d1fbe3..9872f2c 100644 --- a/content/docs/guides/workflows-validation.mdx +++ b/content/docs/guides/workflows-validation.mdx @@ -3,7 +3,6 @@ title: Validation Rules & Workflows description: Complete guide to validation rules and workflow automation in ObjectStack --- -import { Callout } from 'fumadocs-ui/components/callout'; # Validation Rules & Workflows @@ -73,9 +72,9 @@ export const Opportunity = ObjectSchema.create({ }); ``` - +> ℹ️ **Info:** **Important:** The `condition` is inverted logic - it defines when validation **FAILS**. If the condition evaluates to `TRUE`, the validation error is shown. - + ### Expression Examples diff --git a/content/docs/index.cn.mdx b/content/docs/index.cn.mdx index e76466e..e2e3fbd 100644 --- a/content/docs/index.cn.mdx +++ b/content/docs/index.cn.mdx @@ -23,7 +23,7 @@ import { Book, Compass, FileText, Layers } from 'lucide-react'; icon={} title="概念" href="/docs/concepts/manifesto" - description="理解"意图优于实现"和"本地优先"架构的理念。" + description="理解「意图优于实现」和「本地优先」架构的理念。" /> } diff --git a/packages/spec/src/data/query.test.ts b/packages/spec/src/data/query.test.ts index 281e85e..fe38c95 100644 --- a/packages/spec/src/data/query.test.ts +++ b/packages/spec/src/data/query.test.ts @@ -1617,3 +1617,341 @@ describe('QuerySchema - Complex Queries', () => { expect(() => QuerySchema.parse(query)).not.toThrow(); }); }); + +describe('QuerySchema - Edge Cases and Null Handling', () => { + it('should handle null values in filter expressions', () => { + const query: QueryAST = { + object: 'account', + fields: ['name'], + filters: ['deleted_at', 'is_null', null], + }; + + expect(() => QuerySchema.parse(query)).not.toThrow(); + }); + + it('should handle undefined for optional fields', () => { + const query: QueryAST = { + object: 'account', + fields: undefined, + filters: undefined, + sort: undefined, + aggregations: undefined, + joins: undefined, + groupBy: undefined, + having: undefined, + windowFunctions: undefined, + }; + + expect(() => QuerySchema.parse(query)).not.toThrow(); + }); + + it('should handle empty arrays', () => { + const query: QueryAST = { + object: 'account', + fields: [], + aggregations: [], + joins: [], + windowFunctions: [], + groupBy: [], + sort: [], + }; + + expect(() => QuerySchema.parse(query)).not.toThrow(); + }); + + it('should handle zero and negative values in pagination', () => { + const queries = [ + { object: 'account', top: 0, skip: 0 }, + { object: 'account', top: 1, skip: 0 }, + { object: 'account', top: 100, skip: 1000 }, + ]; + + queries.forEach(query => { + expect(() => QuerySchema.parse(query)).not.toThrow(); + }); + }); + + it('should handle complex nested null filters', () => { + const query: QueryAST = { + object: 'order', + fields: ['id'], + filters: [ + ['approved_at', 'is_null', null], + 'and', + ['rejected_at', 'is_null', null], + ], + }; + + expect(() => QuerySchema.parse(query)).not.toThrow(); + }); + + it('should handle optional alias in field nodes', () => { + const query: QueryAST = { + object: 'account', + fields: [ + 'name', + { field: 'owner', fields: ['name', 'email'] }, + { field: 'manager', fields: ['name'], alias: 'mgr' }, + ], + }; + + expect(() => QuerySchema.parse(query)).not.toThrow(); + }); + + it('should handle aggregation without field for COUNT', () => { + const query: QueryAST = { + object: 'order', + aggregations: [ + { function: 'count', alias: 'total_count' }, + ], + }; + + expect(() => QuerySchema.parse(query)).not.toThrow(); + }); + + it('should handle optional distinct flag in aggregation', () => { + const query: QueryAST = { + object: 'order', + aggregations: [ + { function: 'count', field: 'customer_id', alias: 'unique_customers', distinct: true }, + { function: 'sum', field: 'amount', alias: 'total_amount' }, // distinct undefined + ], + groupBy: ['region'], + }; + + expect(() => QuerySchema.parse(query)).not.toThrow(); + }); + + it('should handle optional properties in window functions', () => { + const query: QueryAST = { + object: 'sales', + fields: ['amount'], + windowFunctions: [ + { + function: 'row_number', + alias: 'row_num', + over: { + // partitionBy and orderBy are optional + }, + }, + { + function: 'sum', + field: 'amount', + alias: 'total', + over: { + partitionBy: ['region'], + // orderBy is optional + }, + }, + ], + }; + + expect(() => QuerySchema.parse(query)).not.toThrow(); + }); + + it('should handle optional frame in window specification', () => { + const query: QueryAST = { + object: 'transactions', + fields: ['amount'], + windowFunctions: [ + { + function: 'sum', + field: 'amount', + alias: 'running_total', + over: { + orderBy: [{ field: 'date', order: 'asc' }], + frame: { + type: 'rows', + start: 'UNBOUNDED PRECEDING', + end: 'CURRENT ROW', + }, + }, + }, + ], + }; + + expect(() => QuerySchema.parse(query)).not.toThrow(); + }); + + it('should handle optional subquery in joins', () => { + const query: QueryAST = { + object: 'customer', + joins: [ + { + type: 'left', + object: 'order', + on: ['customer.id', '=', 'order.customer_id'], + }, + { + type: 'inner', + object: 'filtered_orders', + alias: 'fo', + on: ['customer.id', '=', 'fo.customer_id'], + subquery: { + object: 'order', + fields: ['customer_id', 'amount'], + filters: ['amount', '>', 1000], + }, + }, + ], + }; + + expect(() => QuerySchema.parse(query)).not.toThrow(); + }); + + it('should reject invalid object type', () => { + expect(() => QuerySchema.parse({ + object: 123, // Should be string + fields: ['name'], + })).toThrow(); + }); + + it('should reject invalid field types in array', () => { + expect(() => QuerySchema.parse({ + object: 'account', + fields: [123, 456], // Should be strings or objects + })).toThrow(); + }); + + it('should reject invalid aggregation function', () => { + expect(() => QuerySchema.parse({ + object: 'order', + aggregations: [ + { function: 'invalid_func', alias: 'test' }, + ], + })).toThrow(); + }); + + it('should reject invalid join type', () => { + expect(() => QuerySchema.parse({ + object: 'order', + joins: [ + { + type: 'invalid_join', + object: 'customer', + on: ['order.customer_id', '=', 'customer.id'], + }, + ], + })).toThrow(); + }); + + it('should reject invalid window function', () => { + expect(() => QuerySchema.parse({ + object: 'sales', + windowFunctions: [ + { + function: 'invalid_window_func', + alias: 'test', + over: {}, + }, + ], + })).toThrow(); + }); + + it('should reject invalid sort order', () => { + expect(() => QuerySchema.parse({ + object: 'account', + sort: [{ field: 'name', order: 'invalid' }], + })).toThrow(); + }); +}); + +describe('QuerySchema - Type Coercion Edge Cases', () => { + it('should handle various data types in filter values', () => { + const queries = [ + { object: 'account', filters: ['age', '>', 18] }, // number + { object: 'account', filters: ['active', '=', true] }, // boolean + { object: 'account', filters: ['name', '=', 'John'] }, // string + { object: 'account', filters: ['tags', 'in', ['a', 'b', 'c']] }, // array + { object: 'account', filters: ['value', 'between', [0, 100]] }, // array + ]; + + queries.forEach(query => { + expect(() => QuerySchema.parse(query)).not.toThrow(); + }); + }); + + it('should handle boolean flags', () => { + const query: QueryAST = { + object: 'account', + fields: ['name'], + distinct: true, + }; + + expect(() => QuerySchema.parse(query)).not.toThrow(); + + const query2: QueryAST = { + object: 'account', + fields: ['name'], + distinct: false, + }; + + expect(() => QuerySchema.parse(query2)).not.toThrow(); + }); + + it('should handle default sort order', () => { + const query: QueryAST = { + object: 'account', + sort: [{ field: 'name' }], // order defaults to 'asc' + }; + + const result = QuerySchema.parse(query); + expect(result.sort?.[0].order).toBe('asc'); + }); + + it('should handle mixed field types', () => { + const query: QueryAST = { + object: 'account', + fields: [ + 'simple_field', + { + field: 'related_field', + fields: ['nested_field'], + alias: 'rel', + }, + ], + }; + + expect(() => QuerySchema.parse(query)).not.toThrow(); + }); + + it('should handle deeply nested filters', () => { + const query: QueryAST = { + object: 'order', + filters: [ + [ + ['status', '=', 'active'], + 'and', + ['amount', '>', 100], + ], + 'or', + [ + ['priority', '=', 'high'], + 'and', + ['urgent', '=', true], + ], + ], + }; + + expect(() => QuerySchema.parse(query)).not.toThrow(); + }); + + it('should handle complex having clauses', () => { + const query: QueryAST = { + object: 'order', + fields: ['customer_id'], + aggregations: [ + { function: 'count', alias: 'order_count' }, + { function: 'sum', field: 'amount', alias: 'total' }, + ], + groupBy: ['customer_id'], + having: [ + ['order_count', '>', 5], + 'and', + ['total', '>', 1000], + ], + }; + + expect(() => QuerySchema.parse(query)).not.toThrow(); + }); +}); diff --git a/packages/spec/src/data/validation.test.ts b/packages/spec/src/data/validation.test.ts index 159e418..fd3b368 100644 --- a/packages/spec/src/data/validation.test.ts +++ b/packages/spec/src/data/validation.test.ts @@ -5,6 +5,10 @@ import { UniquenessValidationSchema, StateMachineValidationSchema, FormatValidationSchema, + CrossFieldValidationSchema, + AsyncValidationSchema, + CustomValidatorSchema, + ConditionalValidationSchema, type ValidationRule, } from './validation.zod'; @@ -1077,3 +1081,454 @@ describe('ValidationRuleSchema (Discriminated Union)', () => { }); }); }); + +describe('ValidationRuleSchema - Edge Cases and Null Handling', () => { + it('should handle null and undefined in optional fields', () => { + const validation = { + type: 'script' as const, + name: 'test_validation', + message: 'Test message', + condition: 'amount > 0', + active: undefined, // Should default to true + severity: undefined, // Should default to 'error' + }; + + const result = ScriptValidationSchema.parse(validation); + expect(result.active).toBe(true); + expect(result.severity).toBe('error'); + }); + + it('should handle empty arrays in UniquenessValidation', () => { + expect(() => UniquenessValidationSchema.parse({ + type: 'unique', + name: 'test_unique', + message: 'Must be unique', + fields: [], // Empty array should be valid but probably not useful + })).not.toThrow(); + }); + + it('should handle undefined scope in UniquenessValidation', () => { + const validation = { + type: 'unique' as const, + name: 'unique_email', + message: 'Email must be unique', + fields: ['email'], + scope: undefined, + caseSensitive: undefined, // Should default to true + }; + + const result = UniquenessValidationSchema.parse(validation); + expect(result.caseSensitive).toBe(true); + }); + + it('should handle empty state transitions', () => { + const validation = { + type: 'state_machine' as const, + name: 'state_validation', + message: 'Invalid state transition', + field: 'status', + transitions: { + 'draft': [], + 'published': ['draft', 'archived'], + }, + }; + + expect(() => StateMachineValidationSchema.parse(validation)).not.toThrow(); + }); + + it('should handle undefined regex and format in FormatValidation', () => { + const validation = { + type: 'format' as const, + name: 'format_check', + message: 'Invalid format', + field: 'email', + format: 'email' as const, + regex: undefined, + }; + + expect(() => FormatValidationSchema.parse(validation)).not.toThrow(); + }); + + it('should handle various format types', () => { + const formats = ['email', 'url', 'phone', 'json'] as const; + + formats.forEach(format => { + const validation = { + type: 'format' as const, + name: `format_${format}`, + message: `Invalid ${format}`, + field: 'test_field', + format, + }; + + expect(() => FormatValidationSchema.parse(validation)).not.toThrow(); + }); + }); + + it('should handle empty fields array in CrossFieldValidation', () => { + const validation = { + type: 'cross_field' as const, + name: 'test_cross_field', + message: 'Validation failed', + condition: 'true', + fields: [], // Empty array + }; + + expect(() => CrossFieldValidationSchema.parse(validation)).not.toThrow(); + }); + + it('should handle undefined optional fields in AsyncValidation', () => { + const validation = { + type: 'async' as const, + name: 'async_validation', + message: 'Validation failed', + field: 'email', + validatorUrl: '/api/validate', + validatorFunction: undefined, + debounce: undefined, + params: undefined, + }; + + const result = AsyncValidationSchema.parse(validation); + expect(result.timeout).toBe(5000); // Default timeout + }); + + it('should handle custom timeout in AsyncValidation', () => { + const validation = { + type: 'async' as const, + name: 'async_validation', + message: 'Validation failed', + field: 'email', + validatorUrl: '/api/validate', + timeout: 10000, + }; + + const result = AsyncValidationSchema.parse(validation); + expect(result.timeout).toBe(10000); + }); + + it('should handle undefined field in CustomValidator', () => { + const validation = { + type: 'custom' as const, + name: 'custom_validation', + message: 'Validation failed', + field: undefined, // Optional for record-level validation + validatorFunction: 'validateRecord', + params: undefined, + }; + + expect(() => CustomValidatorSchema.parse(validation)).not.toThrow(); + }); + + it('should handle empty params object', () => { + const validation = { + type: 'custom' as const, + name: 'custom_validation', + message: 'Validation failed', + validatorFunction: 'validateRecord', + params: {}, + }; + + expect(() => CustomValidatorSchema.parse(validation)).not.toThrow(); + }); + + it('should handle undefined otherwise in ConditionalValidation', () => { + const validation = { + type: 'conditional' as const, + name: 'conditional_validation', + message: 'Conditional validation', + when: 'type = "special"', + then: { + type: 'script' as const, + name: 'special_validation', + message: 'Special type validation', + condition: 'amount > 100', + }, + otherwise: undefined, + }; + + expect(() => ConditionalValidationSchema.parse(validation)).not.toThrow(); + }); + + it('should reject missing required fields', () => { + expect(() => ScriptValidationSchema.parse({ + type: 'script', + // Missing name, message, and condition + })).toThrow(); + }); + + it('should reject invalid validation type', () => { + expect(() => ValidationRuleSchema.parse({ + type: 'invalid_type', + name: 'test', + message: 'Test', + })).toThrow(); + }); + + it('should reject invalid severity level', () => { + expect(() => ScriptValidationSchema.parse({ + type: 'script', + name: 'test', + message: 'Test', + condition: 'true', + severity: 'invalid', + })).toThrow(); + }); +}); + +describe('ValidationRuleSchema - Type Coercion Edge Cases', () => { + it('should handle boolean active flag', () => { + const testCases = [ + { active: true, expected: true }, + { active: false, expected: false }, + ]; + + testCases.forEach(({ active, expected }) => { + const validation = { + type: 'script' as const, + name: 'test', + message: 'Test', + condition: 'true', + active, + }; + + const result = ScriptValidationSchema.parse(validation); + expect(result.active).toBe(expected); + }); + }); + + it('should handle caseSensitive boolean in uniqueness validation', () => { + const validation = { + type: 'unique' as const, + name: 'unique_test', + message: 'Must be unique', + fields: ['field1'], + caseSensitive: false, + }; + + const result = UniquenessValidationSchema.parse(validation); + expect(result.caseSensitive).toBe(false); + }); + + it('should handle distinct boolean in aggregation', () => { + const validation = { + type: 'async' as const, + name: 'async_test', + message: 'Validation failed', + field: 'test', + validatorUrl: '/api/validate', + debounce: 500, + timeout: 3000, + }; + + const result = AsyncValidationSchema.parse(validation); + expect(result.debounce).toBe(500); + expect(result.timeout).toBe(3000); + }); + + it('should handle various param types', () => { + const paramsTests = [ + { params: { key: 'value' } }, + { params: { nested: { key: 'value' } } }, + { params: { array: [1, 2, 3] } }, + { params: { boolean: true, number: 42, string: 'test' } }, + ]; + + paramsTests.forEach(({ params }) => { + const validation = { + type: 'custom' as const, + name: 'custom_test', + message: 'Test', + validatorFunction: 'validate', + params, + }; + + expect(() => CustomValidatorSchema.parse(validation)).not.toThrow(); + }); + }); + + it('should handle nested conditional validations', () => { + const validation = { + type: 'conditional' as const, + name: 'nested_conditional', + message: 'Nested conditional', + when: 'type = "A"', + then: { + type: 'conditional' as const, + name: 'inner_conditional', + message: 'Inner conditional', + when: 'subtype = "B"', + then: { + type: 'script' as const, + name: 'final_validation', + message: 'Final validation', + condition: 'value > 0', + }, + }, + }; + + expect(() => ValidationRuleSchema.parse(validation)).not.toThrow(); + }); + + it('should handle complex state machine transitions', () => { + const validation = { + type: 'state_machine' as const, + name: 'complex_state', + message: 'Invalid state transition', + field: 'status', + transitions: { + 'draft': ['review', 'cancelled'], + 'review': ['approved', 'rejected', 'draft'], + 'approved': ['published', 'draft'], + 'rejected': ['draft'], + 'published': ['archived'], + 'archived': [], + 'cancelled': [], + }, + }; + + expect(() => StateMachineValidationSchema.parse(validation)).not.toThrow(); + }); + + it('should handle format validation with regex', () => { + const validation = { + type: 'format' as const, + name: 'regex_format', + message: 'Invalid format', + field: 'custom_field', + regex: '^[A-Z]{3}-\\d{4}$', // Pattern like ABC-1234 + }; + + expect(() => FormatValidationSchema.parse(validation)).not.toThrow(); + }); + + it('should handle cross-field validation with complex conditions', () => { + const validation = { + type: 'cross_field' as const, + name: 'complex_cross_field', + message: 'Complex validation failed', + condition: '(end_date > start_date) AND (amount >= min_amount) AND (amount <= max_amount)', + fields: ['start_date', 'end_date', 'amount', 'min_amount', 'max_amount'], + }; + + expect(() => CrossFieldValidationSchema.parse(validation)).not.toThrow(); + }); + + it('should handle async validation with all optional fields', () => { + const validation = { + type: 'async' as const, + name: 'comprehensive_async', + message: 'Async validation failed', + field: 'email', + validatorUrl: '/api/validate/email', + validatorFunction: 'validateEmail', + timeout: 2000, + debounce: 300, + params: { + checkDomain: true, + allowDisposable: false, + }, + }; + + expect(() => AsyncValidationSchema.parse(validation)).not.toThrow(); + }); +}); + +describe('ValidationRuleSchema - Boundary Conditions', () => { + it('should handle very long validation names', () => { + const validation = { + type: 'script' as const, + name: 'very_long_validation_name_that_follows_snake_case_convention', + message: 'Test', + condition: 'true', + }; + + expect(() => ScriptValidationSchema.parse(validation)).not.toThrow(); + }); + + it('should handle very long messages', () => { + const longMessage = 'This is a very long validation message that provides detailed information about what went wrong and how to fix it. '.repeat(10); + + const validation = { + type: 'script' as const, + name: 'test_long_message', + message: longMessage, + condition: 'true', + }; + + expect(() => ScriptValidationSchema.parse(validation)).not.toThrow(); + }); + + it('should handle large number of fields in uniqueness validation', () => { + const validation = { + type: 'unique' as const, + name: 'composite_unique', + message: 'Combination must be unique', + fields: ['field1', 'field2', 'field3', 'field4', 'field5', 'field6', 'field7', 'field8'], + }; + + expect(() => UniquenessValidationSchema.parse(validation)).not.toThrow(); + }); + + it('should handle large number of state transitions', () => { + const transitions: Record = {}; + for (let i = 0; i < 20; i++) { + transitions[`state_${i}`] = [`state_${(i + 1) % 20}`]; + } + + const validation = { + type: 'state_machine' as const, + name: 'large_state_machine', + message: 'Invalid transition', + field: 'status', + transitions, + }; + + expect(() => StateMachineValidationSchema.parse(validation)).not.toThrow(); + }); + + it('should handle extreme timeout values', () => { + const testCases = [ + { timeout: 100 }, // Very short + { timeout: 5000 }, // Default + { timeout: 30000 }, // Long + { timeout: 60000 }, // Very long + ]; + + testCases.forEach(({ timeout }) => { + const validation = { + type: 'async' as const, + name: 'timeout_test', + message: 'Test', + field: 'test', + validatorUrl: '/api/validate', + timeout, + }; + + expect(() => AsyncValidationSchema.parse(validation)).not.toThrow(); + }); + }); + + it('should handle debounce edge cases', () => { + const testCases = [ + { debounce: 0 }, + { debounce: 100 }, + { debounce: 500 }, + { debounce: 1000 }, + { debounce: 5000 }, + ]; + + testCases.forEach(({ debounce }) => { + const validation = { + type: 'async' as const, + name: 'debounce_test', + message: 'Test', + field: 'test', + validatorUrl: '/api/validate', + debounce, + }; + + expect(() => AsyncValidationSchema.parse(validation)).not.toThrow(); + }); + }); +});