@apistudio/apim-cli
Version:
CLI for API Management Products
1,607 lines (1,491 loc) • 59.4 kB
text/typescript
/**
* 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,