Skip to content

Commit

Permalink
test: unit test for dependency management utils
Browse files Browse the repository at this point in the history
  • Loading branch information
Ghosh committed Oct 31, 2021
1 parent 9148f49 commit 6bbb2db
Show file tree
Hide file tree
Showing 3 changed files with 293 additions and 6 deletions.
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
import { $TSContext, JSONUtilities, pathManager, getPackageManager } from 'amplify-cli-core';
import * as fs from 'fs-extra';
import { $TSContext } from 'amplify-cli-core';
import execa from 'execa';
import ora from 'ora';
import { getAllResources } from '../../utils/dependency-management-utils';
import { generateCloudFormationFromCDK } from '../../utils/generate-cfn-from-cdk';
import { buildCustomResources } from '../../utils/build-custom-resources';

jest.mock('amplify-cli-core');
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,287 @@
import { $TSContext, CFNTemplateFormat, readCFNTemplate, pathManager, stateManager } from 'amplify-cli-core';
import { glob } from 'glob';
import * as fs from 'fs-extra';
import { getResourceCfnOutputAttributes, getAllResources, addCDKResourceDependency } from '../../utils/dependency-management-utils';
import * as cdk from '@aws-cdk/core';

jest.mock('amplify-cli-core');
jest.mock('glob');
jest.mock('fs-extra');

const readCFNTemplate_mock = readCFNTemplate as jest.MockedFunction<typeof readCFNTemplate>;
const glob_mock = glob as jest.Mocked<typeof glob>;
const fs_mock = fs as jest.Mocked<typeof fs>;

pathManager.getBackendDirPath = jest.fn().mockReturnValue('mockTargetDir');
pathManager.getResourceDirectoryPath = jest.fn().mockReturnValue('mockResourceDir');

describe('getResourceCfnOutputAttributes() scenarios', () => {
let mockContext: $TSContext;

beforeEach(() => {
jest.clearAllMocks();
mockContext = {
amplify: {
openEditor: jest.fn(),
updateamplifyMetaAfterResourceAdd: jest.fn(),
copyBatch: jest.fn(),
getResourceStatus: jest.fn().mockResolvedValue({
allResources: [
{
resourceName: 'mockresource1',
service: 'customCDK',
},
{
resourceName: 'mockresource2',
service: 'customCDK',
},
],
}),
},
} as unknown as $TSContext;
});

it('get resource attr for resources with build folder with one cfn file', async () => {
fs_mock.existsSync.mockReturnValue(true); // if build dir exists

readCFNTemplate_mock.mockReturnValueOnce({
templateFormat: CFNTemplateFormat.JSON,
cfnTemplate: { Outputs: { mockKey: { Value: 'mockValue' } } },
});
glob_mock.sync.mockReturnValueOnce(['mockFileName']);

expect(getResourceCfnOutputAttributes('mockCategory', 'mockResourceName')).toEqual(['mockKey']);
});

it('get resource attr for resources with build folder with multiple cfn files', async () => {
fs_mock.existsSync.mockReturnValue(true); // if build dir exists

readCFNTemplate_mock.mockReturnValueOnce({
templateFormat: CFNTemplateFormat.JSON,
cfnTemplate: { Outputs: { mockKey: { Value: 'mockValue' } } },
});

glob_mock.sync.mockReturnValueOnce(['mockFileName1', 'mockFileName2']);

expect(getResourceCfnOutputAttributes('mockCategory', 'mockResourceName')).toEqual([]);
});

it('get resource attr for resources without build folder', async () => {
fs_mock.existsSync.mockReturnValue(false); // if build dir exists

readCFNTemplate_mock.mockReturnValueOnce({
templateFormat: CFNTemplateFormat.JSON,
cfnTemplate: { Outputs: { mockKey: { Value: 'mockValue' } } },
});
glob_mock.sync.mockReturnValueOnce(['mockFileName']);

expect(getResourceCfnOutputAttributes('mockCategory', 'mockResourceName')).toEqual(['mockKey']);
});

it('get resource attr for resources without build folder with multiple cfn files', async () => {
fs_mock.existsSync.mockReturnValue(false); // if build dir exists

readCFNTemplate_mock.mockReturnValueOnce({
templateFormat: CFNTemplateFormat.JSON,
cfnTemplate: { Outputs: { mockKey: { Value: 'mockValue' } } },
});
glob_mock.sync.mockReturnValueOnce(['mockFileName1', 'mockFileName2']);

expect(getResourceCfnOutputAttributes('mockCategory', 'mockResourceName')).toEqual([]);
});

it('get resource attr for resources without any cfn files', async () => {
fs_mock.existsSync.mockReturnValue(false); // if build dir exists
glob_mock.sync.mockReturnValueOnce([]);

expect(getResourceCfnOutputAttributes('mockCategory', 'mockResourceName')).toEqual([]);
});
});

