UNPKG

@apistudio/apim-cli

Version:

CLI for API Management Products

358 lines (331 loc) 11.9 kB
/** * 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; } }