@apistudio/apim-cli
Version:
CLI for API Management Products
358 lines (331 loc) • 11.9 kB
text/typescript
/**
* Copyright Super iPaaS Integration LLC, an IBM Company 2024
*/
import yaml from 'js-yaml';
import { GatewayAssetHandler } from '../converter/gateway-asset.handler.js';
import { RefParser } from '../parsers/ref.parser.js';
import { AssertionsGenerator } from '../service/assertMapper.js';
import pkg from 'postman-collection';
const {FormParam,RequestBody}=pkg;
import {
Environment,
EnvironmentVariable,
RequestItem,
RequestParameter,
RequestSettting,
RequestBodyObject,
TestSpec,
Env_Spec,
Assertions,
TestMetadata,
ExpType, ICollectionCreator,RequestHeader,RequestAuth,TestAssertion,FormDataItem,ExtendedFormParamDefinition,RawData,ExtendedRequestBodyDefinition,
UrlEncodedFormDataItem
} from '../models/interface.js';
import JSZip from 'jszip';
import { LogWrapper } from '../service/log-wrapper.js';
export class CollectionCreator implements ICollectionCreator {
private async getApiEndpoints(buffer: Buffer, key: string) {
LogWrapper.logInfo('0212', key);
const gatewayHandler = new GatewayAssetHandler(buffer);
return gatewayHandler.getApiEndpoints(key);
}
async createCollection(parsedData: TestSpec, fileBuffer: Buffer) {
LogWrapper.logInfo('0213', `${parsedData.metadata.name}`);
const metadata = parsedData.metadata as TestMetadata;
const collection = this.initializeCollection(metadata.name);
const spec = await this.getReplacedItems(parsedData, fileBuffer);
const api = spec.api;
let endpoints=[];
if(api.$ref) {
endpoints = await this.getApiEndpoints(fileBuffer, api.$ref);
if (endpoints.length === 0) {
LogWrapper.logError('0004', `No endpoints found for reference: ${api.$ref}`);
throw new Error('Endpoints are empty and invalid asset');
}
}
else {
endpoints=[api.$endpoint];
}
if (spec.request) {
for (const requestItem of spec.request) {
const item = await this.constructItem(requestItem, fileBuffer,parsedData);
endpoints.forEach((endpoint: string) => {
const updatedItem = this.updateCollectionBaseUrl(item, endpoint, requestItem.resource, requestItem.parameters as RequestParameter[]);
// @ts-ignore
collection.item.push(updatedItem);
});
}
}
LogWrapper.logInfo('0214', metadata.name);
return collection;
}
private initializeCollection(value: string) {
LogWrapper.logDebug('0003', `Initializing collection with name: ${value}`);
return {
info: {
name: `${value} Collection`,
version: '1.0.0',
schema: 'https://schema.getpostman.com/json/collection/v2.1.0/collection.json'
},
item: []
};
}
private async getReplacedItems(parsedData: TestSpec, fileBuffer: Buffer) {
LogWrapper.logInfo('0003', 'Replacing placeholders in requests');
if (parsedData.spec?.environment?.$ref) {
const result = await this.resolveRef(fileBuffer, parsedData.spec.environment.$ref);
return this.replacePlaceholders(parsedData.spec, result);
} else if (parsedData.spec?.environment?.variables !== undefined) {
return this.replacePlaceholders(parsedData.spec, parsedData.spec.environment?.variables as EnvironmentVariable[]);
} else {
return parsedData.spec;
}
}
private async constructItem(requestItem: RequestItem, fileBuffer: Buffer,parsedData:any) {
LogWrapper.logDebug('0003', `Constructing item for request: ${requestItem.method} ${requestItem.resource}`);
return {
name: `${requestItem.method} ${requestItem.resource}`,
event: await this.constructAssertion(requestItem.assertions, fileBuffer,parsedData),
protocolProfileBehavior: requestItem.settings ? this.constructSettings(requestItem.settings) : undefined,
request: {
url: {},
method: requestItem.method,
header: this.constructHeaders(requestItem.headers as RequestHeader[]),
body: requestItem.payload ? this.constructRequestBody(requestItem.payload) : undefined,
auth: this.constructAuth(requestItem.auth)
}
};
}
private constructHeaders(headers: RequestHeader[] | undefined) {
LogWrapper.logDebug('0003', 'Constructing headers');
return headers ? headers.map((header: { key: string; value: string }) => ({
key: header.key,
value: header.value
})) : [];
}
private constructAuth(auth: RequestAuth | undefined) {
LogWrapper.logDebug('0003', 'Constructing auth for request');
if (!auth) {
return undefined;
}
if (auth.basicAuth) {
return {
type: 'basic',
basic: [{
key: 'username',
value: auth.basicAuth.username
}, {
key: 'password',
value: auth.basicAuth.password
}]
};
} else if (auth.bearerToken) {
return {
type: 'bearer',
bearer: [{
key: 'token',
value: auth.bearerToken
}]
};
} else if (auth.noauth) {
return { type: 'noauth' };
}
return undefined;
}
private async resolveRef(fileBuffer: Buffer, ref: string): Promise<EnvironmentVariable[]> {
LogWrapper.logInfo('0003', `Resolving reference: ${ref}`);
const parseRefObj = new RefParser();
const { namespace, name, version } = parseRefObj.parseRef(ref);
const entries = await this.loadZipEntries(fileBuffer);
for (const data of entries) {
const pds = yaml.loadAll(data) as Environment[];
for (const pd of pds) {
if (
pd.kind?.toLowerCase() === 'environment' &&
pd.metadata.name === name &&
(typeof pd.metadata.version === 'number' ? Number(version) === Number(pd.metadata.version) : pd.metadata.version === version) &&
(namespace ? pd.metadata.namespace === namespace : true)
) {
LogWrapper.logDebug('0003', `Reference resolved successfully: ${ref}`);
const spec = pd.spec as Env_Spec;
return spec.variables as EnvironmentVariable[];
}
}
}
LogWrapper.logError('0004', `Reference not found: ${ref}`);
throw new Error(`Reference ${ref} not found in the provided files.`);
}
// @ts-ignore
private async loadZipEntries(fileBuffer) {
LogWrapper.logDebug('0003', 'Loading ZIP entries');
const zip = new JSZip();
const zipContent = await zip.loadAsync(fileBuffer);
const entries = [];
for (const fileName in zipContent.files) {
const file = zipContent.files[fileName];
if (!file.dir) {
const content = await file.async('string');
entries.push(content);
}
}
LogWrapper.logInfo('0003', `Loaded ${entries.length} entries from ZIP`);
return entries;
}
private replacePlaceholders(obj: any, variables: EnvironmentVariable[]){
LogWrapper.logDebug('0003', 'Replacing placeholders in object');
if (typeof obj === 'string') {
variables.forEach(variable => {
const placeholderRegex = new RegExp(`\\$\\{${variable.key}\\}`, 'g');
// @ts-ignore
obj = obj.replace(placeholderRegex, variable.value);
});
} else if (Array.isArray(obj)) {
obj.forEach((item, index) => {
// @ts-ignore
obj[index] = this.replacePlaceholders(item, variables);
});
} else if (typeof obj === 'object' && obj !== null) {
Object.keys(obj).forEach(key => {
// @ts-ignore
obj[key] = this.replacePlaceholders(obj[key], variables);
});
}
return obj;
}
private async constructAssertion(obj: Assertions, fileBuffer: Buffer,parsedData:any) {
LogWrapper.logDebug('0003', 'Constructing assertions');
const exec: string[] = [];
let asserts: ExpType[] = [];
const assertGen = new AssertionsGenerator();
if (obj.$ref !== undefined) {
asserts = await this.resolveRefAssertion(fileBuffer, obj.$ref);
if(parsedData?.spec?.environment?.$ref){
const result = await this.resolveRef(fileBuffer, parsedData.spec.environment.$ref);
asserts= this.replacePlaceholders(asserts, result);
}
}
if (obj.expressions !== undefined) {
asserts = asserts.concat(obj.expressions as ExpType[]);
}
if (asserts.length > 0) {
exec.push(assertGen.initializeCommonScriptLibrary());
asserts.forEach(assert => {
exec.push(assertGen.converttoAssertionStructure(assert));
});
}
return [{
'listen': 'test',
'script': {
'exec': exec
}
}];
}
private async resolveRefAssertion(fileBuffer: Buffer, ref: string) {
LogWrapper.logInfo('0003', `Resolving assertion reference: ${ref}`);
const parseRefObj = new RefParser();
const { namespace, name, version } = parseRefObj.parseRef(ref);
const entries = await this.loadZipEntries(fileBuffer);
for (const data of entries) {
const pds = yaml.loadAll(data) as TestAssertion[];
for (const pd of pds) {
if (
pd.kind?.toLowerCase() === 'assertion' &&
pd.metadata.name === name &&
(typeof pd.metadata.version === 'number' ? Number(version) === Number(pd.metadata.version) : pd.metadata.version === version) &&
(namespace ? pd.metadata.namespace === namespace : true)
) {
LogWrapper.logDebug('0003', `Assertion reference resolved: ${ref}`);
return pd.spec as ExpType[];
}
}
}
LogWrapper.logError('0003', `Assertion reference not found: ${ref}`);
throw new Error(`Reference ${ref} not found in the provided files.`);
}
private constructSettings(obj: RequestSettting) {
LogWrapper.logDebug('0003', 'Constructing request settings');
return {
'disableUrlEncoding': !obj.encodeURL ? false : true,
'strictSSL': obj.sslVerification
};
}
private constructRequestBody(obj: RequestBodyObject): InstanceType<typeof RequestBody> | undefined {
LogWrapper.logDebug('0003', 'Constructing request body');
if (obj.formData) {
return this.constructFormDataBody(obj.formData as FormDataItem[]);
} else if (obj.urlEncodedFormData) {
return this.constructUrlEncodedBody(obj.urlEncodedFormData as UrlEncodedFormDataItem[]);
} else if (obj.raw) {
return this.constructRawBody(obj.raw);
}
return undefined;
}
private constructFormDataBody(formData: FormDataItem[]): InstanceType<typeof RequestBody> {
LogWrapper.logDebug('0003', 'Constructing form data body');
const formDataParams = formData.map(item => new FormParam({
key: item.key,
src: item.type === 'file' ? item.value : undefined,
value: item.type !== 'file' ? item.value : undefined,
type: item.type
} as ExtendedFormParamDefinition));
return new RequestBody({
mode: 'formdata',
formdata: formDataParams
});
}
private constructUrlEncodedBody(urlEncodedFormData: Array<{
key: string;
value: string
}>): InstanceType<typeof RequestBody> {
LogWrapper.logDebug('0003', 'Constructing URL encoded body');
const formDataParams = urlEncodedFormData.map(item => new FormParam({
key: item.key,
value: item.value,
type: 'text'
} as ExtendedFormParamDefinition));
return new RequestBody({
mode: 'urlencoded',
urlencoded: formDataParams
});
}
private constructRawBody(raw: RawData): InstanceType<typeof RequestBody> | undefined {
LogWrapper.logDebug('0003', 'Constructing raw body');
const rawTypes = ['json', 'js', 'html', 'xml'] as const;
for (const type of rawTypes) {
if (raw[type]) {
return new RequestBody({
mode: 'raw',
raw: raw[type],
options: {
raw: {
language: type === 'js' ? 'javascript' : type
}
}
} as ExtendedRequestBodyDefinition);
}
}
return undefined;
}
private constructUrl(url: string, reqPath: string, param: RequestParameter[]) {
LogWrapper.logDebug('0003', 'Constructing URL');
const myURL = new URL(url + reqPath);
return {
raw: url + reqPath,
protocol: myURL.protocol.slice(0, -1),
host: myURL.hostname.split('.').filter(p => p),
path: myURL.pathname.split('/').filter(p => p),
port: myURL.port ? myURL.port : undefined,
query: param ? param.map(variable => ({
key: variable.key,
value: variable.value
})) : []
};
}
private updateCollectionBaseUrl(item: unknown, Endpoint: string, reqPath: string, Parameters: RequestParameter[]) {
LogWrapper.logDebug('0003', 'Updating collection base URL');
const urlObj = this.constructUrl(Endpoint, reqPath, Parameters);
const ItemDeepCopy = JSON.parse(JSON.stringify(item));
ItemDeepCopy.request.url = urlObj;
return ItemDeepCopy;
}
}