Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
326 changes: 326 additions & 0 deletions server/adaptors/integrations/__test__/builder.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,326 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import { SavedObjectsClientContract } from '../../../../../../src/core/server';
import { IntegrationInstanceBuilder } from '../integrations_builder';
import { Integration } from '../repository/integration';

const mockSavedObjectsClient: SavedObjectsClientContract = ({
bulkCreate: jest.fn(),
create: jest.fn(),
delete: jest.fn(),
find: jest.fn(),
get: jest.fn(),
update: jest.fn(),
} as unknown) as SavedObjectsClientContract;

const sampleIntegration: Integration = ({
deepCheck: jest.fn().mockResolvedValue(true),
getAssets: jest.fn().mockResolvedValue({
savedObjects: [
{
id: 'asset1',
references: [{ id: 'ref1' }],
},
{
id: 'asset2',
references: [{ id: 'ref2' }],
},
],
}),
getConfig: jest.fn().mockResolvedValue({
name: 'integration-template',
type: 'integration-type',
}),
} as unknown) as Integration;

describe('IntegrationInstanceBuilder', () => {
let builder: IntegrationInstanceBuilder;

beforeEach(() => {
builder = new IntegrationInstanceBuilder(mockSavedObjectsClient);
});

afterEach(() => {
jest.clearAllMocks();
});

describe('build', () => {
it('should build an integration instance', async () => {
const options = {
dataSource: 'instance-datasource',
name: 'instance-name',
};

const remappedAssets = [
{
id: 'remapped-asset1',
references: [{ id: 'remapped-ref1' }],
},
{
id: 'remapped-asset2',
references: [{ id: 'remapped-ref2' }],
},
];
const postAssetsResponse = {
saved_objects: [
{ id: 'created-asset1', type: 'dashboard', attributes: { title: 'Dashboard 1' } },
{ id: 'created-asset2', type: 'visualization', attributes: { title: 'Visualization 1' } },
],
};
const expectedInstance = {
name: 'instance-name',
templateName: 'integration-template',
dataSource: 'instance-datasource',
creationDate: expect.any(String),
assets: [
{
assetType: 'dashboard',
assetId: 'created-asset1',
status: 'available',
isDefaultAsset: true,
description: 'Dashboard 1',
},
{
assetType: 'visualization',
assetId: 'created-asset2',
status: 'available',
isDefaultAsset: false,
description: 'Visualization 1',
},
],
};

// Mock the implementation of the methods in the Integration class
sampleIntegration.deepCheck = jest.fn().mockResolvedValue(true);
sampleIntegration.getAssets = jest.fn().mockResolvedValue({ savedObjects: remappedAssets });
sampleIntegration.getConfig = jest.fn().mockResolvedValue({
name: 'integration-template',
type: 'integration-type',
});

// Mock builder sub-methods
const remapIDsSpy = jest.spyOn(builder, 'remapIDs');
const postAssetsSpy = jest.spyOn(builder, 'postAssets');

(mockSavedObjectsClient.bulkCreate as jest.Mock).mockResolvedValue(postAssetsResponse);

const instance = await builder.build(sampleIntegration, options);

expect(sampleIntegration.deepCheck).toHaveBeenCalled();
expect(sampleIntegration.getAssets).toHaveBeenCalled();
expect(remapIDsSpy).toHaveBeenCalledWith(remappedAssets);
expect(postAssetsSpy).toHaveBeenCalledWith(remappedAssets);
expect(instance).toEqual(expectedInstance);
});

it('should reject with an error if integration is not valid', async () => {
const options = {
dataSource: 'instance-datasource',
name: 'instance-name',
};
sampleIntegration.deepCheck = jest.fn().mockResolvedValue(false);

await expect(builder.build(sampleIntegration, options)).rejects.toThrowError(
'Integration is not valid'
);
});

it('should reject with an error if getAssets throws an error', async () => {
const options = {
dataSource: 'instance-datasource',
name: 'instance-name',
};

const errorMessage = 'Failed to get assets';
sampleIntegration.deepCheck = jest.fn().mockResolvedValue(true);
sampleIntegration.getAssets = jest.fn().mockRejectedValue(new Error(errorMessage));

await expect(builder.build(sampleIntegration, options)).rejects.toThrowError(errorMessage);
});

it('should reject with an error if postAssets throws an error', async () => {
const options = {
dataSource: 'instance-datasource',
name: 'instance-name',
};
const remappedAssets = [
{
id: 'remapped-asset1',
references: [{ id: 'remapped-ref1' }],
},
];
const errorMessage = 'Failed to post assets';
sampleIntegration.deepCheck = jest.fn().mockResolvedValue(true);
sampleIntegration.getAssets = jest.fn().mockResolvedValue({ savedObjects: remappedAssets });
builder.postAssets = jest.fn().mockRejectedValue(new Error(errorMessage));

await expect(builder.build(sampleIntegration, options)).rejects.toThrowError(errorMessage);
});

it('should reject with an error if getConfig returns null', async () => {
const options = {
dataSource: 'instance-datasource',
name: 'instance-name',
};
sampleIntegration.getConfig = jest.fn().mockResolvedValue(null);

await expect(builder.build(sampleIntegration, options)).rejects.toThrowError();
});
});

describe('remapIDs', () => {
it('should remap IDs and references in assets', () => {
const assets = [
{
id: 'asset1',
references: [{ id: 'ref1' }, { id: 'ref2' }],
},
{
id: 'asset2',
references: [{ id: 'ref1' }, { id: 'ref3' }],
},
];
const expectedRemappedAssets = [
{
id: expect.any(String),
references: [{ id: expect.any(String) }, { id: expect.any(String) }],
},
{
id: expect.any(String),
references: [{ id: expect.any(String) }, { id: expect.any(String) }],
},
];

const remappedAssets = builder.remapIDs(assets);

expect(remappedAssets).toEqual(expectedRemappedAssets);
});
});

describe('postAssets', () => {
it('should post assets and return asset references', async () => {
const assets = [
{
id: 'asset1',
type: 'dashboard',
attributes: { title: 'Dashboard 1' },
},
{
id: 'asset2',
type: 'visualization',
attributes: { title: 'Visualization 1' },
},
];
const expectedRefs = [
{
assetType: 'dashboard',
assetId: 'created-asset1',
status: 'available',
isDefaultAsset: true,
description: 'Dashboard 1',
},
{
assetType: 'visualization',
assetId: 'created-asset2',
status: 'available',
isDefaultAsset: false,
description: 'Visualization 1',
},
];
const bulkCreateResponse = {
saved_objects: [
{ id: 'created-asset1', type: 'dashboard', attributes: { title: 'Dashboard 1' } },
{ id: 'created-asset2', type: 'visualization', attributes: { title: 'Visualization 1' } },
],
};

(mockSavedObjectsClient.bulkCreate as jest.Mock).mockResolvedValue(bulkCreateResponse);

const refs = await builder.postAssets(assets);

expect(mockSavedObjectsClient.bulkCreate).toHaveBeenCalledWith(assets);
expect(refs).toEqual(expectedRefs);
});

it('should reject with an error if bulkCreate throws an error', async () => {
const assets = [
{
id: 'asset1',
type: 'dashboard',
attributes: { title: 'Dashboard 1' },
},
];
const errorMessage = 'Failed to create assets';
(mockSavedObjectsClient.bulkCreate as jest.Mock).mockRejectedValue(new Error(errorMessage));

await expect(builder.postAssets(assets)).rejects.toThrowError(errorMessage);
});
});

describe('buildInstance', () => {
it('should build an integration instance', async () => {
const integration = {
getConfig: jest.fn().mockResolvedValue({
name: 'integration-template',
type: 'integration-type',
}),
};
const refs = [
{
assetType: 'dashboard',
assetId: 'created-asset1',
status: 'available',
isDefaultAsset: true,
description: 'Dashboard 1',
},
];
const options = {
dataSource: 'instance-datasource',
name: 'instance-name',
};
const expectedInstance = {
name: 'instance-name',
templateName: 'integration-template',
dataSource: 'instance-datasource',
tags: undefined,
creationDate: expect.any(String),
assets: refs,
};

const instance = await builder.buildInstance(
(integration as unknown) as Integration,
refs,
options
);

expect(integration.getConfig).toHaveBeenCalled();
expect(instance).toEqual(expectedInstance);
});

it('should reject with an error if getConfig returns null', async () => {
const integration = {
getConfig: jest.fn().mockResolvedValue(null),
};
const refs = [
{
assetType: 'dashboard',
assetId: 'created-asset1',
status: 'available',
isDefaultAsset: true,
description: 'Dashboard 1',
},
];
const options = {
dataSource: 'instance-datasource',
name: 'instance-name',
};

await expect(
builder.buildInstance((integration as unknown) as Integration, refs, options)
).rejects.toThrowError();
});
});
});
Loading