UNPKG

@apistudio/apim-cli

Version:

CLI for API Management Products

1,607 lines (1,491 loc) 59.4 kB
/** * Copyright IBM Corp. 2024, 2025 */ import { AssertionEngine } from '../../../src/engine/assertion/assertion.engine.js'; import { AssertConstants } from '../../../src/constants/assertConstants.js'; import { VCM } from '../../../src/engine/variable-context-manager/context-manager.js'; jest.mock('../../../src/handlers/assertion.handler.js', () => ({ performAssertion: jest .fn() .mockImplementation((action: string, actual: any, expected: any) => { if (action === AssertConstants.equals_action && actual !== expected) { throw new Error('Assertion failed: values are not equal'); } if ( action === AssertConstants.matches_action && !actual.match(new RegExp(expected)) ) { throw new Error('Assertion failed: values do not match'); } if ( action === AssertConstants.include_action && !expected.includes(actual) ) { throw new Error('Assertion failed: values do not include'); } if ( action === AssertConstants.type_action && typeof actual !== expected ) { throw new Error('Assertion failed: invalid type'); } if ( action === AssertConstants.lengthOf_action && actual.length != expected ) { throw new Error('Assertion failed: length not matching'); } if ( action === AssertConstants.greaterThan_action && !(actual > expected) ) { throw new Error('Assertion failed: value is not greater than expected'); } if (action === AssertConstants.lessThan_action && !(actual < expected)) { throw new Error('Assertion failed: value is not less than expected'); } if (action === 'invalidAction') { throw {}; } }), })); describe('AssertionEngine', () => { let assertionEngine: AssertionEngine; let contextId: string; beforeEach(() => { contextId = 'contextId'; assertionEngine = new AssertionEngine(); }); describe('assert', () => { afterEach(() => { VCM.clearAll(); }); it('should return an array of RunExecutionAssertion objects', async () => { VCM.createContext(contextId).setVariable('status', 200); VCM.createContext(contextId).setVariable('responseStatus', 200); const assertions = [ { kind: 'assertion' as const, metadata: { name: 'basicassert', version: 'alpha', namespace: 'iam_host_ip', }, spec: [ { name: 'test1', if: '${responseStatus} == 200', action: AssertConstants.equals_action, key: 'status', value: 200, }, { name: 'test2', if: true, action: AssertConstants.equals_action, key: 'status', value: 404, }, ], }, ]; const request = { assertions, }; const [results] = await assertionEngine.assert(request, contextId); expect(results).toHaveLength(2); expect(results[0].assertion).toBe('test1'); expect(results[0].skipped).toBeFalsy(); expect(results[0].error).toBeUndefined(); expect(results[1].assertion).toBe('test2'); expect(results[1].skipped).toBeFalsy(); expect(results[1].error).toEqual({ message: 'Assertion failed: values are not equal', stack: expect.any(String), name: 'Error', test: 'test2', }); }); it('should return an array of RunExecutionAssertion objects with conditions and stop on fail', async () => { VCM.createContext(contextId).setVariable('status', 200); VCM.createContext(contextId).setVariable('responseStatus', 200); const assertions = [ { kind: 'assertion' as const, metadata: { name: 'basicassert', version: 'alpha', namespace: 'iam_host_ip', }, spec: [ { name: 'test1', action: AssertConstants.equals_action, key: 'status', value: 200, }, { name: 'test2', if: '${responseStatus} == 200', action: AssertConstants.equals_action, key: 'status', value: 404, stopOnFail: true, }, { name: 'test3', action: AssertConstants.equals_action, key: 'status', value: 404, }, ], }, ]; const request = { assertions, }; const [results] = await assertionEngine.assert(request, contextId); // When test2 fails with stopOnFail=true, remaining assertions are marked as skipped expect(results).toHaveLength(3); expect(results[0].assertion).toBe('test1'); expect(results[0].skipped).toBeFalsy(); expect(results[0].error).toBeUndefined(); expect(results[1].assertion).toBe('test2'); expect(results[1].skipped).toBeFalsy(); expect(results[1].error).toEqual({ message: 'Assertion failed: values are not equal', stack: expect.any(String), name: 'Error', test: 'test2', }); // Third test should be marked as skipped expect(results[2].assertion).toBe('test3'); expect(results[2].skipped).toBeTruthy(); expect(results[2].error).toBeUndefined(); }); it('should handle missing keys in the response', async () => { VCM.createContext(contextId).setVariable('value', 200); const assertions = [ { kind: 'assertion' as const, metadata: { name: 'basicassert', version: 'alpha', namespace: 'iam_host_ip', }, spec: [ { name: 'test1', action: AssertConstants.equals_action, key: 'nonExistentKey', value: 'value', }, ], }, ]; const request = { assertions, }; const [results] = await assertionEngine.assert(request, contextId); expect(results).toHaveLength(1); expect(results[0].skipped).toBeFalsy(); expect(results[0].error).toBeDefined(); }); it('should perform assertions correctly', async () => { VCM.createContext(contextId).setVariable('status', 200); const assertions = [ { kind: 'assertion' as const, metadata: { name: 'basicassert', version: 'alpha', namespace: 'iam_host_ip', }, spec: [ { name: 'test1', action: AssertConstants.greaterThan_action, key: 'status', value: 100, }, ], }, ]; const request = { assertions, }; const [results] = await assertionEngine.assert(request, contextId); expect(results).toHaveLength(1); expect(results[0].skipped).toBeFalsy(); expect(results[0].error).toBeUndefined(); }); it('should perform assert in header values correctly', async () => { VCM.createContext(contextId).setVariable('header', { 'Content-Type': 'application/json', }); const assertions = [ { kind: 'assertion' as const, metadata: { name: 'basicassert', version: 'alpha', namespace: 'iam_host_ip', }, spec: [ { name: 'test1', action: AssertConstants.equals_action, key: 'header.Content-Type', value: 'application/json', }, { name: 'test2', action: AssertConstants.equals_action, key: '${header.Content-Type}', value: 'application/json', }, { name: 'test3', action: AssertConstants.equals_action, key: 'header.Content-Type', value: 'application/xml', }, ], }, ]; const request = { assertions, }; const [results] = await assertionEngine.assert(request, contextId); expect(results).toHaveLength(3); expect(results[0].skipped).toBeFalsy(); expect(results[0].error).toBeUndefined(); expect(results[1].skipped).toBeFalsy(); expect(results[1].error).toBeUndefined(); expect(results[2].skipped).toBeFalsy(); expect(results[2].error).toBeDefined(); }); it('should handle empty error value', async () => { VCM.createContext(contextId).setVariable('status', 200); const assertions = [ { kind: 'assertion' as const, metadata: { name: 'basicassert', version: 'alpha', namespace: 'iam_host_ip', }, spec: [ { name: 'test1', action: 'invalidAction', key: 'status', value: 'application/json', }, ], }, ]; const request = { assertions, }; const [results] = await assertionEngine.assert(request, contextId); expect(results).toHaveLength(1); expect(results[0].skipped).toBeFalsy(); expect(results[0].error).toBeDefined(); }); it('should handle variable resolution in value', async () => { VCM.createContext(contextId).setVariable('status', 200); VCM.createContext(contextId).setVariable('statusCode', 200); const assertions = [ { kind: 'assertion' as const, metadata: { name: 'basicassert', version: 'alpha', namespace: 'iam_host_ip', }, spec: [ { name: 'test1', action: AssertConstants.equals_action, key: 'status', value: '${statusCode}', }, ], }, ]; const request = { assertions, }; const [results] = await assertionEngine.assert(request, contextId); expect(results).toHaveLength(1); expect(results[0].skipped).toBeFalsy(); expect(results[0].error).toBeUndefined(); }); describe('regrex, should validate regrex', () => { it('for valid case', async () => { VCM.createContext(contextId).setVariable('email', 'test@gmail.com'); const assertions = [ { kind: 'assertion' as const, metadata: { name: 'basicassert', version: 'alpha', namespace: 'iam_host_ip', }, spec: [ { name: 'test1', action: AssertConstants.matches_action, key: 'email', value: '[^\s@]+@[^\s@]+\.[^\s@]{2,}', }, ], }, ]; const request = { assertions, }; const [results] = await assertionEngine.assert(request, contextId); expect(results).toHaveLength(1); expect(results[0].skipped).toBeFalsy(); expect(results[0].error).toBeUndefined(); }); it('for invalid case', async () => { VCM.createContext(contextId).setVariable('email', 'test.com'); const assertions = [ { kind: 'assertion' as const, metadata: { name: 'basicassert', version: 'alpha', namespace: 'iam_host_ip', }, spec: [ { name: 'test1', action: AssertConstants.matches_action, key: 'email', value: '[^\s@]+@[^\s@]+\.[^\s@]{2,}', }, ], }, ]; const request = { assertions, }; const [results] = await assertionEngine.assert(request, contextId); expect(results).toHaveLength(1); expect(results[0].skipped).toBeFalsy(); expect(results[0].error).toBeDefined(); }); }); it('should match value in array', async () => { VCM.createContext(contextId).setVariable('users', [ { name: 'John', age: 30 }, { name: 'Jane', age: 25 }, { name: 'Doe', age: 35 }, ]); const assertions = [ { kind: 'assertion' as const, metadata: { name: 'basicassert', version: 'alpha', namespace: 'iam_host_ip', }, spec: [ { name: 'test1', action: AssertConstants.include_action, key: 'users.1.name', value: ['John', 'Jane', 'Doe'], }, ], }, ]; const request = { assertions, }; const [results] = await assertionEngine.assert(request, contextId); expect(results).toHaveLength(1); expect(results[0].skipped).toBeFalsy(); expect(results[0].error).toBeUndefined(); }); describe('should perform assertion on type of value', () => { it('for valid', async () => { VCM.createContext(contextId).setVariable('status', 200); const assertions = [ { kind: 'assertion' as const, metadata: { name: 'basicassert', version: 'alpha', namespace: 'iam_host_ip', }, spec: [ { name: 'test1', action: AssertConstants.type_action, key: 'status', value: 'number', }, ], }, ]; const request = { assertions, }; const [results] = await assertionEngine.assert(request, contextId); expect(results).toHaveLength(1); expect(results[0].skipped).toBeFalsy(); expect(results[0].error).toBeUndefined(); }); it('for invalid', async () => { VCM.createContext(contextId).setVariable('status', 200); const assertions = [ { kind: 'assertion' as const, metadata: { name: 'basicassert', version: 'alpha', namespace: 'iam_host_ip', }, spec: [ { name: 'test1', action: AssertConstants.type_action, key: 'status', value: 'string', }, ], }, ]; const request = { assertions, }; const [results] = await assertionEngine.assert(request, contextId); expect(results).toHaveLength(1); expect(results[0].skipped).toBeFalsy(); expect(results[0].error).toBeDefined(); }); }); describe('flat response structures with primitive values', () => { it('should assert primitive string values', async () => { VCM.createContext(contextId).setVariable('response', { name: 'John', email: 'john@example.com', active: true, age: 30, }); const assertions = [ { kind: 'assertion' as const, metadata: { name: 'primitiveAssert', version: 'alpha', namespace: 'test', }, spec: [ { name: 'string_equals', action: AssertConstants.equals_action, key: 'response.name', value: 'John', }, { name: 'string_type', action: AssertConstants.type_action, key: 'response.email', value: 'string', }, { name: 'boolean_equals', action: AssertConstants.equals_action, key: 'response.active', value: true, }, { name: 'number_equals', action: AssertConstants.equals_action, key: 'response.age', value: 30, }, ], }, ]; const request = { assertions }; const [results] = await assertionEngine.assert(request, contextId); expect(results).toHaveLength(4); expect(results[0].error).toBeUndefined(); expect(results[1].error).toBeUndefined(); expect(results[2].error).toBeUndefined(); expect(results[3].error).toBeUndefined(); }); it('should handle null and undefined values', async () => { VCM.createContext(contextId).setVariable('response', { nullValue: null, emptyString: '', zero: 0, }); const assertions = [ { kind: 'assertion' as const, metadata: { name: 'edgeCaseAssert', version: 'alpha', namespace: 'test', }, spec: [ { name: 'null_check', action: AssertConstants.equals_action, key: 'response.nullValue', value: null, }, { name: 'empty_string', action: AssertConstants.equals_action, key: 'response.emptyString', value: '', }, { name: 'zero_value', action: AssertConstants.equals_action, key: 'response.zero', value: 0, }, ], }, ]; const request = { assertions }; const [results] = await assertionEngine.assert(request, contextId); expect(results).toHaveLength(3); expect(results[0].error).toBeUndefined(); expect(results[1].error).toBeUndefined(); expect(results[2].error).toBeUndefined(); }); }); describe('arrays of simple objects with various property types', () => { it('should assert on specific array elements by index', async () => { VCM.createContext(contextId).setVariable('users', [ { name: 'John', age: 30, roles: ['admin', 'user'] }, { name: 'Jane', age: 25, roles: ['user'] }, { name: 'Bob', age: 40, roles: ['manager', 'user'] }, ]); const assertions = [ { kind: 'assertion' as const, metadata: { name: 'arrayAssert', version: 'alpha', namespace: 'test', }, spec: [ { name: 'first_user_name', action: AssertConstants.equals_action, key: 'users.0.name', value: 'John', }, { name: 'second_user_age', action: AssertConstants.equals_action, key: 'users.1.age', value: 25, }, { name: 'third_user_roles', action: AssertConstants.lengthOf_action, key: 'users.2.roles', value: 2, }, ], }, ]; const request = { assertions }; const [results] = await assertionEngine.assert(request, contextId); expect(results).toHaveLength(3); expect(results[0].error).toBeUndefined(); expect(results[1].error).toBeUndefined(); expect(results[2].error).toBeUndefined(); }); it('should assert on array length', async () => { VCM.createContext(contextId).setVariable('products', [ { id: 1, name: 'Product A', price: 10.99 }, { id: 2, name: 'Product B', price: 24.99 }, { id: 3, name: 'Product C', price: 5.99 }, ]); const assertions = [ { kind: 'assertion' as const, metadata: { name: 'arrayLengthAssert', version: 'alpha', namespace: 'test', }, spec: [ { name: 'products_count', action: AssertConstants.lengthOf_action, key: 'products', value: 3, }, ], }, ]; const request = { assertions }; const [results] = await assertionEngine.assert(request, contextId); expect(results).toHaveLength(1); expect(results[0].error).toBeUndefined(); }); }); describe('deeply nested array objects with multiple levels', () => { it('should assert on deeply nested array properties', async () => { VCM.createContext(contextId).setVariable('organization', { departments: [ { name: 'Engineering', teams: [ { name: 'Frontend', members: [ { id: 1, name: 'Alice', skills: ['JavaScript', 'React'] }, { id: 2, name: 'Bob', skills: ['TypeScript', 'Angular'] }, ], }, { name: 'Backend', members: [ { id: 3, name: 'Charlie', skills: ['Java', 'Spring'] }, { id: 4, name: 'Diana', skills: ['Python', 'Django'] }, ], }, ], }, { name: 'Marketing', teams: [ { name: 'Digital', members: [ { id: 5, name: 'Eve', skills: ['SEO', 'Analytics'] }, ], }, ], }, ], }); const assertions = [ { kind: 'assertion' as const, metadata: { name: 'deepNestedAssert', version: 'alpha', namespace: 'test', }, spec: [ { name: 'engineering_team_count', action: AssertConstants.lengthOf_action, key: 'organization.departments.0.teams', value: 2, }, { name: 'frontend_member_count', action: AssertConstants.lengthOf_action, key: 'organization.departments.0.teams.0.members', value: 2, }, { name: 'backend_first_member_name', action: AssertConstants.equals_action, key: 'organization.departments.0.teams.1.members.0.name', value: 'Charlie', }, { name: 'marketing_team_member_skills', action: AssertConstants.lengthOf_action, key: 'organization.departments.1.teams.0.members.0.skills', value: 2, }, ], }, ]; const request = { assertions }; const [results] = await assertionEngine.assert(request, contextId); expect(results).toHaveLength(4); expect(results[0].error).toBeUndefined(); expect(results[1].error).toBeUndefined(); expect(results[2].error).toBeUndefined(); expect(results[3].error).toBeUndefined(); }); it('should handle arrays with nested arrays', async () => { VCM.createContext(contextId).setVariable('matrix', [ [ [1, 2, 3], [4, 5, 6], ], [ [7, 8, 9], [10, 11, 12], ], ]); const assertions = [ { kind: 'assertion' as const, metadata: { name: 'nestedArrayAssert', version: 'alpha', namespace: 'test', }, spec: [ { name: 'first_dimension_length', action: AssertConstants.lengthOf_action, key: 'matrix', value: 2, }, { name: 'second_dimension_length', action: AssertConstants.lengthOf_action, key: 'matrix.0', value: 2, }, { name: 'third_dimension_length', action: AssertConstants.lengthOf_action, key: 'matrix.0.0', value: 3, }, { name: 'specific_value', action: AssertConstants.equals_action, key: 'matrix.1.1.2', value: 12, }, ], }, ]; const request = { assertions }; const [results] = await assertionEngine.assert(request, contextId); expect(results).toHaveLength(4); expect(results[0].error).toBeUndefined(); expect(results[1].error).toBeUndefined(); expect(results[2].error).toBeUndefined(); expect(results[3].error).toBeUndefined(); }); }); describe('mixed data structures combining arrays and objects', () => { it('should handle complex mixed structures', async () => { VCM.createContext(contextId).setVariable('apiResponse', { metadata: { version: '1.0', generated: '2025-01-01', status: 'success', }, data: { users: [ { id: 1, profile: { name: 'John Doe', email: 'john@example.com', preferences: { notifications: true, theme: 'dark', }, }, posts: [ { id: 101, title: 'First post', comments: [{ author: 'Jane', text: 'Great!' }], }, { id: 102, title: 'Second post', comments: [] }, ], }, { id: 2, profile: { name: 'Jane Smith', email: 'jane@example.com', preferences: { notifications: false, theme: 'light', }, }, posts: [ { id: 201, title: 'Hello world', comments: [{ author: 'John', text: 'Nice!' }], }, ], }, ], stats: { totalUsers: 2, totalPosts: 3, activeUsers: ['John Doe', 'Jane Smith'], }, }, }); const assertions = [ { kind: 'assertion' as const, metadata: { name: 'mixedStructureAssert', version: 'alpha', namespace: 'test', }, spec: [ { name: 'metadata_version', action: AssertConstants.equals_action, key: 'apiResponse.metadata.version', value: '1.0', }, { name: 'user_count', action: AssertConstants.lengthOf_action, key: 'apiResponse.data.users', value: 2, }, { name: 'first_user_posts_count', action: AssertConstants.lengthOf_action, key: 'apiResponse.data.users.0.posts', value: 2, }, { name: 'second_user_theme', action: AssertConstants.equals_action, key: 'apiResponse.data.users.1.profile.preferences.theme', value: 'light', }, { name: 'stats_active_users', action: AssertConstants.lengthOf_action, key: 'apiResponse.data.stats.activeUsers', value: 2, }, { name: 'first_user_first_post_comments', action: AssertConstants.lengthOf_action, key: 'apiResponse.data.users.0.posts.0.comments', value: 1, }, ], }, ]; const request = { assertions }; const [results] = await assertionEngine.assert(request, contextId); expect(results).toHaveLength(6); expect(results[0].error).toBeUndefined(); expect(results[1].error).toBeUndefined(); expect(results[2].error).toBeUndefined(); expect(results[3].error).toBeUndefined(); expect(results[4].error).toBeUndefined(); expect(results[5].error).toBeUndefined(); }); it('should handle objects with array properties and nested objects', async () => { VCM.createContext(contextId).setVariable('product', { id: 'prod-123', name: 'Smartphone', variants: [ { color: 'black', storage: ['64GB', '128GB'], prices: { '64GB': { amount: 699, currency: 'USD' }, '128GB': { amount: 799, currency: 'USD' }, }, }, { color: 'white', storage: ['64GB', '128GB', '256GB'], prices: { '64GB': { amount: 699, currency: 'USD' }, '128GB': { amount: 799, currency: 'USD' }, '256GB': { amount: 899, currency: 'USD' }, }, }, ], reviews: { average: 4.5, count: 120, recent: [ { user: 'user1', rating: 5, comment: 'Great product!' }, { user: 'user2', rating: 4, comment: 'Good value' }, ], }, }); const assertions = [ { kind: 'assertion' as const, metadata: { name: 'productAssert', version: 'alpha', namespace: 'test', }, spec: [ { name: 'product_name', action: AssertConstants.equals_action, key: 'product.name', value: 'Smartphone', }, { name: 'variant_count', action: AssertConstants.lengthOf_action, key: 'product.variants', value: 2, }, { name: 'white_variant_storage_options', action: AssertConstants.lengthOf_action, key: 'product.variants.1.storage', value: 3, }, { name: 'black_variant_128gb_price', action: AssertConstants.equals_action, key: 'product.variants.0.prices.128GB.amount', value: 799, }, { name: 'review_count', action: AssertConstants.equals_action, key: 'product.reviews.count', value: 120, }, { name: 'recent_reviews_count', action: AssertConstants.lengthOf_action, key: 'product.reviews.recent', value: 2, }, ], }, ]; const request = { assertions }; const [results] = await assertionEngine.assert(request, contextId); expect(results).toHaveLength(6); expect(results[0].error).toBeUndefined(); expect(results[1].error).toBeUndefined(); expect(results[2].error).toBeUndefined(); expect(results[3].error).toBeUndefined(); expect(results[4].error).toBeUndefined(); expect(results[5].error).toBeUndefined(); }); }); describe('edge cases with wildcard syntax', () => { it('should handle empty arrays', async () => { VCM.createContext(contextId).setVariable('data', { emptyArray: [], arrayWithEmptyObjects: [{}, {}], nestedEmptyArrays: [[], []], }); const assertions = [ { kind: 'assertion' as const, metadata: { name: 'emptyArrayAssert', version: 'alpha', namespace: 'test', }, spec: [ { name: 'empty_array_length', action: AssertConstants.lengthOf_action, key: 'data.emptyArray', value: 0, }, { name: 'array_with_empty_objects_length', action: AssertConstants.lengthOf_action, key: 'data.arrayWithEmptyObjects', value: 2, }, { name: 'nested_empty_arrays_length', action: AssertConstants.lengthOf_action, key: 'data.nestedEmptyArrays', value: 2, }, ], }, ]; const request = { assertions }; const [results] = await assertionEngine.assert(request, contextId); expect(results).toHaveLength(3); expect(results[0].error).toBeUndefined(); expect(results[1].error).toBeUndefined(); expect(results[2].error).toBeUndefined(); }); it('should handle null values', async () => { VCM.createContext(contextId).setVariable('data', { nullValue: null, objectWithNull: { prop: null }, arrayWithNulls: [null, 'value', null], }); const assertions = [ { kind: 'assertion' as const, metadata: { name: 'nullValueAssert', version: 'alpha', namespace: 'test', }, spec: [ { name: 'direct_null_value', action: AssertConstants.equals_action, key: 'data.nullValue', value: null, }, { name: 'object_with_null_prop', action: AssertConstants.equals_action, key: 'data.objectWithNull.prop', value: null, }, { name: 'array_with_nulls_length', action: AssertConstants.lengthOf_action, key: 'data.arrayWithNulls', value: 3, }, ], }, ]; const request = { assertions }; const [results] = await assertionEngine.assert(request, contextId); expect(results).toHaveLength(3); expect(results[0].error).toBeUndefined(); expect(results[1].error).toBeUndefined(); expect(results[2].error).toBeUndefined(); }); it('should handle undefined properties', async () => { VCM.createContext(contextId).setVariable('data', { definedProp: 'value', // undefinedProp is intentionally not defined }); const assertions = [ { kind: 'assertion' as const, metadata: { name: 'undefinedPropAssert', version: 'alpha', namespace: 'test', }, spec: [ { name: 'defined_property', action: AssertConstants.equals_action, key: 'data.definedProp', value: 'value', }, { name: 'undefined_property', action: AssertConstants.equals_action, key: 'data.undefinedProp', value: undefined, }, ], }, ]; const request = { assertions }; const [results] = await assertionEngine.assert(request, contextId); expect(results).toHaveLength(2); expect(results[0].error).toBeUndefined(); expect(results[1].error).toBeUndefined(); }); it('should handle wildcard on empty or sparse structures', async () => { VCM.createContext(contextId).setVariable('data', { emptyArray: [], sparseArray: [, , { name: 'item' }], // Array with empty slots emptyObject: {}, }); const assertions = [ { kind: 'assertion' as const, metadata: { name: 'wildcardEmptyAssert', version: 'alpha', namespace: 'test', }, spec: [ { name: 'wildcard_on_empty_array', action: AssertConstants.lengthOf_action, key: 'data.emptyArray.*', value: 0, }, { name: 'wildcard_on_sparse_array', action: AssertConstants.equals_action, key: 'data.sparseArray.2.name', value: 'item', }, { name: 'wildcard_on_empty_object', action: AssertConstants.lengthOf_action, key: 'data.emptyObject.*', value: 0, }, ], }, ]; const request = { assertions }; const [results] = await assertionEngine.assert(request, contextId); expect(results).toHaveLength(3); // The first assertion might fail depending on implementation - wildcards on empty arrays // The third assertion might fail depending on implementation - wildcards on empty objects expect(results[1].error).toBeUndefined(); // This should pass }); }); describe('boundary conditions with wildcard patterns', () => { it('should handle wildcards matching different data types', async () => { VCM.createContext(contextId).setVariable('mixedData', { items: [ 123, 'string', true, { key: 'value' }, [1, 2, 3], null, undefined, ], }); const assertions = [ { kind: 'assertion' as const, metadata: { name: 'mixedTypesAssert', version: 'alpha', namespace: 'test', }, spec: [ { name: 'items_length', action: AssertConstants.lengthOf_action, key: 'mixedData.items', value: 7, }, { name: 'number_type', action: AssertConstants.type_action, key: 'mixedData.items.0', value: 'number', }, { name: 'string_type', action: AssertConstants.type_action, key: 'mixedData.items.1', value: 'string', }, { name: 'boolean_type', action: AssertConstants.type_action, key: 'mixedData.items.2', value: 'boolean', }, { name: 'object_type', action: AssertConstants.type_action, key: 'mixedData.items.3', value: 'object', }, { name: 'array_type', action: AssertConstants.type_action, key: 'mixedData.items.4', value: 'object', // Arrays are objects in JavaScript }, ], }, ]; const request = { assertions }; const [results] = await assertionEngine.assert(request, contextId); expect(results).toHaveLength(6); expect(results[0].error).toBeUndefined(); expect(results[1].error).toBeUndefined(); expect(results[2].error).toBeUndefined(); expect(results[3].error).toBeUndefined(); expect(results[4].error).toBeUndefined(); expect(results[5].error).toBeUndefined(); }); it('should handle wildcards with type conversions', async () => { VCM.createContext(contextId).setVariable('data', { values: [ '42', // string that looks like a number 42, // actual number 'true', // string that looks like a boolean true, // actual boolean '[]', // string that looks like an array [], // actual array '{}', // string that looks like an object {}, // actual object ], }); const assertions = [ { kind: 'assertion' as const, metadata: { name: 'typeConversionAssert', version: 'alpha', namespace: 'test', }, spec: [ { name: 'string_number_type', action: AssertConstants.type_action, key: 'data.values.0', value: 'string', }, { name: 'actual_number_type', action: AssertConstants.type_action, key: 'data.values.1', value: 'number', }, { name: 'string_boolean_type', action: AssertConstants.type_action, key: 'data.values.2', value: 'string', }, { name: 'actual_boolean_type', action: AssertConstants.type_action, key: 'data.values.3', value: 'boolean', }, { name: 'string_array_type', action: AssertConstants.type_action, key: 'data.values.4', value: 'string', }, { name: 'actual_array_type', action: AssertConstants.type_action, key: 'data.values.5', value: 'object', }, ], }, ]; const request = { assertions }; const [results] = await assertionEngine.assert(request, contextId); expect(results).toHaveLength(6); expect(results[0].error).toBeUndefined(); expect(results[1].error).toBeUndefined(); expect(results[2].error).toBeUndefined(); expect(results[3].error).toBeUndefined(); expect(results[4].error).toBeUndefined(); expect(results[5].error).toBeUndefined(); }); it('should handle wildcards with edge case values', async () => { VCM.createContext(contextId).setVariable('edgeCases', { values: [ 0, // falsy number '', // empty string false, // boolean false NaN, // Not a Number Infinity, // Infinity -Infinity, // Negative Infinity ], }); const assertions = [ { kind: 'assertion' as const, metadata: { name: 'edgeCaseValuesAssert', version: 'alpha', namespace: 'test', }, spec: [ { name: 'zero_value', action: AssertConstants.equals_action, key: 'edgeCases.values.0', value: 0, }, { name: 'empty_string', action: AssertConstants.equals_action, key: 'edgeCases.values.1', value: '', }, { name: 'false_value', action: AssertConstants.equals_action, key: 'edgeCases.values.2', value: false, }, // NaN, Infinity and -Infinity tests might be implementation-specific // so we're just checking their types { name: 'nan_type', action: AssertConstants.type_action, key: 'edgeCases.values.3', value: 'number', }, { name: 'infinity_type', action: AssertConstants.type_action, key: 'edgeCases.values.4', value: 'number', }, { name: 'negative_infinity_type', action: AssertConstants.type_action, key: 'edgeCases.values.5', value: 'number', }, ], }, ]; const request = { assertions }; const [results] = await assertionEngine.assert(request, contextId); expect(results).toHaveLength(6); expect(results[0].error).toBeUndefined(); expect(results[1].error).toBeUndefined(); expect(results[2].error).toBeUndefined(); expect(results[3].error).toBeUndefined(); expect(results[4].error).toBeUndefined(); expect(results[5].error).toBeUndefined(); }); }); describe('performance with large nested structures', () => { it('should handle large arrays efficiently', async () => { // Create a large array with 1000 items const largeArray = Array.from({ length: 1000 }, (_, i) => ({ id: i, name: `Item ${i}`, active: i % 2 === 0, tags: [`tag-${i % 10}`, `category-${i % 5}`], })); VCM.createContext(contextId).setVariable('largeData', { items: largeArray, }); const assertions = [ { kind: 'assertion' as const, metadata: { name: 'largeArrayAssert', version: 'alpha', namespace: 'test', }, spec: [ { name: 'array_length', action: AssertConstants.lengthOf_action, key: 'largeData.items', value: 1000, }, { name: 'specific_item', action: AssertConstants.equals_action, key: 'largeData.items.500.id', value: 500, }, { name: 'last_item', action: AssertConstants.equals_action, key: 'largeData.items.999.name', value: 'Item 999', }, ], }, ]; const request = { assertions }; const [results] = await assertionEngine.assert(request, contextId); expect(results).toHaveLength(3); expect(results[0].error).toBeUndefined(); expect(results[1].error).toBeUndefined(); expect(results[2].error).toBeUndefined(); }); it('should handle deeply nested large structures', async () => { // Create a deeply nested structure with multiple levels function createNestedStructure( depth: number, breadth: number, currentDepth = 0, ): Record<string, any> { if (currentDepth >= depth) { return { value: `leaf-${currentDepth}-${Math.random()}` }; } const result: Record<string, any> = {}; for (let i = 0; i < breadth; i++) { result[`level${currentDepth}-${i}`] = createNestedStructure( depth, breadth, currentDepth + 1, ); } return result; } // Create a structure with depth 5 and breadth 3 (3^5 = 243 leaf nodes) const nestedStructure = createNestedStructure(5, 3); VCM.createContext(contextId).setVariable('nestedData', nestedStructure); const assertions = [ { kind: 'assertion' as const, metadata: { name: 'deepNestedAssert', version: 'alpha', namespace: 'test', }, spec: [ { name: 'nested_path_exists', action: AssertConstants.type_action, key: 'nestedData.level0-0.level1-0.level2-0.level3-0.level4-0.value', value: 'string', }, { name: 'different_nested_path_exists', action: AssertConstants.type_action, key: 'nestedData.level0-2.level1-1.level2-2.level3-0.level4-1.value', value: 'string', }, ], }, ]; const request = { assertions }; const [results] = await assertionEngine.assert(request, contextId); expect(results).toHaveLength(2); expect(results[0].error).toBeUndefined(); expect(results[1].error).toBeUndefined(); }); }); describe('negative test cases for wildcard patterns', () => { it('should handle non-matching wildcard paths', async () => { VCM.createContext(contextId).setVariable('data', { users: [ { name: 'John', role: 'admin' }, { name: 'Jane', role: 'user' }, ], settings: { theme: 'dark', notifications: true, }, }); const assertions = [ { kind: 'assertion' as const, metadata: { name: 'nonMatchingWildcardAssert', version: 'alpha', namespace: 'test', }, spec: [ { name: 'non_existent_wildcard_path', action: AssertConstants.lengthOf_action, key: 'data.nonexistent.*.name', value: 0,