describe('getAllResources() scenarios', () => {
let mockContext: $TSContext;

beforeEach(() => {
jest.clearAllMocks();
mockContext = {
amplify: {
openEditor: jest.fn(),
updateamplifyMetaAfterResourceAdd: jest.fn(),
copyBatch: jest.fn(),
getResourceStatus: jest.fn().mockResolvedValue({
allResources: [
{
resourceName: 'mockresource1',
service: 'customCDK',
},
{
resourceName: 'mockresource2',
service: 'customCDK',
},
],
}),
},
} as unknown as $TSContext;
});

it('get all resource types', async () => {
fs_mock.existsSync.mockReturnValue(false); // if build dir exists

readCFNTemplate_mock.mockReturnValue({
templateFormat: CFNTemplateFormat.JSON,
cfnTemplate: { Outputs: { mockKey: { Value: 'mockValue' } } },
});

glob_mock.sync.mockReturnValue(['mockFileName']);

stateManager.getMeta = jest.fn().mockReturnValue({
mockCategory1: {
mockResourceName1: {},
},
mockCategory2: {
mockResourceName2: {},
},
});

expect(getAllResources()).toEqual({
mockCategory1: { mockResourceName1: { mockKey: 'string' } },
mockCategory2: { mockResourceName2: { mockKey: 'string' } },
});
});
});

