Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merge in repository classes from osints/dev #552

Merged
merged 3 commits into from
Jun 22, 2023
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
246 changes: 246 additions & 0 deletions server/adaptors/integrations/repository/__test__/integration.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,246 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import * as fs from 'fs/promises';
import { Integration } from '../integration';
import { Dirent, Stats } from 'fs';
import * as path from 'path';

jest.mock('fs/promises');

describe('Integration', () => {
let integration: Integration;
const sampleIntegration: IntegrationTemplate = {
name: 'sample',
version: '2.0.0',
license: 'Apache-2.0',
type: '',
components: [],
assets: {
savedObjects: {
name: 'sample',
version: '1.0.1',
},
},
};

beforeEach(() => {
integration = new Integration('./sample');
});

describe('check', () => {
it('should return false if the directory does not exist', async () => {
const spy = jest.spyOn(fs, 'stat').mockResolvedValue({ isDirectory: () => false } as Stats);

const result = await integration.check();

expect(spy).toHaveBeenCalled();
expect(result).toBe(false);
});

it('should return true if the directory exists and getConfig returns a valid template', async () => {
jest.spyOn(fs, 'stat').mockResolvedValue({ isDirectory: () => true } as Stats);
integration.getConfig = jest.fn().mockResolvedValue(sampleIntegration);

const result = await integration.check();

expect(result).toBe(true);
});

it('should return false if the directory exists but getConfig returns null', async () => {
jest.spyOn(fs, 'stat').mockResolvedValue({ isDirectory: () => true } as Stats);
integration.getConfig = jest.fn().mockResolvedValue(null);

const result = await integration.check();

expect(result).toBe(false);
});
});

describe('getLatestVersion', () => {
it('should return the latest version if there are JSON files matching the integration name', async () => {
const files: unknown[] = ['sample-1.0.0.json', 'sample-2.0.0.json'];
jest.spyOn(fs, 'readdir').mockResolvedValue(files as Dirent[]);

const result = await integration.getLatestVersion();

expect(result).toBe('2.0.0');
});

it('should return null if there are no JSON files matching the integration name', async () => {
const files: unknown[] = ['other-1.0.0.json', 'other-2.0.0.json'];
jest.spyOn(fs, 'readdir').mockResolvedValue(files as Dirent[]);

const result = await integration.getLatestVersion();

expect(result).toBeNull();
});

it('should ignore files without a decimal version', async () => {
const files: unknown[] = ['sample-1.0.0.json', 'sample-2.0.two.json', 'sample-three.json'];
jest.spyOn(fs, 'readdir').mockResolvedValue(files as Dirent[]);

const result = await integration.getLatestVersion();

expect(result).toBe('1.0.0');
});
});

describe('getConfig', () => {
it('should return the parsed config template if it is valid', async () => {
jest.spyOn(fs, 'readFile').mockResolvedValue(JSON.stringify(sampleIntegration));

const result = await integration.getConfig(sampleIntegration.version);

expect(result).toEqual(sampleIntegration);
});

it('should return null and log validation errors if the config template is invalid', async () => {
const invalidTemplate = { ...sampleIntegration, version: 2 };
jest.spyOn(fs, 'readFile').mockResolvedValue(JSON.stringify(invalidTemplate));
const logValidationErrorsMock = jest.spyOn(console, 'error');

const result = await integration.getConfig(sampleIntegration.version);

expect(result).toBeNull();
expect(logValidationErrorsMock).toHaveBeenCalledWith(expect.any(String), expect.any(Array));
});

it('should return null and log syntax errors if the config file has syntax errors', async () => {
jest.spyOn(fs, 'readFile').mockResolvedValue('Invalid JSON');
const logSyntaxErrorsMock = jest.spyOn(console, 'error');

const result = await integration.getConfig(sampleIntegration.version);

expect(result).toBeNull();
expect(logSyntaxErrorsMock).toHaveBeenCalledWith(expect.any(String), expect.any(SyntaxError));
});

it('should return null and log errors if the integration config does not exist', async () => {
integration.directory = './non-existing-directory';
const logErrorsMock = jest.spyOn(console, 'error');
jest.spyOn(fs, 'readFile').mockImplementation((..._args) => {
// Can't find any information on how to mock an actual file not found error,
// But at least according to the current implementation this should be equivalent.
const error: any = new Error('ENOENT: File not found');
error.code = 'ENOENT';
return Promise.reject(error);
});

const result = await integration.getConfig(sampleIntegration.version);

expect(jest.spyOn(fs, 'readFile')).toHaveBeenCalled();
expect(logErrorsMock).toHaveBeenCalledWith(expect.any(String));
expect(result).toBeNull();
});
});

describe('getAssets', () => {
it('should return linked saved object assets when available', async () => {
integration.getConfig = jest.fn().mockResolvedValue(sampleIntegration);
jest.spyOn(fs, 'readFile').mockResolvedValue('{"name":"asset1"}\n{"name":"asset2"}');

const result = await integration.getAssets(sampleIntegration.version);

expect(result.savedObjects).toEqual([{ name: 'asset1' }, { name: 'asset2' }]);
});

it('should reject a return if the provided version has no config', async () => {
integration.getConfig = jest.fn().mockResolvedValue(null);

expect(integration.getAssets()).rejects.toThrowError();
});

it('should log an error if the saved object assets are invalid', async () => {
const logErrorsMock = jest.spyOn(console, 'error');
integration.getConfig = jest.fn().mockResolvedValue(sampleIntegration);
jest.spyOn(fs, 'readFile').mockResolvedValue('{"unclosed":');

const result = await integration.getAssets(sampleIntegration.version);

expect(logErrorsMock).toHaveBeenCalledWith(expect.any(String), expect.any(Error));
expect(result.savedObjects).toBeUndefined();
});
});

describe('getSchemas', () => {
it('should retrieve mappings and schemas for all components in the config', async () => {
const sampleConfig = {
components: [
{ name: 'component1', version: '1.0.0' },
{ name: 'component2', version: '2.0.0' },
],
};
integration.getConfig = jest.fn().mockResolvedValue(sampleConfig);

const mappingFile1 = 'component1-1.0.0.mapping.json';
const mappingFile2 = 'component2-2.0.0.mapping.json';

jest
.spyOn(fs, 'readFile')
.mockResolvedValueOnce(JSON.stringify({ mapping: 'mapping1' }))
.mockResolvedValueOnce(JSON.stringify({ mapping: 'mapping2' }));

const result = await integration.getSchemas();

expect(result).toEqual({
mappings: {
component1: { mapping: 'mapping1' },
component2: { mapping: 'mapping2' },
},
});

expect(fs.readFile).toHaveBeenCalledWith(
path.join(integration.directory, 'schemas', mappingFile1),
{ encoding: 'utf-8' }
);
expect(fs.readFile).toHaveBeenCalledWith(
path.join(integration.directory, 'schemas', mappingFile2),
{ encoding: 'utf-8' }
);
});

it('should reject with an error if the config is null', async () => {
integration.getConfig = jest.fn().mockResolvedValue(null);

await expect(integration.getSchemas()).rejects.toThrowError(
'Attempted to get assets of invalid config'
);
});

it('should reject with an error if a mapping file is invalid', async () => {
const sampleConfig = {
components: [{ name: 'component1', version: '1.0.0' }],
};
integration.getConfig = jest.fn().mockResolvedValue(sampleConfig);
jest.spyOn(fs, 'readFile').mockRejectedValueOnce(new Error('Could not load schema'));

await expect(integration.getSchemas()).rejects.toThrowError('Could not load schema');
});
});

describe('getStatic', () => {
it('should return data as a buffer if the static is present', async () => {
const readFileMock = jest
.spyOn(fs, 'readFile')
.mockResolvedValue(Buffer.from('logo data', 'ascii'));
expect(await integration.getStatic('/logo.png')).toStrictEqual(
Buffer.from('logo data', 'ascii')
);
expect(readFileMock).toBeCalledWith(path.join('sample', 'static', 'logo.png'));
});

it('should return null and log an error if the static file is not found', async () => {
const logErrorsMock = jest.spyOn(console, 'error');
jest.spyOn(fs, 'readFile').mockImplementation((..._args) => {
const error: any = new Error('ENOENT: File not found');
error.code = 'ENOENT';
return Promise.reject(error);
});
expect(await integration.getStatic('/logo.png')).toBeNull();
expect(logErrorsMock).toBeCalledWith(expect.any(String));
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import * as fs from 'fs/promises';
import { Repository } from '../repository';
import { Integration } from '../integration';
import { Dirent, Stats } from 'fs';
import path from 'path';

jest.mock('fs/promises');

describe('Repository', () => {
let repository: Repository;

beforeEach(() => {
repository = new Repository('path/to/directory');
});

describe('getIntegrationList', () => {
it('should return an array of Integration instances', async () => {
// Mock fs.readdir to return a list of folders
jest.spyOn(fs, 'readdir').mockResolvedValue((['folder1', 'folder2'] as unknown) as Dirent[]);

// Mock fs.lstat to return a directory status
jest.spyOn(fs, 'lstat').mockResolvedValue({ isDirectory: () => true } as Stats);

// Mock Integration check method to always return true
jest.spyOn(Integration.prototype, 'check').mockResolvedValue(true);

const integrations = await repository.getIntegrationList();

expect(integrations).toHaveLength(2);
expect(integrations[0]).toBeInstanceOf(Integration);
expect(integrations[1]).toBeInstanceOf(Integration);
});

it('should filter out null values from the integration list', async () => {
jest.spyOn(fs, 'readdir').mockResolvedValue((['folder1', 'folder2'] as unknown) as Dirent[]);

// Mock fs.lstat to return a mix of directories and files
jest.spyOn(fs, 'lstat').mockImplementation(async (toLstat) => {
if (toLstat === path.join('path', 'to', 'directory', 'folder1')) {
return { isDirectory: () => true } as Stats;
} else {
return { isDirectory: () => false } as Stats;
}
});

jest.spyOn(Integration.prototype, 'check').mockResolvedValue(true);

const integrations = await repository.getIntegrationList();

expect(integrations).toHaveLength(1);
expect(integrations[0]).toBeInstanceOf(Integration);
});

it('should handle errors and return an empty array', async () => {
jest.spyOn(fs, 'readdir').mockRejectedValue(new Error('Mocked error'));

const integrations = await repository.getIntegrationList();

expect(integrations).toEqual([]);
});
});

describe('getIntegration', () => {
it('should return an Integration instance if it exists and passes the check', async () => {
jest.spyOn(Integration.prototype, 'check').mockResolvedValue(true);

const integration = await repository.getIntegration('integrationName');

expect(integration).toBeInstanceOf(Integration);
});

it('should return null if the integration does not exist or fails the check', async () => {
jest.spyOn(Integration.prototype, 'check').mockResolvedValue(false);

const integration = await repository.getIntegration('invalidIntegration');

expect(integration).toBeNull();
});
});
});
Loading