Skip to content
This repository was archived by the owner on Dec 9, 2024. It is now read-only.
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
25 changes: 25 additions & 0 deletions src/plugins/apim/apimFunctionPlugin.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { MockFactory } from "../../test/mockFactory";
import { invokeHook } from "../../test/utils";
import { AzureApimFunctionPlugin } from './apimFunctionPlugin';

jest.mock('../../services/apimService');
import { ApimService } from '../../services/apimService';

describe('APIM Function Plugin', () => {
it('calls deploy function', async () => {
const deployFunction = jest.fn();

ApimService.prototype.deployFunction = deployFunction;

const sls = MockFactory.createTestServerless();
sls.service.provider['apim'] = 'apim config'
const options = MockFactory.createTestServerlessOptions();
const plugin = new AzureApimFunctionPlugin(sls, options);

await invokeHook(plugin, 'after:deploy:function:deploy');

expect(sls.cli.log).toBeCalledWith('Starting APIM function deployment')
expect(deployFunction).toBeCalled();
expect(sls.cli.log).lastCalledWith('Finished APIM function deployment')
});
});
43 changes: 43 additions & 0 deletions src/plugins/apim/apimServicePlugin.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import Serverless from 'serverless';
import { MockFactory } from "../../test/mockFactory";
import { invokeHook } from "../../test/utils";
import { AzureApimServicePlugin } from './apimServicePlugin';

jest.mock('../../services/apimService');
import { ApimService } from '../../services/apimService';