describe('addCDKResourceDependency() scenarios', () => {
let mockContext: $TSContext;

beforeEach(() => {
jest.clearAllMocks();
mockContext = {
amplify: {
openEditor: jest.fn(),
updateamplifyMetaAfterResourceAdd: jest.fn(),
copyBatch: jest.fn(),
getResourceStatus: jest.fn().mockResolvedValue({
allResources: [
{
resourceName: 'mockresource1',
service: 'customCDK',
},
{
resourceName: 'mockresource2',
service: 'customCDK',
},
],
}),
},
} as unknown as $TSContext;
});

it('get depenencies for a custom CDK stack', async () => {
const getResourceCfnOutputAttributes_mock = getResourceCfnOutputAttributes as jest.MockedFunction<
typeof getResourceCfnOutputAttributes
>;

getResourceCfnOutputAttributes_mock.mockImplementation = jest.fn().mockReturnValueOnce(['outputkey']).mockReturnValueOnce([]);

fs_mock.existsSync.mockReturnValue(false); // if build dir exists

readCFNTemplate_mock.mockReturnValue({
templateFormat: CFNTemplateFormat.JSON,
cfnTemplate: { Outputs: { mockKey: { Value: 'mockValue' } } },
});

glob_mock.sync.mockReturnValue(['mockFileName']);

const mockBackendConfig = {
mockCategory1: {
mockResourceName1: {},
},
mockCategory2: {
mockResourceName2: {},
},
mockCategory3: {
mockResourceName3: {},
},
mockCategory4: {
mockResourceName4: {},
},
};

stateManager.getBackendConfig = jest.fn().mockReturnValue(mockBackendConfig);

stateManager.setBackendConfig = jest.fn();

stateManager.getMeta = jest.fn().mockReturnValue(mockBackendConfig);

stateManager.setMeta = jest.fn();
const mockStack = new cdk.Stack();

// test with adding one dependency at once
let retVal = addCDKResourceDependency(mockStack, 'mockCategory1', 'mockResourceName1', [
{ category: 'mockCategory2', resourceName: 'mockResourceName2' },
]);

expect(retVal).toEqual({
mockCategory2: {
mockResourceName2: { mockKey: 'mockCategory2mockResourceName2mockKey' },
},
});

const postUpdateBackendConfig: any = mockBackendConfig;
postUpdateBackendConfig.mockCategory1.mockResourceName1.dependsOn = [
{
attributes: ['mockKey'],
category: 'mockCategory2',
resourceName: 'mockResourceName2',
},
];

expect(stateManager.setMeta).toBeCalledWith(undefined, postUpdateBackendConfig);
expect(stateManager.setBackendConfig).toBeCalledWith(undefined, postUpdateBackendConfig);

// test with adding multiple dependencies at once

retVal = addCDKResourceDependency(mockStack, 'mockCategory1', 'mockResourceName1', [
{ category: 'mockCategory4', resourceName: 'mockResourceName4' },
{ category: 'mockCategory3', resourceName: 'mockResourceName3' },
]);

expect(retVal).toEqual({
mockCategory4: {
mockResourceName4: { mockKey: 'mockCategory4mockResourceName4mockKey' },
},
mockCategory3: {
mockResourceName3: { mockKey: 'mockCategory3mockResourceName3mockKey' },
},
});

postUpdateBackendConfig.mockCategory1.mockResourceName1.dependsOn = [
{
attributes: ['mockKey'],
category: 'mockCategory3',
resourceName: 'mockResourceName3',
},
{
attributes: ['mockKey'],
category: 'mockCategory4',
resourceName: 'mockResourceName4',
},
];

expect(stateManager.setMeta).toBeCalledWith(undefined, postUpdateBackendConfig);
expect(stateManager.setBackendConfig).toBeCalledWith(undefined, postUpdateBackendConfig);

// test when adding multiple dependencies but none of the dependencies have outputs exported

readCFNTemplate_mock.mockReturnValue({ templateFormat: CFNTemplateFormat.JSON, cfnTemplate: {} });

retVal = addCDKResourceDependency(mockStack, 'mockCategory1', 'mockResourceName1', [
{ category: 'mockCategory4', resourceName: 'mockResourceName4' },
{ category: 'mockCategory3', resourceName: 'mockResourceName3' },
]);

expect(retVal).toEqual({});
expect(stateManager.setMeta).toBeCalledTimes(2); // from the previous two successful calls - and skip the last call
expect(stateManager.setBackendConfig).toBeCalledTimes(2); // from the previous two successful calls - and skip the last call
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export function getResourceCfnOutputAttributes(category: string, resourceName: s
* This looks for a build directory and uses it if one exists.
* Otherwise falls back to the default behavior.
*/
if (fs.existsSync(resourceBuildDir) && fs.lstatSync(resourceBuildDir).isDirectory()) {
if (fs.existsSync(resourceBuildDir)) {
const cfnFiles = glob.sync(cfnTemplateGlobPattern, {
cwd: resourceBuildDir,
});
Expand All @@ -47,6 +47,10 @@ export function getResourceCfnOutputAttributes(category: string, resourceName: s
const cfnFiles = glob.sync(cfnTemplateGlobPattern, {
cwd: resourceDir,
});
if (cfnFiles.length > 1) {
printer.warn(`${resourceName} has more than one CloudFormation definitions in the resource folder which isn't permitted.`);
return [];
}
if (resourceDir && cfnFiles[0]) {
cfnFilePath = path.join(resourceDir, cfnFiles[0]);
}
Expand Down

0 comments on commit 6bbb2db

Please sign in to comment.