@apistudio/apim-cli
Version:
CLI for API Management Products
1,259 lines (1,258 loc) • 46.4 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const base_transformer_impl_1 = require("../core/impl/base-transformer.impl");
const transformer_error_1 = require("../core/errors/transformer.error");
describe('BaseTransformer', () => {
let transformer;
beforeEach(() => {
transformer = new base_transformer_impl_1.BaseTransformer();
});
describe('transform', () => {
it('should skip transformation when skipTransform is true', async () => {
// Arrange
const wrappedAsset = {
inputSchema: {
kind: 'Test',
apiVersion: 'v1',
metadata: {
name: 'test',
version: '1.0.0'
},
spec: {
input: 'request',
output: 'response'
}
},
metadata: {
name: 'test',
version: '1.0.0'
}
};
const config = {
skipTransform: true
};
// Act
const result = await transformer.transform(wrappedAsset, config);
// Assert
expect(result).toBe(wrappedAsset);
expect(result.outputAsset).toBeUndefined();
});
it('should apply transformations when skipTransform is false', async () => {
// Arrange
const wrappedAsset = {
inputSchema: {
kind: 'Test',
apiVersion: 'v1',
metadata: {
name: 'test',
version: '1.0.0'
},
spec: {
input: 'request',
output: 'response'
}
},
metadata: {
name: 'test',
version: '1.0.0'
}
};
const config = {
skipTransform: false,
transformations: {
mappings: [
{
source: '$.spec.input',
target: '$.Test.input'
},
{
source: '$.spec.output',
target: '$.Test.output'
}
]
}
};
// Act
const result = await transformer.transform(wrappedAsset, config);
// Assert
expect(result).not.toBe(wrappedAsset);
expect(result.outputAsset).toEqual({
Test: {
input: 'request',
output: 'response'
}
});
});
});
describe('mappings', () => {
it('should apply mappings correctly', async () => {
// Arrange
const wrappedAsset = {
inputSchema: {
kind: 'Test',
apiVersion: 'v1',
metadata: {
name: 'test',
version: '1.0.0'
},
spec: {
input: 'request',
output: 'response',
nested: {
property: 'value'
}
}
},
metadata: {
name: 'test',
version: '1.0.0'
}
};
const config = {
transformations: {
mappings: [
{
source: '$.spec.input',
target: '$.Test.input'
},
{
source: '$.spec.output',
target: '$.Test.output'
},
{
source: '$.spec.nested',
target: '$.Test.nested'
}
]
}
};
// Act
const result = await transformer.transform(wrappedAsset, config);
// Assert
expect(result.outputAsset).toEqual({
Test: {
input: 'request',
output: 'response',
nested: {
property: 'value'
}
}
});
});
it('should handle non-existent source paths', async () => {
// Arrange
const wrappedAsset = {
inputSchema: {
kind: 'Test',
apiVersion: 'v1',
metadata: {
name: 'test',
version: '1.0.0'
},
spec: {
input: 'request'
}
},
metadata: {
name: 'test',
version: '1.0.0'
}
};
const config = {
transformations: {
mappings: [
{
source: '$.spec.input',
target: '$.Test.input'
},
{
source: '$.spec.output', // This doesn't exist
target: '$.Test.output'
}
]
}
};
// Act
const result = await transformer.transform(wrappedAsset, config);
// Assert
expect(result.outputAsset).toEqual({
Test: {
input: 'request'
// output should not be present
}
});
});
});
describe('replacements', () => {
it('should apply replacements with static values', async () => {
// Arrange
const wrappedAsset = {
inputSchema: {
kind: 'Test',
apiVersion: 'v1',
metadata: {
name: 'test',
version: '1.0.0'
},
spec: {}
},
metadata: {
name: 'test',
version: '1.0.0'
}
};
const config = {
transformations: {
replacements: [
{
target: '$.Test.staticValue',
value: 'static',
precedence: 10,
operation: 'replace'
}
]
}
};
// Act
const result = await transformer.transform(wrappedAsset, config);
// Assert
expect(result.outputAsset).toEqual({
Test: {
staticValue: 'static'
}
});
});
it('should apply replacements with templates', async () => {
// Arrange
const wrappedAsset = {
inputSchema: {
kind: 'Test',
apiVersion: 'v1',
metadata: {
name: 'test',
version: '1.0.0'
},
spec: {
value: 'template value'
}
},
metadata: {
name: 'test',
version: '1.0.0'
}
};
const config = {
transformations: {
replacements: [
{
target: '$.Test.templatedValue',
value: 'Template: {{$.spec.value}}',
precedence: 10,
operation: 'replace'
}
]
}
};
// Act
const result = await transformer.transform(wrappedAsset, config);
// Assert
expect(result.outputAsset).toEqual({
Test: {
templatedValue: 'Template: template value'
}
});
});
it('should apply replacements with object templates', async () => {
// Arrange
const wrappedAsset = {
inputSchema: {
kind: 'Test',
apiVersion: 'v1',
metadata: {
name: 'test',
version: '1.0.0'
},
spec: {
object: {
prop1: 'value1',
prop2: 'value2'
}
}
},
metadata: {
name: 'test',
version: '1.0.0'
}
};
const config = {
transformations: {
replacements: [
{
target: '$.Test.objectValue',
value: '{{$.spec.object}}',
precedence: 10,
operation: 'replace'
}
]
}
};
// Act
const result = await transformer.transform(wrappedAsset, config);
// Assert
expect(result.outputAsset).toEqual({
Test: {
objectValue: {
prop1: 'value1',
prop2: 'value2'
}
}
});
});
it('should apply replacements with conditions', async () => {
// Arrange
const wrappedAsset = {
inputSchema: {
kind: 'Test',
apiVersion: 'v1',
metadata: {
name: 'test',
version: '1.0.0'
},
spec: {
flag: true,
noFlag: false
}
},
metadata: {
name: 'test',
version: '1.0.0'
}
};
const config = {
transformations: {
replacements: [
{
target: '$.Test.conditionalValue1',
value: 'Applied',
precedence: 10,
condition: '$.spec.flag',
operation: 'replace'
},
{
target: '$.Test.conditionalValue2',
value: 'Not Applied',
precedence: 10,
condition: '$.spec.nonExistent',
operation: 'replace'
}
]
}
};
// Act
const result = await transformer.transform(wrappedAsset, config);
// Assert
expect(result.outputAsset).toEqual({
Test: {
conditionalValue1: 'Applied'
// conditionalValue2 should not be present
}
});
});
it('should apply replacements in order of precedence', async () => {
// Arrange
const wrappedAsset = {
inputSchema: {
kind: 'Test',
apiVersion: 'v1',
metadata: {
name: 'test',
version: '1.0.0'
},
spec: {}
},
metadata: {
name: 'test',
version: '1.0.0'
}
};
const config = {
transformations: {
replacements: [
{
target: '$.Test.value',
value: 'First',
precedence: 20,
operation: 'replace'
},
{
target: '$.Test.value',
value: 'Second',
precedence: 10,
operation: 'replace'
}
]
}
};
// Act
const result = await transformer.transform(wrappedAsset, config);
// Assert
expect(result.outputAsset).toEqual({
Test: {
value: 'First'
}
});
});
it('should handle push operation', async () => {
// Arrange
const wrappedAsset = {
inputSchema: {
kind: 'Test',
apiVersion: 'v1',
metadata: {
name: 'test',
version: '1.0.0'
},
spec: {}
},
metadata: {
name: 'test',
version: '1.0.0'
}
};
const config = {
transformations: {
replacements: [
{
target: '$.Test.array',
value: 'Item 1',
precedence: 10,
operation: 'push'
},
{
target: '$.Test.array',
value: 'Item 2',
precedence: 20,
operation: 'push'
}
]
}
};
// Act
const result = await transformer.transform(wrappedAsset, config);
// Assert
expect(result.outputAsset).toEqual({
Test: {
array: ['Item 1', 'Item 2']
}
});
});
it('should handle remove operation', async () => {
// Arrange
const wrappedAsset = {
inputSchema: {
kind: 'Test',
apiVersion: 'v1',
metadata: {
name: 'test',
version: '1.0.0'
},
spec: {}
},
metadata: {
name: 'test',
version: '1.0.0'
}
};
const config = {
transformations: {
replacements: [
{
target: '$.Test.value',
value: 'Value',
precedence: 10,
operation: 'replace'
},
{
target: '$.Test.value',
precedence: 20,
operation: 'remove'
}
]
}
};
// Act
const result = await transformer.transform(wrappedAsset, config);
// Assert
expect(result.outputAsset).toEqual({
Test: {}
});
});
});
describe('complex transformations', () => {
it('should handle the Invoke transformation with cache object', async () => {
// Arrange
const wrappedAsset = {
inputSchema: {
kind: 'Invoke',
apiVersion: 'api.ibm.com/v1',
metadata: {
name: 'invoke-single-backend',
version: '1.0.0',
namespace: 'sample'
},
spec: {
input: 'request',
output: 'response',
cache: {
expire: {
static: {
seconds: 60
}
},
scope: {
narrowScope: {}
}
},
endpoint: {
http: {
verb: 'GET',
status_exception: {
pattern: 500
},
target: {
tlsClientProfile: 'some_tls_profile',
url: 'https://localhost:3000',
urlType: 'plain',
version: 'HTTP/1.0',
timeout: 60,
compression: false,
chunkedUploads: false,
persistentConnection: true
}
}
}
}
},
metadata: {
name: 'invoke-single-backend',
version: '1.0.0',
namespace: 'sample'
}
};
const config = {
skipTransform: false,
transformations: {
mappings: [
{
source: '$.spec.input',
target: '$.Invoke.input'
},
{
source: '$.spec.output',
target: '$.Invoke.output'
},
{
source: '$.spec.endpoint',
target: '$.Invoke.endpoint'
}
],
replacements: [
{
target: '$.Invoke.cache',
value: '{{$.spec.cache}}',
precedence: 20,
condition: '$.spec.cache',
operation: 'replace'
},
{
target: '$.Invoke.cache',
value: null,
precedence: 10,
operation: 'replace'
}
]
}
};
// Act
const result = await transformer.transform(wrappedAsset, config);
// Assert
expect(result.outputAsset).toEqual({
Invoke: {
input: 'request',
output: 'response',
endpoint: {
http: {
verb: 'GET',
status_exception: {
pattern: 500
},
target: {
tlsClientProfile: 'some_tls_profile',
url: 'https://localhost:3000',
urlType: 'plain',
version: 'HTTP/1.0',
timeout: 60,
compression: false,
chunkedUploads: false,
persistentConnection: true
}
}
},
cache: {
expire: {
static: {
seconds: 60
}
},
scope: {
narrowScope: {}
}
}
}
});
});
it('should handle the Invoke transformation without cache', async () => {
// Arrange
const wrappedAsset = {
inputSchema: {
kind: 'Invoke',
apiVersion: 'api.ibm.com/v1',
metadata: {
name: 'invoke-single-backend',
version: '1.0.0',
namespace: 'sample'
},
spec: {
input: 'request',
output: 'response',
endpoint: {
http: {
verb: 'GET',
status_exception: {
pattern: 500
},
target: {
tlsClientProfile: 'some_tls_profile',
url: 'https://localhost:3000',
urlType: 'plain',
version: 'HTTP/1.0',
timeout: 60,
compression: false,
chunkedUploads: false,
persistentConnection: true
}
}
}
}
},
metadata: {
name: 'invoke-single-backend',
version: '1.0.0',
namespace: 'sample'
}
};
const config = {
skipTransform: false,
transformations: {
mappings: [
{
source: '$.spec.input',
target: '$.Invoke.input'
},
{
source: '$.spec.output',
target: '$.Invoke.output'
},
{
source: '$.spec.endpoint',
target: '$.Invoke.endpoint'
}
],
replacements: [
{
target: '$.Invoke.cache',
value: '{{$.spec.cache}}',
precedence: 10,
condition: '$.spec.cache',
operation: 'replace'
},
{
target: '$.Invoke.cache',
value: null,
precedence: 20,
operation: 'replace'
}
]
}
};
// Act
const result = await transformer.transform(wrappedAsset, config);
// Assert
expect(result.outputAsset).toEqual({
Invoke: {
input: 'request',
output: 'response',
endpoint: {
http: {
verb: 'GET',
status_exception: {
pattern: 500
},
target: {
tlsClientProfile: 'some_tls_profile',
url: 'https://localhost:3000',
urlType: 'plain',
version: 'HTTP/1.0',
timeout: 60,
compression: false,
chunkedUploads: false,
persistentConnection: true
}
}
},
cache: null
}
});
});
describe('error handling', () => {
it('should handle errors in transform method', async () => {
// Arrange
const wrappedAsset = {
inputSchema: {
kind: 'Test',
apiVersion: 'v1',
metadata: {
name: 'test',
version: '1.0.0'
},
spec: {}
},
metadata: {
name: 'test',
version: '1.0.0'
}
};
const config = {
transformations: {
mappings: [
{
source: 'invalid-path', // Invalid path format
target: '$.Test.value'
}
]
}
};
// Act & Assert
// The error is caught and logged, but not rethrown in applyMappings
const result = await transformer.transform(wrappedAsset, config);
expect(result.outputAsset).toEqual({});
});
it('should handle errors in applyMappings', async () => {
// Arrange
const wrappedAsset = {
inputSchema: {
kind: 'Test',
apiVersion: 'v1',
metadata: {
name: 'test',
version: '1.0.0'
},
spec: {}
},
metadata: {
name: 'test',
version: '1.0.0'
}
};
// This test is already covered by the previous test
// We'll just verify that the transform method works with nonexistent paths
const config = {
transformations: {
mappings: [
{
source: '$.nonexistent.path',
target: '$.Test.value'
}
]
}
};
// Act
const result = await transformer.transform(wrappedAsset, config);
// Assert
expect(result.outputAsset).toEqual({});
});
});
describe('array iteration replacements', () => {
it('should apply array iteration replacements', async () => {
// Arrange
const wrappedAsset = {
inputSchema: {
kind: 'Test',
apiVersion: 'v1',
metadata: {
name: 'test',
version: '1.0.0'
},
spec: {
items: [
{ id: 1, name: 'Item 1' },
{ id: 2, name: 'Item 2' },
{ id: 3, name: 'Item 3' }
]
}
},
metadata: {
name: 'test',
version: '1.0.0'
}
};
const config = {
transformations: {
replacements: [
{
target: '$.Test.items[{{$.spec.items[*].id}}]',
value: '{{$.spec.items[*].name}}',
precedence: 10,
operation: 'replace'
}
]
}
};
// Act
const result = await transformer.transform(wrappedAsset, config);
// Assert
expect(result.outputAsset).toEqual({
Test: {
items: {
'1': 'Item 1',
'2': 'Item 2',
'3': 'Item 3'
}
}
});
});
it('should handle invalid array paths in array iteration', async () => {
// Arrange
const wrappedAsset = {
inputSchema: {
kind: 'Test',
apiVersion: 'v1',
metadata: {
name: 'test',
version: '1.0.0'
},
spec: {
notAnArray: 'string value'
}
},
metadata: {
name: 'test',
version: '1.0.0'
}
};
const config = {
transformations: {
replacements: [
{
target: '$.Test.items',
value: 'Value',
precedence: 10,
condition: '$.spec.notAnArray[*]', // This is not an array
operation: 'replace'
}
]
}
};
// Act
const result = await transformer.transform(wrappedAsset, config);
// Assert
expect(result.outputAsset).toEqual({});
});
it('should handle complex array iteration with object templates', async () => {
// Arrange
const wrappedAsset = {
inputSchema: {
kind: 'Test',
apiVersion: 'v1',
metadata: {
name: 'test',
version: '1.0.0'
},
spec: {
items: [
{ id: 1, details: { name: 'Item 1', value: 100 } },
{ id: 2, details: { name: 'Item 2', value: 200 } }
]
}
},
metadata: {
name: 'test',
version: '1.0.0'
}
};
const config = {
transformations: {
replacements: [
{
target: '$.Test.processedItems[{{$.spec.items[*].id}}]',
value: {
name: '{{$.spec.items[*].details.name}}',
processedValue: '{{$.spec.items[*].details.value}}'
},
precedence: 10,
operation: 'replace'
}
]
}
};
// Act
const result = await transformer.transform(wrappedAsset, config);
// Assert
expect(result.outputAsset).toEqual({
Test: {
processedItems: {
'1': {
name: 'Item 1',
processedValue: 100
},
'2': {
name: 'Item 2',
processedValue: 200
}
}
}
});
});
});
describe('path handling', () => {
it('should handle root path operations', async () => {
// Arrange
const wrappedAsset = {
inputSchema: {
kind: 'Test',
apiVersion: 'v1',
metadata: {
name: 'test',
version: '1.0.0'
},
spec: {
rootObject: {
prop1: 'value1',
prop2: 'value2'
}
}
},
metadata: {
name: 'test',
version: '1.0.0'
}
};
const config = {
transformations: {
replacements: [
{
target: '$',
value: '{{$.spec.rootObject}}',
precedence: 10,
operation: 'replace'
}
]
}
};
// Act
const result = await transformer.transform(wrappedAsset, config);
// Assert
expect(result.outputAsset).toEqual({
prop1: 'value1',
prop2: 'value2'
});
});
it('should throw error for invalid path format', async () => {
// Arrange
const wrappedAsset = {
inputSchema: {
kind: 'Test',
apiVersion: 'v1',
metadata: {
name: 'test',
version: '1.0.0'
},
spec: {}
},
metadata: {
name: 'test',
version: '1.0.0'
}
};
const config = {
transformations: {
replacements: [
{
target: 'invalid.path', // Missing $ prefix
value: 'value',
precedence: 10,
operation: 'replace'
}
]
}
};
// Act & Assert
await expect(transformer.transform(wrappedAsset, config)).rejects.toThrow(transformer_error_1.TransformerError);
});
it('should handle non-object value for root path', async () => {
// Arrange
const wrappedAsset = {
inputSchema: {
kind: 'Test',
apiVersion: 'v1',
metadata: {
name: 'test',
version: '1.0.0'
},
spec: {
stringValue: 'just a string'
}
},
metadata: {
name: 'test',
version: '1.0.0'
}
};
// This test is already covered by other tests
// We'll just verify that the transform method works with non-object values for root path
const config = {
transformations: {
replacements: [
{
target: '$',
value: '{{$.spec.stringValue}}',
precedence: 10,
operation: 'replace'
}
]
}
};
// Act
const result = await transformer.transform(wrappedAsset, config);
// Assert
expect(result.outputAsset).toEqual({});
});
});
describe('template processing', () => {
it('should process templates with multiple placeholders', async () => {
// Arrange
const wrappedAsset = {
inputSchema: {
kind: 'Test',
apiVersion: 'v1',
metadata: {
name: 'test',
version: '1.0.0'
},
spec: {
firstName: 'John',
lastName: 'Doe',
age: 30
}
},
metadata: {
name: 'test',
version: '1.0.0'
}
};
const config = {
transformations: {
replacements: [
{
target: '$.Test.fullInfo',
value: 'Name: {{$.spec.firstName}} {{$.spec.lastName}}, Age: {{$.spec.age}}',
precedence: 10,
operation: 'replace'
}
]
}
};
// Act
const result = await transformer.transform(wrappedAsset, config);
// Assert
expect(result.outputAsset).toEqual({
Test: {
fullInfo: 'Name: John Doe, Age: 30'
}
});
});
it('should process templates with object values', async () => {
// Arrange
const wrappedAsset = {
inputSchema: {
kind: 'Test',
apiVersion: 'v1',
metadata: {
name: 'test',
version: '1.0.0'
},
spec: {
person: {
firstName: 'John',
lastName: 'Doe',
address: {
city: 'New York',
country: 'USA'
}
}
}
},
metadata: {
name: 'test',
version: '1.0.0'
}
};
const config = {
transformations: {
replacements: [
{
target: '$.Test.personInfo',
value: 'Person: {{$.spec.person}}',
precedence: 10,
operation: 'replace'
}
]
}
};
// Act
const result = await transformer.transform(wrappedAsset, config);
// Assert
expect(result.outputAsset).toEqual({
Test: {
personInfo: `Person: ${JSON.stringify(wrappedAsset.inputSchema.spec.person)}`
}
});
});
it('should handle invalid path in template', async () => {
// Arrange
const wrappedAsset = {
inputSchema: {
kind: 'Test',
apiVersion: 'v1',
metadata: {
name: 'test',
version: '1.0.0'
},
spec: {}
},
metadata: {
name: 'test',
version: '1.0.0'
}
};
const config = {
transformations: {
replacements: [
{
target: '$.Test.value',
value: '{{invalidPath}}', // Invalid path format
precedence: 10,
operation: 'replace'
}
]
}
};
// Act & Assert
await expect(transformer.transform(wrappedAsset, config)).rejects.toThrow(transformer_error_1.TransformerError);
});
});
describe('protected methods', () => {
it('should extract source version correctly', () => {
// This is a direct test of the protected method using type casting
const version = transformer.extractSourceVersion('api.ibm.com/v1');
expect(version).toBe('v1');
});
it('should handle complex version strings', () => {
// This is a direct test of the protected method using type casting
const version = transformer.extractSourceVersion('api.ibm.com/v1alpha2');
expect(version).toBe('v1alpha2');
});
it('should sanitize path components correctly', () => {
// This is a direct test of the protected method using type casting
const sanitized = transformer.sanitizePathComponent('file../../../etc/passwd.txt');
expect(sanitized).toBe('fileetcpasswd.txt');
});
});
describe('removeValueByPath', () => {
it('should remove values by path', async () => {
// Arrange
const wrappedAsset = {
inputSchema: {
kind: 'Test',
apiVersion: 'v1',
metadata: {
name: 'test',
version: '1.0.0'
},
spec: {}
},
metadata: {
name: 'test',
version: '1.0.0'
}
};
const config = {
transformations: {
replacements: [
{
target: '$.Test.value',
value: 'Initial Value',
precedence: 10,
operation: 'replace'
},
{
target: '$.Test.value',
precedence: 20,
operation: 'remove'
}
]
}
};
// Act
const result = await transformer.transform(wrappedAsset, config);
// Assert
expect(result.outputAsset).toEqual({
Test: {}
});
});
it('should handle remove operation on non-existent paths', async () => {
// Arrange
const wrappedAsset = {
inputSchema: {
kind: 'Test',
apiVersion: 'v1',
metadata: {
name: 'test',
version: '1.0.0'
},
spec: {}
},
metadata: {
name: 'test',
version: '1.0.0'
}
};
const config = {
transformations: {
replacements: [
{
target: '$.Test.nonExistent.deepPath',
precedence: 10,
operation: 'remove'
}
]
}
};
// Act
const result = await transformer.transform(wrappedAsset, config);
// Assert
expect(result.outputAsset).toEqual({});
});
});
});
});