describe('APIM Service Plugin', () => {
it('is defined', () => {
expect(AzureApimServicePlugin).toBeDefined();
Expand All @@ -16,4 +21,42 @@ describe('APIM Service Plugin', () => {

expect(plugin).not.toBeNull();
});

it('calls deploy API and deploy functions', async () => {
const deployApi = jest.fn();
const deployFunctions = jest.fn();

ApimService.prototype.deployApi = deployApi;
ApimService.prototype.deployFunctions = deployFunctions;

const sls = MockFactory.createTestServerless();
sls.service.provider['apim'] = 'apim config'
const options = MockFactory.createTestServerlessOptions();
const plugin = new AzureApimServicePlugin(sls, options);

await invokeHook(plugin, 'after:deploy:deploy');

expect(sls.cli.log).toBeCalledWith('Starting APIM service deployment')
expect(deployApi).toBeCalled();
expect(deployFunctions).toBeCalled();
expect(sls.cli.log).lastCalledWith('Finished APIM service deployment')
});

it('does not call deploy API or deploy functions when "apim" not included in config', async () => {
const deployApi = jest.fn();
const deployFunctions = jest.fn();

ApimService.prototype.deployApi = deployApi;
ApimService.prototype.deployFunctions = deployFunctions;

const sls = MockFactory.createTestServerless();
const options = MockFactory.createTestServerlessOptions();
const plugin = new AzureApimServicePlugin(sls, options);

await invokeHook(plugin, 'after:deploy:deploy');

expect(sls.cli.log).not.toBeCalled()
expect(deployApi).not.toBeCalled();
expect(deployFunctions).not.toBeCalled();
});
});
3 changes: 2 additions & 1 deletion src/plugins/deploy/azureDeployPlugin.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ import { MockFactory } from "../../test/mockFactory";
import { invokeHook } from "../../test/utils";
import { AzureDeployPlugin } from "./azureDeployPlugin";

jest.mock("../../services/resourceService");
jest.mock("../../services/functionAppService");
import { FunctionAppService } from "../../services/functionAppService";

jest.mock("../../services/resourceService");
import { ResourceService } from "../../services/resourceService";

describe('Deploy plugin', () => {
Expand Down
31 changes: 31 additions & 0 deletions src/plugins/package/azurePackage.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { MockFactory } from "../../test/mockFactory";
import { invokeHook } from "../../test/utils";
import { AzurePackage } from "./azurePackage"

jest.mock('../../shared/bindings');
import { BindingUtils } from '../../shared/bindings';
jest.mock('../../shared/utils');
import { Utils, FunctionMetadata } from '../../shared/utils';

describe('Azure Package Plugin', () => {
it('sets up provider configuration', async () => {
const metadata = 'metadata';
const functionName = 'function1';

const getFunctionMetaDataFn = jest.fn(() => metadata as any as FunctionMetadata);
const createEventsBindingsFn = jest.fn();

Utils.getFunctionMetaData = getFunctionMetaDataFn
BindingUtils.createEventsBindings = createEventsBindingsFn

const sls = MockFactory.createTestServerless();
const options = MockFactory.createTestServerlessOptions();
const plugin = new AzurePackage(sls, options);

await invokeHook(plugin, 'package:setupProviderConfiguration');

expect(sls.cli.log).toBeCalledWith('Building Azure Events Hooks');
expect(getFunctionMetaDataFn).toBeCalledWith(functionName, sls);
expect(createEventsBindingsFn).toBeCalledWith(sls.config.servicePath, functionName, metadata);
});
});
8 changes: 4 additions & 4 deletions src/plugins/package/azurePackage.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@

import Serverless from 'serverless';
import AzureProvider from '../../provider/azureProvider';
import { createEventsBindings } from '../../shared/bindings';
import { getFunctionMetaData } from '../../shared/utils';
import { BindingUtils } from '../../shared/bindings';
import { Utils } from '../../shared/utils';

export class AzurePackage {
provider: AzureProvider
Expand All @@ -19,9 +19,9 @@ export class AzurePackage {

const createEventsPromises = this.serverless.service.getAllFunctions()
.map((functionName) => {
const metaData = getFunctionMetaData(functionName, this.serverless);
const metaData = Utils.getFunctionMetaData(functionName, this.serverless);

return createEventsBindings(this.serverless.config.servicePath, functionName, metaData);
return BindingUtils.createEventsBindings(this.serverless.config.servicePath, functionName, metaData);
});

return Promise.all(createEventsPromises);
Expand Down
26 changes: 26 additions & 0 deletions src/plugins/remove/azureRemove.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { MockFactory } from "../../test/mockFactory";
import { invokeHook } from "../../test/utils";
import { AzureRemove } from "./azureRemove";

jest.mock("../../services/resourceService");
import { ResourceService } from "../../services/resourceService";

describe('Remove Plugin', () => {
it('calls remove hook', async () => {
const deleteDeployment = jest.fn();
const deleteResourceGroup = jest.fn();

ResourceService.prototype.deleteDeployment = deleteDeployment;
ResourceService.prototype.deleteResourceGroup = deleteResourceGroup;

const sls = MockFactory.createTestServerless();
const options = MockFactory.createTestServerlessOptions();
const plugin = new AzureRemove(sls, options);

await invokeHook(plugin, 'remove:remove');

expect(deleteDeployment).toBeCalled();
expect(deleteResourceGroup).toBeCalled();
expect(sls.cli.log).toBeCalledWith('Service successfully removed');
});
});
4 changes: 2 additions & 2 deletions src/shared/binding.test.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { MockFactory } from '../test/mockFactory';
import { getBindingsMetaData } from './bindings';
import { BindingUtils } from './bindings';

describe('Bindings', () => {
it('should get bindings metadata from serverless', () => {
const sls = MockFactory.createTestServerless();
expect(sls).not.toBeNull();
getBindingsMetaData(sls);
BindingUtils.getBindingsMetaData(sls);
expect(sls.cli.log).toBeCalledWith('Parsing Azure Functions Bindings.json...');
});
});
154 changes: 116 additions & 38 deletions src/shared/bindings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,50 +2,128 @@ import { writeFileSync } from 'fs';
import { join } from 'path';
import Serverless from 'serverless';
import { FunctionMetadata } from './utils';
const bindingsJson = require('./bindings.json');

const constants = {
bindings: 'bindings',
settings: 'settings',
name: 'name',
displayName: 'displayName',
type: 'type'
};
import { constants } from './constants';

export function getBindingsMetaData(serverless: Serverless) {
const bindingDisplayNames = [];
const bindingTypes = [];
const bindingSettings = [];
const bindingSettingsNames = [];

serverless.cli.log('Parsing Azure Functions Bindings.json...');
const bindingsJson = require('./bindings.json');

for (let bindingsIndex = 0; bindingsIndex < bindingsJson[constants.bindings].length; bindingsIndex++) {
const settingsNames = [];
export class BindingUtils {
public static getBindingsMetaData(serverless: Serverless) {
const bindingDisplayNames = [];
const bindingTypes = [];
const bindingSettings = [];
const bindingSettingsNames = [];

serverless.cli.log('Parsing Azure Functions Bindings.json...');

for (let bindingsIndex = 0; bindingsIndex < bindingsJson[constants.bindings].length; bindingsIndex++) {
const settingsNames = [];

bindingTypes.push(bindingsJson[constants.bindings][bindingsIndex][constants.type]);
bindingDisplayNames.push(bindingsJson[constants.bindings][bindingsIndex][constants.displayName].toLowerCase());
bindingSettings[bindingsIndex] = bindingsJson[constants.bindings][bindingsIndex][constants.settings];

for (let bindingSettingsIndex = 0; bindingSettingsIndex < bindingSettings[bindingsIndex].length; bindingSettingsIndex++) {
settingsNames.push(bindingSettings[bindingsIndex][bindingSettingsIndex][constants.name]);
}

bindingSettingsNames[bindingsIndex] = settingsNames;
}

return {
bindingDisplayNames: bindingDisplayNames,
bindingTypes: bindingTypes,
bindingSettings: bindingSettings,
bindingSettingsNames: bindingSettingsNames
};
}

bindingTypes.push(bindingsJson[constants.bindings][bindingsIndex][constants.type]);
bindingDisplayNames.push(bindingsJson[constants.bindings][bindingsIndex][constants.displayName].toLowerCase());
bindingSettings[bindingsIndex] = bindingsJson[constants.bindings][bindingsIndex][constants.settings];
public static createEventsBindings(servicePath: string, functionName: string, functionMetadata: FunctionMetadata): Promise<any> {
const functionJSON = functionMetadata.params.functionsJson;
functionJSON.entryPoint = functionMetadata.entryPoint;
functionJSON.scriptFile = functionMetadata.handlerPath;
writeFileSync(join(servicePath, functionName, 'function.json'), JSON.stringify(functionJSON, null, 4));
return Promise.resolve();
}

for (let bindingSettingsIndex = 0; bindingSettingsIndex < bindingSettings[bindingsIndex].length; bindingSettingsIndex++) {
settingsNames.push(bindingSettings[bindingsIndex][bindingSettingsIndex][constants.name]);
public static getBindingUserSettingsMetaData(azureSettings, bindingType, bindingTypeIndex, bindingDisplayNames) {
let bindingDisplayNamesIndex = bindingTypeIndex;
const bindingUserSettings = {};

if (azureSettings) {
const directionIndex = Object.keys(azureSettings).indexOf(constants.direction);

if (directionIndex >= 0) {
const key = Object.keys(azureSettings)[directionIndex];
const displayName = `$${bindingType}${azureSettings[key]}_displayName`;

bindingDisplayNamesIndex = bindingDisplayNames.indexOf(displayName.toLowerCase());
bindingUserSettings[constants.direction] = azureSettings[key];
}
}
const bindingUserSettingsMetaData = {
index: bindingDisplayNamesIndex,
userSettings: bindingUserSettings
};

return bindingUserSettingsMetaData;
}

bindingSettingsNames[bindingsIndex] = settingsNames;
public static getHttpOutBinding(bindingUserSettings) {
const binding = {};

binding[constants.type] = 'http';
binding[constants.direction] = constants.outDirection;
binding[constants.name] = '$return';
if (bindingUserSettings[constants.webHookType]) {
binding[constants.name] = 'res';
}

return binding;
}

return {
bindingDisplayNames: bindingDisplayNames,
bindingTypes: bindingTypes,
bindingSettings: bindingSettings,
bindingSettingsNames: bindingSettingsNames
};
public static getBinding(bindingType, bindingSettings, bindingUserSettings) {
const binding = {};

binding[constants.type] = bindingType;
if (bindingUserSettings && bindingUserSettings[constants.direction]) {
binding[constants.direction] = bindingUserSettings[constants.direction];
} else if (bindingType.includes(constants.trigger)) {
binding[constants.direction] = constants.inDirection;
} else {
binding[constants.direction] = constants.outDirection;
}

for (let bindingSettingsIndex = 0; bindingSettingsIndex < bindingSettings.length; bindingSettingsIndex++) {
const name = bindingSettings[bindingSettingsIndex][constants.name];

if (bindingUserSettings && bindingUserSettings[name] !== undefined && bindingUserSettings[name] !== null) {
binding[name] = bindingUserSettings[name];
continue;
}
const value = bindingSettings[bindingSettingsIndex][constants.value];
const required = bindingSettings[bindingSettingsIndex][constants.required];
const resource = bindingSettings[bindingSettingsIndex][constants.resource];

if (required) {
const defaultValue = bindingSettings[bindingSettingsIndex][constants.defaultValue];

if (defaultValue) {
binding[name] = defaultValue;
} else if (name === constants.connection && resource.toLowerCase() === constants.storage) {
binding[name] = 'AzureWebJobsStorage';
} else {
throw new Error(`Required property ${name} is missing for binding:${bindingType}`);
}
}

if (value === constants.enum && name !== constants.webHookType) {
const enumValues = bindingSettings[bindingSettingsIndex][constants.enum];

binding[name] = enumValues[0][constants.value];
}
}

return binding;
}

}

export async function createEventsBindings(servicePath: string, functionName: string, functionMetadata: FunctionMetadata): Promise<any> {
const functionJSON = functionMetadata.params.functionsJson;
functionJSON.entryPoint = functionMetadata.entryPoint;
functionJSON.scriptFile = functionMetadata.handlerPath;
writeFileSync(join(servicePath, functionName, 'function.json'), JSON.stringify(functionJSON, null, 4));
return Promise.resolve();
}
24 changes: 24 additions & 0 deletions src/shared/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
export const constants = {
bindings: 'bindings',
settings: 'settings',
name: 'name',
displayName: 'displayName',
type: 'type',
direction: 'direction',
trigger: 'Trigger',
inDirection: 'in',
outDirection: 'out',
value: 'value',
resource: 'resource',
required: 'required',
storage: 'storage',
connection: 'connection',
enum: 'enum',
defaultValue: 'defaultValue',
webHookType: 'webHookType',
httpTrigger: 'httpTrigger',
queue: 'queue',
queueName: 'queueName',
xAzureSettings: 'x-azure-settings',
entryPoint: 'entryPoint'
}
Loading