UNPKG

@apistudio/apim-cli

Version:

CLI for API Management Products

309 lines (251 loc) 11.9 kB
/** * Copyright Super iPaaS Integration LLC, an IBM Company 2024 */ /* eslint-disable @typescript-eslint/no-explicit-any */ import AdmZip from 'adm-zip'; import { loadCacheWithProject } from './asset-cache-helper.js'; import { isYamlFile } from '../common/fs-helper.js'; import { readMultiYaml } from '../common/yaml-helper.js'; import { isValidAsset } from './asset-helper.js'; import { AssetCache } from '../../cache/asset-cache.js'; import { getRefsFromAsset } from '../../handlers/asset-handler.js'; import { BaseAsset } from '../../model/assets-model.js'; jest.mock('../common/fs-helper.js', () => ({ isYamlFile: jest.fn(), })); jest.mock('../common/yaml-helper.js', () => ({ readMultiYaml: jest.fn(), })); jest.mock('./asset-helper.js', () => ({ isValidAsset: jest.fn(), })); jest.mock('../common/message-helper.js', () => ({ showWarning: jest.fn(), showInfo: jest.fn() })); jest.mock('../../cache/asset-cache.js', () => { return { AssetCache: { getInstance: jest.fn().mockReturnValue({ markAsProcessed: jest.fn(), checkAndMarkAsUnProcessed: jest.fn(), }), }, }; }); jest.mock('../../handlers/asset-handler.js', () => ({ getRefsFromAsset: jest.fn(), })); describe('Asset cache helper function test suite', () => { afterEach(() => { jest.clearAllMocks(); }); it('should handle empty zip', () => { const zip = new AdmZip(); jest.spyOn(zip, 'getEntries').mockReturnValue([]); loadCacheWithProject(zip); expect(readMultiYaml).not.toHaveBeenCalled(); expect(isValidAsset).not.toHaveBeenCalled(); expect(AssetCache.getInstance().markAsProcessed).not.toHaveBeenCalled(); expect(getRefsFromAsset).not.toHaveBeenCalled(); }); it('should handle zip with invalid asset', () => { const zip = new AdmZip(); const mockAsset = { kind: 'invalidKind', metadata: { name: 'invalidName' } } as BaseAsset; jest.spyOn(zip, 'getEntries').mockReturnValue([ { entryName: 'asset.yml', isDirectory: false, getData: jest.fn().mockReturnValue(Buffer.from('invalid yaml')) } as any, ]); (isYamlFile as jest.Mock).mockReturnValue(true); (readMultiYaml as jest.Mock).mockReturnValue([mockAsset]); (isValidAsset as jest.Mock).mockReturnValue(false); loadCacheWithProject(zip); expect(readMultiYaml).toHaveBeenCalledWith('asset.yml', 'invalid yaml'); expect(isValidAsset).toHaveBeenCalledWith(mockAsset); expect(AssetCache.getInstance().markAsProcessed).not.toHaveBeenCalled(); expect(getRefsFromAsset).not.toHaveBeenCalled(); }); it('should handle zip with non-yaml files', () => { const zip = new AdmZip(); jest.spyOn(zip, 'getEntries').mockReturnValue([ { entryName: 'file.txt', isDirectory: false, getData: jest.fn() } as any, { entryName: 'image.png', isDirectory: false, getData: jest.fn() } as any, ]); (isYamlFile as jest.Mock).mockReturnValue(false); loadCacheWithProject(zip); expect(readMultiYaml).not.toHaveBeenCalled(); expect(isValidAsset).not.toHaveBeenCalled(); expect(AssetCache.getInstance().markAsProcessed).not.toHaveBeenCalled(); expect(getRefsFromAsset).not.toHaveBeenCalled(); }); it('should handle zip with one valid asset', () => { const zip = new AdmZip(); const mockAsset = { kind: 'validKind', metadata: { name: 'validName' } } as BaseAsset; const mockRefs = [{ kind: 'refKind', metadata: { name: 'refName' } }] as BaseAsset[]; jest.spyOn(zip, 'getEntries').mockReturnValue([ { entryName: 'asset.yml', isDirectory: false, getData: jest.fn().mockReturnValue(Buffer.from('valid yaml')) } as any, ]); (isYamlFile as jest.Mock).mockReturnValue(true); (readMultiYaml as jest.Mock).mockReturnValue([mockAsset]); (isValidAsset as jest.Mock).mockReturnValue(true); (getRefsFromAsset as jest.Mock).mockReturnValue(mockRefs); loadCacheWithProject(zip); expect(readMultiYaml).toHaveBeenCalledWith('asset.yml', 'valid yaml'); expect(isValidAsset).toHaveBeenCalledWith(mockAsset); expect(AssetCache.getInstance().markAsProcessed).toHaveBeenCalledWith(mockAsset); expect(getRefsFromAsset).toHaveBeenCalledWith(mockAsset); expect(AssetCache.getInstance().checkAndMarkAsUnProcessed).toHaveBeenCalledWith(mockRefs[0]); }); it('should handle dependency assets correctly', () => { const zip = new AdmZip(); const mockAsset = { kind: 'validKind', metadata: { name: 'validName' } } as BaseAsset; const mockRefAsset = { kind: 'refKind', metadata: { name: 'refName' } } as BaseAsset; const mockRefs = [mockRefAsset]; jest.spyOn(zip, 'getEntries').mockReturnValue([ { entryName: 'asset.yml', isDirectory: false, getData: jest.fn().mockReturnValue(Buffer.from('valid yaml')) } as any, ]); (isYamlFile as jest.Mock).mockReturnValue(true); (readMultiYaml as jest.Mock).mockReturnValue([mockAsset]); (isValidAsset as jest.Mock).mockReturnValue(true); (getRefsFromAsset as jest.Mock).mockReturnValue(mockRefs); loadCacheWithProject(zip); expect(readMultiYaml).toHaveBeenCalledWith('asset.yml', 'valid yaml'); expect(isValidAsset).toHaveBeenCalledWith(mockAsset); expect(AssetCache.getInstance().markAsProcessed).toHaveBeenCalledWith(mockAsset); expect(getRefsFromAsset).toHaveBeenCalledWith(mockAsset); expect(AssetCache.getInstance().checkAndMarkAsUnProcessed).toHaveBeenCalledWith(mockRefAsset); }); it('should process multiple valid assets in one YAML file', () => { const zip = new AdmZip(); const mockAssets = [ { kind: 'validKind1', metadata: { name: 'validName1' } } as BaseAsset, { kind: 'validKind2', metadata: { name: 'validName2' } } as BaseAsset, ]; jest.spyOn(zip, 'getEntries').mockReturnValue([ { entryName: 'assets.yml', isDirectory: false, getData: jest.fn().mockReturnValue(Buffer.from('multiple valid yaml')) } as any, ]); (isYamlFile as jest.Mock).mockReturnValue(true); (readMultiYaml as jest.Mock).mockReturnValue(mockAssets); (isValidAsset as jest.Mock).mockReturnValue(true); loadCacheWithProject(zip); mockAssets.forEach(asset => { expect(isValidAsset).toHaveBeenCalledWith(asset); expect(AssetCache.getInstance().markAsProcessed).toHaveBeenCalledWith(asset); }); expect(readMultiYaml).toHaveBeenCalledWith('assets.yml', 'multiple valid yaml'); }); it('should process valid assets and skip invalid ones in one YAML file', () => { const zip = new AdmZip(); const validAsset = { kind: 'validKind', metadata: { name: 'validName' } } as BaseAsset; const invalidAsset = { kind: 'invalidKind', metadata: { name: 'invalidName' } } as BaseAsset; jest.spyOn(zip, 'getEntries').mockReturnValue([ { entryName: 'assets.yml', isDirectory: false, getData: jest.fn().mockReturnValue(Buffer.from('valid and invalid yaml')) } as any, ]); (isYamlFile as jest.Mock).mockReturnValue(true); (readMultiYaml as jest.Mock).mockReturnValue([validAsset, invalidAsset]); (isValidAsset as jest.Mock) .mockReturnValueOnce(true) // validAsset .mockReturnValueOnce(false); // invalidAsset loadCacheWithProject(zip); expect(readMultiYaml).toHaveBeenCalledWith('assets.yml', 'valid and invalid yaml'); expect(isValidAsset).toHaveBeenCalledWith(validAsset); expect(isValidAsset).toHaveBeenCalledWith(invalidAsset); expect(AssetCache.getInstance().markAsProcessed).toHaveBeenCalledWith(validAsset); expect(AssetCache.getInstance().markAsProcessed).not.toHaveBeenCalledWith(invalidAsset); }); it('should extract and pass source project name from entry path', () => { const zip = new AdmZip(); const mockAsset = { kind: 'Product', metadata: { name: 'TestProduct' } } as BaseAsset; const mockRef = { kind: 'API', ref: 'dev:PaymentAPI:1.0', isNewlyAdded: true }; jest.spyOn(zip, 'getEntries').mockReturnValue([ { entryName: 'ProjectA/api-assets/product.yml', isDirectory: false, getData: jest.fn().mockReturnValue(Buffer.from('yaml content')) } as any, ]); (isYamlFile as jest.Mock).mockReturnValue(true); (readMultiYaml as jest.Mock).mockReturnValue([mockAsset]); (isValidAsset as jest.Mock).mockReturnValue(true); (getRefsFromAsset as jest.Mock).mockReturnValue([mockRef]); loadCacheWithProject(zip); // Verify that checkForDependencyAssets was called with the source project expect(getRefsFromAsset).toHaveBeenCalledWith(mockAsset); expect(AssetCache.getInstance().checkAndMarkAsUnProcessed).toHaveBeenCalledWith( expect.objectContaining({ kind: 'API', ref: 'dev:PaymentAPI:1.0', isNewlyAdded: true, sourceProject: 'ProjectA' }) ); }); it('should handle multiple projects with different source projects', () => { const zip = new AdmZip(); const mockAssetA = { kind: 'Product', metadata: { name: 'ProductA' } } as BaseAsset; const mockAssetB = { kind: 'Product', metadata: { name: 'ProductB' } } as BaseAsset; const mockRefA = { kind: 'API', ref: 'dev:API_A:1.0', isNewlyAdded: true }; const mockRefB = { kind: 'API', ref: 'dev:API_B:1.0', isNewlyAdded: true }; jest.spyOn(zip, 'getEntries').mockReturnValue([ { entryName: 'ProjectA/api-assets/product.yml', isDirectory: false, getData: jest.fn().mockReturnValue(Buffer.from('yaml A')) } as any, { entryName: 'ProjectC/api-assets/product.yml', isDirectory: false, getData: jest.fn().mockReturnValue(Buffer.from('yaml B')) } as any, ]); (isYamlFile as jest.Mock).mockReturnValue(true); (readMultiYaml as jest.Mock) .mockReturnValueOnce([mockAssetA]) .mockReturnValueOnce([mockAssetB]); (isValidAsset as jest.Mock).mockReturnValue(true); (getRefsFromAsset as jest.Mock) .mockReturnValueOnce([mockRefA]) .mockReturnValueOnce([mockRefB]); loadCacheWithProject(zip); // Verify each dependency has correct source project expect(AssetCache.getInstance().checkAndMarkAsUnProcessed).toHaveBeenCalledWith( expect.objectContaining({ kind: 'API', ref: 'dev:API_A:1.0', sourceProject: 'ProjectA' }) ); expect(AssetCache.getInstance().checkAndMarkAsUnProcessed).toHaveBeenCalledWith( expect.objectContaining({ kind: 'API', ref: 'dev:API_B:1.0', sourceProject: 'ProjectC' }) ); }); it('should handle nested folder structure in entry path', () => { const zip = new AdmZip(); const mockAsset = { kind: 'Product', metadata: { name: 'TestProduct' } } as BaseAsset; const mockRef = { kind: 'API', ref: 'dev:API:1.0', isNewlyAdded: true }; jest.spyOn(zip, 'getEntries').mockReturnValue([ { entryName: 'ProjectA/nested/folder/product.yml', isDirectory: false, getData: jest.fn().mockReturnValue(Buffer.from('yaml')) } as any, ]); (isYamlFile as jest.Mock).mockReturnValue(true); (readMultiYaml as jest.Mock).mockReturnValue([mockAsset]); (isValidAsset as jest.Mock).mockReturnValue(true); (getRefsFromAsset as jest.Mock).mockReturnValue([mockRef]); loadCacheWithProject(zip); // Should extract 'ProjectA' as source project even with nested folders expect(AssetCache.getInstance().checkAndMarkAsUnProcessed).toHaveBeenCalledWith( expect.objectContaining({ sourceProject: 'ProjectA' }) ); }); it('should handle entry path without project folder', () => { const zip = new AdmZip(); const mockAsset = { kind: 'Product', metadata: { name: 'TestProduct' } } as BaseAsset; const mockRef = { kind: 'API', ref: 'dev:API:1.0', isNewlyAdded: true }; jest.spyOn(zip, 'getEntries').mockReturnValue([ { entryName: 'product.yml', isDirectory: false, getData: jest.fn().mockReturnValue(Buffer.from('yaml')) } as any, ]); (isYamlFile as jest.Mock).mockReturnValue(true); (readMultiYaml as jest.Mock).mockReturnValue([mockAsset]); (isValidAsset as jest.Mock).mockReturnValue(true); (getRefsFromAsset as jest.Mock).mockReturnValue([mockRef]); loadCacheWithProject(zip); // Should use the filename as source project when no folder structure expect(AssetCache.getInstance().checkAndMarkAsUnProcessed).toHaveBeenCalledWith( expect.objectContaining({ sourceProject: 'product.yml' }) ); }); });