diff --git a/extension.bundle.ts b/extension.bundle.ts index 8b618c990..486ceaee6 100644 --- a/extension.bundle.ts +++ b/extension.bundle.ts @@ -23,7 +23,7 @@ export * from './src/commands/initProjectForVSCode/initProjectForVSCode'; export * from './src/commands/deploy/verifyAppSettings'; export * from './src/constants'; export * from './src/extensionVariables'; -export * from './src/FunctionConfig'; +export * from './src/funcConfig/function'; export * from './src/vsCodeConfig/settings'; export * from './src/templates/IFunctionTemplate'; export * from './src/templates/ScriptTemplateRetriever'; diff --git a/src/commands/addBinding/BindingCreateStep.ts b/src/commands/addBinding/BindingCreateStep.ts index 48dd046dd..a25cfea40 100644 --- a/src/commands/addBinding/BindingCreateStep.ts +++ b/src/commands/addBinding/BindingCreateStep.ts @@ -5,7 +5,7 @@ import { Progress, Uri, window, workspace } from "vscode"; import { AzureWizardExecuteStep } from "vscode-azureextensionui"; -import { IFunctionBinding, IFunctionJson } from "../../FunctionConfig"; +import { IFunctionBinding, IFunctionJson } from "../../funcConfig/function"; import { IBindingTemplate } from "../../templates/IBindingTemplate"; import { confirmEditJsonFile } from '../../utils/fs'; import { nonNullProp } from "../../utils/nonNull"; diff --git a/src/commands/addBinding/IBindingWizardContext.ts b/src/commands/addBinding/IBindingWizardContext.ts index f642c6f0d..ecbdd782a 100644 --- a/src/commands/addBinding/IBindingWizardContext.ts +++ b/src/commands/addBinding/IBindingWizardContext.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IFunctionBinding } from "../../FunctionConfig"; +import { IFunctionBinding } from "../../funcConfig/function"; import { IBindingTemplate } from "../../templates/IBindingTemplate"; import { IFunctionWizardContext } from "../createFunction/IFunctionWizardContext"; diff --git a/src/commands/addBinding/settingSteps/AzureConnectionCreateStepBase.ts b/src/commands/addBinding/settingSteps/AzureConnectionCreateStepBase.ts index 58644cbcb..05994ea4a 100644 --- a/src/commands/addBinding/settingSteps/AzureConnectionCreateStepBase.ts +++ b/src/commands/addBinding/settingSteps/AzureConnectionCreateStepBase.ts @@ -5,7 +5,7 @@ import { Progress } from 'vscode'; import { AzureWizardExecuteStep } from 'vscode-azureextensionui'; -import { setLocalAppSetting } from '../../../LocalAppSettings'; +import { setLocalAppSetting } from '../../../funcConfig/local.settings'; import { localize } from '../../../localize'; import { IFunctionSetting } from '../../../templates/IFunctionSetting'; import { nonNullProp } from '../../../utils/nonNull'; diff --git a/src/commands/addBinding/settingSteps/BindingNameStep.ts b/src/commands/addBinding/settingSteps/BindingNameStep.ts index 9e8a532da..d6d58174d 100644 --- a/src/commands/addBinding/settingSteps/BindingNameStep.ts +++ b/src/commands/addBinding/settingSteps/BindingNameStep.ts @@ -4,13 +4,13 @@ *--------------------------------------------------------------------------------------------*/ import * as fse from 'fs-extra'; -import { FunctionConfig } from '../../../FunctionConfig'; +import { ParsedFunctionJson } from '../../../funcConfig/function'; import { localize } from '../../../localize'; import { IBindingWizardContext } from '../IBindingWizardContext'; import { StringPromptStep } from './StringPromptStep'; export class BindingNameStep extends StringPromptStep { - private _functionConfig: FunctionConfig | undefined; + private _functionJson: ParsedFunctionJson | undefined; public async getDefaultValue(wizardContext: IBindingWizardContext): Promise { const defaultValue: string | undefined = await super.getDefaultValue(wizardContext); @@ -44,11 +44,11 @@ export class BindingNameStep extends StringPromptStep { private async bindingExists(wizardContext: IBindingWizardContext, val: string): Promise { try { - if (!this._functionConfig) { - this._functionConfig = new FunctionConfig(await fse.readJSON(wizardContext.functionJsonPath)); + if (!this._functionJson) { + this._functionJson = new ParsedFunctionJson(await fse.readJSON(wizardContext.functionJsonPath)); } - return !!this._functionConfig.bindings.find(b => b.name === val); + return !!this._functionJson.bindings.find(b => b.name === val); } catch { // If we can't parse the function.json file, we will prompt to overwrite the file later and can assume the binding doesn't exist return false; diff --git a/src/commands/addBinding/settingSteps/LocalAppSettingCreateStep.ts b/src/commands/addBinding/settingSteps/LocalAppSettingCreateStep.ts index 11d6511b9..723b65442 100644 --- a/src/commands/addBinding/settingSteps/LocalAppSettingCreateStep.ts +++ b/src/commands/addBinding/settingSteps/LocalAppSettingCreateStep.ts @@ -6,7 +6,7 @@ import { Progress } from 'vscode'; import { AzureWizardExecuteStep } from 'vscode-azureextensionui'; import { localSettingsFileName } from '../../../constants'; -import { setLocalAppSetting } from '../../../LocalAppSettings'; +import { setLocalAppSetting } from '../../../funcConfig/local.settings'; import { localize } from '../../../localize'; import { nonNullProp } from '../../../utils/nonNull'; import { IBindingWizardContext } from '../IBindingWizardContext'; diff --git a/src/commands/addBinding/settingSteps/LocalAppSettingListStep.ts b/src/commands/addBinding/settingSteps/LocalAppSettingListStep.ts index 81406b7e8..31de2af29 100644 --- a/src/commands/addBinding/settingSteps/LocalAppSettingListStep.ts +++ b/src/commands/addBinding/settingSteps/LocalAppSettingListStep.ts @@ -7,7 +7,7 @@ import * as path from 'path'; import { AzureWizardExecuteStep, AzureWizardPromptStep, IAzureQuickPickItem, ISubscriptionWizardContext, IWizardOptions, StorageAccountKind, StorageAccountListStep, StorageAccountPerformance, StorageAccountReplication } from 'vscode-azureextensionui'; import { localSettingsFileName } from '../../../constants'; import { ext } from '../../../extensionVariables'; -import { getLocalAppSettings, ILocalAppSettings } from '../../../LocalAppSettings'; +import { getLocalSettingsJson, ILocalSettingsJson } from '../../../funcConfig/local.settings'; import { localize } from '../../../localize'; import { IFunctionSetting, ResourceType } from '../../../templates/IFunctionSetting'; import { IBindingWizardContext } from '../IBindingWizardContext'; @@ -30,7 +30,7 @@ export class LocalAppSettingListStep extends AzureWizardPromptStep { const localSettingsPath: string = path.join(wizardContext.projectPath, localSettingsFileName); - const settings: ILocalAppSettings = await getLocalAppSettings(localSettingsPath); + const settings: ILocalSettingsJson = await getLocalSettingsJson(localSettingsPath); const existingSettings: string[] = settings.Values ? Object.keys(settings.Values) : []; let picks: IAzureQuickPickItem[] = [{ label: localize('newAppSetting', '$(plus) Create new local app setting'), data: undefined }]; picks = picks.concat(existingSettings.map((s: string) => { return { data: s, label: s }; })); diff --git a/src/commands/appSettings/downloadAppSettings.ts b/src/commands/appSettings/downloadAppSettings.ts index b42b6d65e..d9b06f252 100644 --- a/src/commands/appSettings/downloadAppSettings.ts +++ b/src/commands/appSettings/downloadAppSettings.ts @@ -9,7 +9,7 @@ import * as vscode from 'vscode'; import { AppSettingsTreeItem, SiteClient } from "vscode-azureappservice"; import { localSettingsFileName } from "../../constants"; import { ext } from "../../extensionVariables"; -import { getLocalAppSettings, ILocalAppSettings } from "../../LocalAppSettings"; +import { getLocalSettingsJson, ILocalSettingsJson } from "../../funcConfig/local.settings"; import { localize } from "../../localize"; import { confirmOverwriteSettings } from "./confirmOverwriteSettings"; import { decryptLocalSettings } from "./decryptLocalSettings"; @@ -30,12 +30,12 @@ export async function downloadAppSettings(node?: AppSettingsTreeItem): Promise { ext.outputChannel.show(true); ext.outputChannel.appendLine(localize('downloadStart', 'Downloading settings from "{0}"...', client.fullName)); - let localSettings: ILocalAppSettings = await getLocalAppSettings(localSettingsPath, true /* allowOverwrite */); + let localSettings: ILocalSettingsJson = await getLocalSettingsJson(localSettingsPath, true /* allowOverwrite */); const isEncrypted: boolean | undefined = localSettings.IsEncrypted; if (localSettings.IsEncrypted) { await decryptLocalSettings(localSettingsUri); - localSettings = await fse.readJson(localSettingsPath); + localSettings = await fse.readJson(localSettingsPath); } try { diff --git a/src/commands/appSettings/uploadAppSettings.ts b/src/commands/appSettings/uploadAppSettings.ts index 74c7a8d12..3cf987489 100644 --- a/src/commands/appSettings/uploadAppSettings.ts +++ b/src/commands/appSettings/uploadAppSettings.ts @@ -9,7 +9,7 @@ import * as vscode from 'vscode'; import { AppSettingsTreeItem, SiteClient } from "vscode-azureappservice"; import { localSettingsFileName } from "../../constants"; import { ext } from "../../extensionVariables"; -import { ILocalAppSettings } from "../../LocalAppSettings"; +import { ILocalSettingsJson } from "../../funcConfig/local.settings"; import { localize } from "../../localize"; import { confirmOverwriteSettings } from "./confirmOverwriteSettings"; import { decryptLocalSettings } from "./decryptLocalSettings"; @@ -30,11 +30,11 @@ export async function uploadAppSettings(node?: AppSettingsTreeItem, workspacePat await node.runWithTemporaryDescription(localize('uploading', 'Uploading...'), async () => { ext.outputChannel.show(true); ext.outputChannel.appendLine(localize('uploadStart', 'Uploading settings to "{0}"...', client.fullName)); - let localSettings: ILocalAppSettings = await fse.readJson(localSettingsPath); + let localSettings: ILocalSettingsJson = await fse.readJson(localSettingsPath); if (localSettings.IsEncrypted) { await decryptLocalSettings(localSettingsUri); try { - localSettings = await fse.readJson(localSettingsPath); + localSettings = await fse.readJson(localSettingsPath); } finally { await encryptLocalSettings(localSettingsUri); } diff --git a/src/commands/createFunction/FunctionCreateStepBase.ts b/src/commands/createFunction/FunctionCreateStepBase.ts index 7651ac5cb..28a658e04 100644 --- a/src/commands/createFunction/FunctionCreateStepBase.ts +++ b/src/commands/createFunction/FunctionCreateStepBase.ts @@ -6,12 +6,14 @@ import * as fse from 'fs-extra'; import * as path from 'path'; import { Progress, Uri, window, workspace } from 'vscode'; -import { AzureWizardExecuteStep, callWithTelemetryAndErrorHandling, IActionContext } from 'vscode-azureextensionui'; -import { localSettingsFileName } from '../../constants'; +import { AzureWizardExecuteStep, callWithTelemetryAndErrorHandling, IActionContext, parseError } from 'vscode-azureextensionui'; +import { hostFileName, localSettingsFileName, ProjectRuntime } from '../../constants'; import { ext } from '../../extensionVariables'; -import { validateAzureWebJobsStorage } from '../../LocalAppSettings'; +import { IHostJson } from '../../funcConfig/host'; +import { validateAzureWebJobsStorage } from '../../funcConfig/local.settings'; import { localize } from '../../localize'; import { IFunctionTemplate } from '../../templates/IFunctionTemplate'; +import { writeFormattedJson } from '../../utils/fs'; import { nonNullProp } from '../../utils/nonNull'; import { getContainingWorkspace } from '../../utils/workspace'; import { IFunctionWizardContext } from './IFunctionWizardContext'; @@ -53,6 +55,10 @@ export abstract class FunctionCreateStepBase e progress.report({ message: localize('creatingFunction', 'Creating new {0}...', template.name) }); const newFilePath: string = await this.executeCore(wizardContext); + if (wizardContext.runtime !== ProjectRuntime.v1 && !template.isHttpTrigger && !template.isTimerTrigger) { + await this.verifyExtensionBundle(wizardContext); + } + const cachedFunc: ICachedFunction = { projectPath: wizardContext.projectPath, newFilePath, isHttpTrigger: template.isHttpTrigger }; if (wizardContext.openBehavior) { @@ -65,6 +71,23 @@ export abstract class FunctionCreateStepBase e runPostFunctionCreateSteps(cachedFunc); } + public async verifyExtensionBundle(wizardContext: T): Promise { + const hostFilePath: string = path.join(wizardContext.projectPath, hostFileName); + try { + const hostJson: IHostJson = await fse.readJSON(hostFilePath); + if (!hostJson.extensionBundle) { + // https://github.com/Microsoft/vscode-azurefunctions/issues/1202 + hostJson.extensionBundle = { + id: 'Microsoft.Azure.Functions.ExtensionBundle', + version: '[1.*, 2.0.0)' + }; + await writeFormattedJson(hostFilePath, hostJson); + } + } catch (error) { + throw new Error(localize('failedToParseHostJson', 'Failed to parse {0}: {1}', hostFileName, parseError(error).message)); + } + } + public shouldExecute(wizardContext: T): boolean { return !!wizardContext.functionTemplate; } diff --git a/src/commands/createFunction/scriptSteps/ScriptFunctionCreateStep.ts b/src/commands/createFunction/scriptSteps/ScriptFunctionCreateStep.ts index 507d42d4d..46d788ef7 100644 --- a/src/commands/createFunction/scriptSteps/ScriptFunctionCreateStep.ts +++ b/src/commands/createFunction/scriptSteps/ScriptFunctionCreateStep.ts @@ -6,7 +6,7 @@ import * as fse from 'fs-extra'; import * as path from 'path'; import { functionJsonFileName, ProjectLanguage } from '../../../constants'; -import { IFunctionBinding, IFunctionJson } from '../../../FunctionConfig'; +import { IFunctionBinding, IFunctionJson } from '../../../funcConfig/function'; import { localize } from '../../../localize'; import { IScriptFunctionTemplate } from '../../../templates/parseScriptTemplates'; import * as fsUtil from '../../../utils/fs'; @@ -48,13 +48,13 @@ export class ScriptFunctionCreateStep extends FunctionCreateStepBase; - protected abstract getTasks(runtime: ProjectRuntime): TaskDefinition[]; + protected abstract getTasks(): TaskDefinition[]; protected getDebugConfiguration?(runtime: ProjectRuntime): DebugConfiguration; protected getRecommendedExtensions?(language: ProjectLanguage): string[]; @@ -71,8 +71,8 @@ export abstract class InitVSCodeStepBase extends AzureWizardExecuteStep { - const newTasks: TaskDefinition[] = this.getTasks(runtime); + private async writeTasksJson(wizardContext: IProjectWizardContext, vscodePath: string): Promise { + const newTasks: TaskDefinition[] = this.getTasks(); for (const task of newTasks) { // tslint:disable-next-line: strict-boolean-expressions no-unsafe-any let cwd: string = (task.options && task.options.cwd) || '.'; diff --git a/src/commands/initProjectForVSCode/InitVSCodeStep/PythonInitVSCodeStep.ts b/src/commands/initProjectForVSCode/InitVSCodeStep/PythonInitVSCodeStep.ts index 8841b19a2..ec0190250 100644 --- a/src/commands/initProjectForVSCode/InitVSCodeStep/PythonInitVSCodeStep.ts +++ b/src/commands/initProjectForVSCode/InitVSCodeStep/PythonInitVSCodeStep.ts @@ -7,9 +7,9 @@ import * as fse from 'fs-extra'; import * as os from 'os'; import * as path from 'path'; import { DebugConfiguration, TaskDefinition } from 'vscode'; -import { extensionPrefix, extInstallCommand, extInstallTaskName, func, funcWatchProblemMatcher, gitignoreFileName, hostStartCommand, isWindows, localSettingsFileName, packTaskName, Platform, pythonVenvSetting } from "../../../constants"; +import { extensionPrefix, extInstallCommand, func, funcWatchProblemMatcher, gitignoreFileName, hostStartCommand, isWindows, localSettingsFileName, packTaskName, Platform, pythonVenvSetting } from "../../../constants"; import { pythonDebugConfig } from '../../../debug/PythonDebugProvider'; -import { azureWebJobsStorageKey, getLocalAppSettings, ILocalAppSettings } from '../../../LocalAppSettings'; +import { azureWebJobsStorageKey, getLocalSettingsJson, ILocalSettingsJson } from '../../../funcConfig/local.settings'; import { writeFormattedJson } from '../../../utils/fs'; import { venvUtils } from '../../../utils/venvUtils'; import { IProjectWizardContext } from '../../createNewProject/IProjectWizardContext'; @@ -21,6 +21,8 @@ export class PythonInitVSCodeStep extends ScriptInitVSCodeStep { private _venvName: string | undefined; protected async executeCore(wizardContext: IProjectWizardContext): Promise { + await super.executeCore(wizardContext); + const zipPath: string = this.setDeploySubpath(wizardContext, `${path.basename(wizardContext.projectPath)}.zip`); this._venvName = await getExistingVenv(wizardContext.projectPath); @@ -38,18 +40,28 @@ export class PythonInitVSCodeStep extends ScriptInitVSCodeStep { } protected getTasks(): TaskDefinition[] { + const pipInstallLabel: string = 'pipInstall'; + const dependsOn: string | undefined = this.requiresFuncExtensionsInstall ? extInstallCommand : this._venvName ? pipInstallLabel : undefined; const tasks: TaskDefinition[] = [ { type: func, command: hostStartCommand, problemMatcher: funcWatchProblemMatcher, isBackground: true, - dependsOn: extInstallTaskName + dependsOn } ]; if (this._venvName) { - const pipInstallLabel: string = 'pipInstall'; + if (this.requiresFuncExtensionsInstall) { + tasks.push({ + type: func, + command: extInstallCommand, + dependsOn: pipInstallLabel, + problemMatcher: [] + }); + } + const venvSettingReference: string = `\${config:${extensionPrefix}.${pythonVenvSetting}}`; function getPipInstallCommand(platform: NodeJS.Platform): string { @@ -57,12 +69,6 @@ export class PythonInitVSCodeStep extends ScriptInitVSCodeStep { } tasks.push( - { - type: func, - command: extInstallCommand, - dependsOn: pipInstallLabel, - problemMatcher: [] - }, { label: pipInstallLabel, type: 'shell', @@ -121,7 +127,7 @@ async function ensureAzureWebJobsStorage(projectPath: string): Promise { // Make sure local settings isn't using Storage Emulator for non-windows // https://github.com/Microsoft/vscode-azurefunctions/issues/583 const localSettingsPath: string = path.join(projectPath, localSettingsFileName); - const localSettings: ILocalAppSettings = await getLocalAppSettings(localSettingsPath); + const localSettings: ILocalSettingsJson = await getLocalSettingsJson(localSettingsPath); // tslint:disable-next-line:strict-boolean-expressions localSettings.Values = localSettings.Values || {}; localSettings.Values[azureWebJobsStorageKey] = ''; diff --git a/src/commands/initProjectForVSCode/InitVSCodeStep/ScriptInitVSCodeStep.ts b/src/commands/initProjectForVSCode/InitVSCodeStep/ScriptInitVSCodeStep.ts index 684c431a8..0ff365f7e 100644 --- a/src/commands/initProjectForVSCode/InitVSCodeStep/ScriptInitVSCodeStep.ts +++ b/src/commands/initProjectForVSCode/InitVSCodeStep/ScriptInitVSCodeStep.ts @@ -3,8 +3,10 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import * as semver from 'semver'; import { TaskDefinition } from 'vscode'; import { extInstallTaskName, func, funcWatchProblemMatcher, hostStartCommand, ProjectRuntime } from '../../../constants'; +import { getLocalFuncCoreToolsVersion } from '../../../funcCoreTools/getLocalFuncCoreToolsVersion'; import { IProjectWizardContext } from '../../createNewProject/IProjectWizardContext'; import { InitVSCodeStepBase } from './InitVSCodeStepBase'; @@ -12,28 +14,41 @@ import { InitVSCodeStepBase } from './InitVSCodeStepBase'; * Base class for all projects based on a simple script (i.e. JavaScript, C# Script, Bash, etc.) that don't require compilation */ export class ScriptInitVSCodeStep extends InitVSCodeStepBase { - protected getTasks(runtime: ProjectRuntime): TaskDefinition[] { + protected requiresFuncExtensionsInstall: boolean = false; + + protected getTasks(): TaskDefinition[] { return [ { type: func, command: hostStartCommand, problemMatcher: funcWatchProblemMatcher, - dependsOn: runtime === ProjectRuntime.v1 ? undefined : extInstallTaskName, + dependsOn: this.requiresFuncExtensionsInstall ? extInstallTaskName : undefined, isBackground: true } ]; } protected async executeCore(wizardContext: IProjectWizardContext): Promise { - // "func extensions install" task creates C# build artifacts that should be hidden - // See issue: https://github.com/Microsoft/vscode-azurefunctions/pull/699 - this.settings.push({ prefix: 'files', key: 'exclude', value: { obj: true, bin: true } }); + if (wizardContext.runtime === ProjectRuntime.v2) { + try { + const currentVersion: string | null = await getLocalFuncCoreToolsVersion(); + // Starting after this version, projects can use extension bundle instead of running "func extensions install" + this.requiresFuncExtensionsInstall = !!currentVersion && semver.lte(currentVersion, '2.5.553'); + } catch { + // use default of false + } + } - this.setDeploySubpath(wizardContext, '.'); - if (!this.preDeployTask) { - if (wizardContext.runtime !== ProjectRuntime.v1) { + if (this.requiresFuncExtensionsInstall) { + // "func extensions install" task creates C# build artifacts that should be hidden + // See issue: https://github.com/Microsoft/vscode-azurefunctions/pull/699 + this.settings.push({ prefix: 'files', key: 'exclude', value: { obj: true, bin: true } }); + + if (!this.preDeployTask) { this.preDeployTask = extInstallTaskName; } } + + this.setDeploySubpath(wizardContext, '.'); } } diff --git a/src/commands/initProjectForVSCode/InitVSCodeStep/TypeScriptInitVSCodeStep.ts b/src/commands/initProjectForVSCode/InitVSCodeStep/TypeScriptInitVSCodeStep.ts index 19ff5e122..39c40ac73 100644 --- a/src/commands/initProjectForVSCode/InitVSCodeStep/TypeScriptInitVSCodeStep.ts +++ b/src/commands/initProjectForVSCode/InitVSCodeStep/TypeScriptInitVSCodeStep.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { TaskDefinition } from 'vscode'; -import { extInstallTaskName, func, funcWatchProblemMatcher, hostStartCommand, ProjectRuntime } from '../../../constants'; +import { extInstallTaskName, func, funcWatchProblemMatcher, hostStartCommand } from '../../../constants'; import { JavaScriptInitVSCodeStep } from "./JavaScriptInitVSCodeStep"; const npmPruneTaskLabel: string = 'npm prune'; @@ -14,7 +14,7 @@ const npmBuildTaskLabel: string = 'npm build'; export class TypeScriptInitVSCodeStep extends JavaScriptInitVSCodeStep { public readonly preDeployTask: string = npmPruneTaskLabel; - public getTasks(runtime: ProjectRuntime): TaskDefinition[] { + public getTasks(): TaskDefinition[] { return [ { type: func, @@ -27,7 +27,7 @@ export class TypeScriptInitVSCodeStep extends JavaScriptInitVSCodeStep { type: 'shell', label: npmBuildTaskLabel, command: 'npm run build', - dependsOn: runtime === ProjectRuntime.v1 ? npmInstallTaskLabel : [extInstallTaskName, npmInstallTaskLabel], + dependsOn: this.requiresFuncExtensionsInstall ? [extInstallTaskName, npmInstallTaskLabel] : npmInstallTaskLabel, problemMatcher: '$tsc' }, { diff --git a/src/funcConfig/README.md b/src/funcConfig/README.md new file mode 100644 index 000000000..b8a559c10 --- /dev/null +++ b/src/funcConfig/README.md @@ -0,0 +1 @@ +This folder contains logic related to Azure Functions project files, like "local.settings.json", "host.json", and "function.json". diff --git a/src/FunctionConfig.ts b/src/funcConfig/function.ts similarity index 62% rename from src/FunctionConfig.ts rename to src/funcConfig/function.ts index d0876d7bc..e60144e34 100644 --- a/src/FunctionConfig.ts +++ b/src/funcConfig/function.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { localize } from "./localize"; +import { localize } from "../localize"; export interface IFunctionJson { disabled?: boolean; @@ -26,56 +26,54 @@ export enum HttpAuthLevel { anonymous = 'anonymous' } -enum BindingDirection { - in = 'in', - out = 'out' -} - /** * Basic config for a function, stored in the 'function.json' file at the root of the function's folder * Since the user can manually edit their 'function.json' file, we can't assume it will have the proper schema */ -export class FunctionConfig { - public readonly functionJson: IFunctionJson; +export class ParsedFunctionJson { + public readonly data: IFunctionJson; // tslint:disable-next-line:no-any public constructor(data: any) { // tslint:disable-next-line:no-unsafe-any if (typeof data === 'object' && data !== null && (data.bindings === undefined || data.bindings instanceof Array)) { - this.functionJson = data; + this.data = data; } else { - this.functionJson = {}; + this.data = {}; } } public get bindings(): IFunctionBinding[] { // tslint:disable-next-line: strict-boolean-expressions - return this.functionJson.bindings || []; + return this.data.bindings || []; } public get disabled(): boolean { - return this.functionJson.disabled === true; + return this.data.disabled === true; } - public get inBinding(): IFunctionBinding | undefined { - let inBinding: IFunctionBinding | undefined = this.bindings.find((b: IFunctionBinding) => b.direction === BindingDirection.in); - if (inBinding === undefined && this.bindings.length > 0) { - // The generated 'function.json' file for C# class libraries doesn't have direction information (by design), so just use the first - inBinding = this.bindings[0]; - } - - return inBinding; + /** + * A trigger defines how a function is invoked and a function must have exactly one trigger. + * https://docs.microsoft.com/azure/azure-functions/functions-triggers-bindings + */ + public get triggerBinding(): IFunctionBinding | undefined { + // tslint:disable-next-line: strict-boolean-expressions + return this.bindings.find(b => /trigger$/i.test(b.type || '')); } public get isHttpTrigger(): boolean { - return !!this.inBinding && !!this.inBinding.type && this.inBinding.type.toLowerCase() === 'httptrigger'; + return !!this.triggerBinding && !!this.triggerBinding.type && /^http/i.test(this.triggerBinding.type); + } + + public get isTimerTrigger(): boolean { + return !!this.triggerBinding && !!this.triggerBinding.type && /^timer/i.test(this.triggerBinding.type); } public get authLevel(): HttpAuthLevel { - if (this.inBinding && this.inBinding.authLevel) { - const authLevel: HttpAuthLevel | undefined = HttpAuthLevel[this.inBinding.authLevel.toLowerCase()]; + if (this.triggerBinding && this.triggerBinding.authLevel) { + const authLevel: HttpAuthLevel | undefined = HttpAuthLevel[this.triggerBinding.authLevel.toLowerCase()]; if (authLevel === undefined) { - throw new Error(localize('unrecognizedAuthLevel', 'Unrecognized auth level "{0}".', this.inBinding.authLevel)); + throw new Error(localize('unrecognizedAuthLevel', 'Unrecognized auth level "{0}".', this.triggerBinding.authLevel)); } else { return authLevel; } diff --git a/src/funcConfig/host.ts b/src/funcConfig/host.ts new file mode 100644 index 000000000..8f7539b0e --- /dev/null +++ b/src/funcConfig/host.ts @@ -0,0 +1,15 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +export interface IHostJson { + version?: string; + managedDependency?: { + enabled?: boolean; + }; + extensionBundle?: { + id?: string; + version?: string; + }; +} diff --git a/src/LocalAppSettings.ts b/src/funcConfig/local.settings.ts similarity index 87% rename from src/LocalAppSettings.ts rename to src/funcConfig/local.settings.ts index 6fcdb63c5..df1870972 100644 --- a/src/LocalAppSettings.ts +++ b/src/funcConfig/local.settings.ts @@ -7,13 +7,13 @@ import * as fse from 'fs-extra'; import * as path from 'path'; import * as vscode from 'vscode'; import { DialogResponses, IActionContext, parseError, StorageAccountKind, StorageAccountPerformance, StorageAccountReplication } from 'vscode-azureextensionui'; -import { localSettingsFileName } from './constants'; -import { ext } from './extensionVariables'; -import { localize } from './localize'; -import * as azUtil from './utils/azure'; -import * as fsUtil from './utils/fs'; +import { localSettingsFileName } from '../constants'; +import { ext } from '../extensionVariables'; +import { localize } from '../localize'; +import * as azUtil from '../utils/azure'; +import * as fsUtil from '../utils/fs'; -export interface ILocalAppSettings { +export interface ILocalSettingsJson { IsEncrypted?: boolean; Values?: { [key: string]: string }; ConnectionStrings?: { [key: string]: string }; @@ -27,7 +27,7 @@ export async function validateAzureWebJobsStorage(actionContext: IActionContext, return; } - const settings: ILocalAppSettings = await getLocalAppSettings(localSettingsPath); + const settings: ILocalSettingsJson = await getLocalSettingsJson(localSettingsPath); if (settings.Values && settings.Values[azureWebJobsStorageKey]) { return; } @@ -58,9 +58,10 @@ export async function validateAzureWebJobsStorage(actionContext: IActionContext, await fsUtil.writeFormattedJson(localSettingsPath, settings); } } + export async function setLocalAppSetting(functionAppPath: string, key: string, value: string): Promise { const localSettingsPath: string = path.join(functionAppPath, localSettingsFileName); - const settings: ILocalAppSettings = await getLocalAppSettings(localSettingsPath); + const settings: ILocalSettingsJson = await getLocalSettingsJson(localSettingsPath); // tslint:disable-next-line:strict-boolean-expressions settings.Values = settings.Values || {}; @@ -77,12 +78,12 @@ export async function setLocalAppSetting(functionAppPath: string, key: string, v await fsUtil.writeFormattedJson(localSettingsPath, settings); } -export async function getLocalAppSettings(localSettingsPath: string, allowOverwrite: boolean = false): Promise { +export async function getLocalSettingsJson(localSettingsPath: string, allowOverwrite: boolean = false): Promise { if (await fse.pathExists(localSettingsPath)) { const data: string = (await fse.readFile(localSettingsPath)).toString(); if (/[^\s]/.test(data)) { try { - return JSON.parse(data); + return JSON.parse(data); } catch (error) { if (allowOverwrite) { const message: string = localize('failedToParseWithOverwrite', 'Failed to parse "{0}": {1}. Overwrite?', localSettingsFileName, parseError(error).message); diff --git a/src/templates/IFunctionTemplate.ts b/src/templates/IFunctionTemplate.ts index 5aa1b258c..878616e30 100644 --- a/src/templates/IFunctionTemplate.ts +++ b/src/templates/IFunctionTemplate.ts @@ -18,6 +18,7 @@ export interface IFunctionTemplate { defaultFunctionName: string; language: string; isHttpTrigger: boolean; + isTimerTrigger: boolean; userPromptedSettings: IFunctionSetting[]; categories: TemplateCategory[]; } diff --git a/src/templates/parseDotnetTemplates.ts b/src/templates/parseDotnetTemplates.ts index 72726cc38..3a9471652 100644 --- a/src/templates/parseDotnetTemplates.ts +++ b/src/templates/parseDotnetTemplates.ts @@ -54,7 +54,8 @@ function parseDotnetTemplate(rawTemplate: IRawTemplate): IFunctionTemplate { } return { - isHttpTrigger: rawTemplate.Name.toLowerCase().startsWith('http') || rawTemplate.Name.toLowerCase().endsWith('webhook'), + isHttpTrigger: /^http/i.test(rawTemplate.Name) || /webhook$/i.test(rawTemplate.Name), + isTimerTrigger: /^timer/i.test(rawTemplate.Name), id: rawTemplate.Identity, name: rawTemplate.Name, defaultFunctionName: rawTemplate.DefaultName, diff --git a/src/templates/parseScriptTemplates.ts b/src/templates/parseScriptTemplates.ts index 50304b7cc..b29281478 100644 --- a/src/templates/parseScriptTemplates.ts +++ b/src/templates/parseScriptTemplates.ts @@ -6,7 +6,7 @@ import { isString } from 'util'; import { ProjectLanguage } from '../constants'; import { ext } from '../extensionVariables'; -import { FunctionConfig, IFunctionBinding } from '../FunctionConfig'; +import { IFunctionBinding, ParsedFunctionJson } from '../funcConfig/function'; import { IBindingTemplate } from './IBindingTemplate'; import { IEnumValue, IFunctionSetting, ResourceType, ValueType } from './IFunctionSetting'; import { IFunctionTemplate, TemplateCategory } from './IFunctionTemplate'; @@ -155,7 +155,7 @@ export function parseScriptBindings(config: IConfig, resources: IResources): IBi } export function parseScriptTemplate(rawTemplate: IRawTemplate, resources: IResources, bindingTemplates: IBindingTemplate[]): IScriptFunctionTemplate { - const functionConfig: FunctionConfig = new FunctionConfig(rawTemplate.function); + const functionJson: ParsedFunctionJson = new ParsedFunctionJson(rawTemplate.function); let language: ProjectLanguage = rawTemplate.metadata.language; // The templateApiZip only supports script languages, and thus incorrectly defines 'C#Script' as 'C#', etc. @@ -176,13 +176,13 @@ export function parseScriptTemplate(rawTemplate: IRawTemplate, resources: IResou const userPromptedSettings: IFunctionSetting[] = []; if (rawTemplate.metadata.userPrompt) { for (const settingName of rawTemplate.metadata.userPrompt) { - if (functionConfig.inBinding) { - const inBinding: IFunctionBinding = functionConfig.inBinding; - const bindingTemplate: IBindingTemplate | undefined = bindingTemplates.find(b => b.type === inBinding.type); + if (functionJson.triggerBinding) { + const triggerBinding: IFunctionBinding = functionJson.triggerBinding; + const bindingTemplate: IBindingTemplate | undefined = bindingTemplates.find(b => b.type === triggerBinding.type); if (bindingTemplate) { const setting: IFunctionSetting | undefined = bindingTemplate.settings.find((bs: IFunctionSetting) => bs.name === settingName); if (setting) { - const functionSpecificDefaultValue: string | undefined = inBinding[setting.name]; + const functionSpecificDefaultValue: string | undefined = triggerBinding[setting.name]; if (functionSpecificDefaultValue) { // overwrite common default value with the function-specific default value setting.defaultValue = functionSpecificDefaultValue; @@ -195,13 +195,14 @@ export function parseScriptTemplate(rawTemplate: IRawTemplate, resources: IResou } return { - functionConfig: functionConfig, - isHttpTrigger: functionConfig.isHttpTrigger, + functionJson, + isHttpTrigger: functionJson.isHttpTrigger, + isTimerTrigger: functionJson.isTimerTrigger, id: rawTemplate.id, name: getResourceValue(resources, rawTemplate.metadata.name), defaultFunctionName: rawTemplate.metadata.defaultFunctionName, - language: language, - userPromptedSettings: userPromptedSettings, + language, + userPromptedSettings, templateFiles: rawTemplate.files, categories: rawTemplate.metadata.category }; @@ -209,7 +210,7 @@ export function parseScriptTemplate(rawTemplate: IRawTemplate, resources: IResou export interface IScriptFunctionTemplate extends IFunctionTemplate { templateFiles: { [filename: string]: string }; - functionConfig: FunctionConfig; + functionJson: ParsedFunctionJson; } /** diff --git a/src/tree/FunctionTreeItem.ts b/src/tree/FunctionTreeItem.ts index 7631c513c..1935a397a 100644 --- a/src/tree/FunctionTreeItem.ts +++ b/src/tree/FunctionTreeItem.ts @@ -10,7 +10,7 @@ import { functionsAdminRequest, ISiteTreeRoot } from 'vscode-azureappservice'; import { AzureTreeItem, DialogResponses } from 'vscode-azureextensionui'; import { ProjectRuntime } from '../constants'; import { ext } from '../extensionVariables'; -import { FunctionConfig, HttpAuthLevel } from '../FunctionConfig'; +import { HttpAuthLevel, ParsedFunctionJson } from '../funcConfig/function'; import { localize } from '../localize'; import { nodeUtils } from '../utils/nodeUtils'; import { nonNullProp } from '../utils/nonNull'; @@ -21,7 +21,7 @@ export class FunctionTreeItem extends AzureTreeItem { public static contextValue: string = 'azFuncFunction'; public static readOnlyContextValue: string = 'azFuncFunctionReadOnly'; public readonly parent: FunctionsTreeItem; - public readonly config: FunctionConfig; + public readonly config: ParsedFunctionJson; private readonly _name: string; private _triggerUrl: string | undefined; @@ -31,7 +31,7 @@ export class FunctionTreeItem extends AzureTreeItem { super(parent); this._name = getFunctionNameFromId(nonNullProp(func, 'id')); - this.config = new FunctionConfig(func.config); + this.config = new ParsedFunctionJson(func.config); } public static async createFunctionTreeItem(parent: FunctionsTreeItem, func: WebSiteManagementModels.FunctionEnvelope): Promise { diff --git a/src/tree/localProject/LocalBindingTreeItem.ts b/src/tree/localProject/LocalBindingTreeItem.ts index f2d49d07e..887c8d9f0 100644 --- a/src/tree/localProject/LocalBindingTreeItem.ts +++ b/src/tree/localProject/LocalBindingTreeItem.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { AzureTreeItem } from 'vscode-azureextensionui'; -import { IFunctionBinding } from '../../FunctionConfig'; +import { IFunctionBinding } from '../../funcConfig/function'; import { nonNullProp } from '../../utils/nonNull'; import { IProjectRoot } from './IProjectRoot'; import { LocalBindingsTreeItem } from './LocalBindingsTreeItem'; diff --git a/src/tree/localProject/LocalBindingsTreeItem.ts b/src/tree/localProject/LocalBindingsTreeItem.ts index c70074a6e..57faf47e0 100644 --- a/src/tree/localProject/LocalBindingsTreeItem.ts +++ b/src/tree/localProject/LocalBindingsTreeItem.ts @@ -6,7 +6,7 @@ import { AzureParentTreeItem, AzureWizard, IActionContext } from 'vscode-azureextensionui'; import { createBindingWizard } from '../../commands/addBinding/createBindingWizard'; import { IBindingWizardContext } from '../../commands/addBinding/IBindingWizardContext'; -import { FunctionConfig } from '../../FunctionConfig'; +import { ParsedFunctionJson } from '../../funcConfig/function'; import { localize } from '../../localize'; import { nodeUtils } from '../../utils/nodeUtils'; import { nonNullProp } from '../../utils/nonNull'; @@ -21,9 +21,9 @@ export class LocalBindingsTreeItem extends AzureParentTreeItem { public readonly childTypeLabel: string = localize('binding', 'binding'); public functionJsonPath: string; - private readonly _config: FunctionConfig; + private readonly _config: ParsedFunctionJson; - public constructor(parent: LocalFunctionTreeItem, config: FunctionConfig, functionJsonPath: string) { + public constructor(parent: LocalFunctionTreeItem, config: ParsedFunctionJson, functionJsonPath: string) { super(parent); this._config = config; this.functionJsonPath = functionJsonPath; diff --git a/src/tree/localProject/LocalFunctionTreeItem.ts b/src/tree/localProject/LocalFunctionTreeItem.ts index 5adec47bf..4d33d89ba 100644 --- a/src/tree/localProject/LocalFunctionTreeItem.ts +++ b/src/tree/localProject/LocalFunctionTreeItem.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { AzureParentTreeItem, AzureTreeItem } from 'vscode-azureextensionui'; -import { FunctionConfig } from '../../FunctionConfig'; +import { ParsedFunctionJson } from '../../funcConfig/function'; import { nodeUtils } from '../../utils/nodeUtils'; import { FunctionTreeItem } from '../FunctionTreeItem'; import { IProjectRoot } from './IProjectRoot'; @@ -17,7 +17,7 @@ export class LocalFunctionTreeItem extends AzureParentTreeItem { private readonly _name: string; private _bindingsNode: LocalBindingsTreeItem; - public constructor(parent: LocalFunctionsTreeItem, name: string, config: FunctionConfig, functionJsonPath: string) { + public constructor(parent: LocalFunctionsTreeItem, name: string, config: ParsedFunctionJson, functionJsonPath: string) { super(parent); this._name = name; this._bindingsNode = new LocalBindingsTreeItem(this, config, functionJsonPath); diff --git a/src/tree/localProject/LocalFunctionsTreeItem.ts b/src/tree/localProject/LocalFunctionsTreeItem.ts index 10e03e531..e73d56c65 100644 --- a/src/tree/localProject/LocalFunctionsTreeItem.ts +++ b/src/tree/localProject/LocalFunctionsTreeItem.ts @@ -7,7 +7,7 @@ import * as fse from 'fs-extra'; import * as path from 'path'; import { AzureParentTreeItem, AzureTreeItem, createTreeItemsWithErrorHandling } from 'vscode-azureextensionui'; import { functionJsonFileName } from '../../constants'; -import { FunctionConfig } from '../../FunctionConfig'; +import { ParsedFunctionJson } from '../../funcConfig/function'; import { localize } from '../../localize'; import { nodeUtils } from '../../utils/nodeUtils'; import { IProjectRoot } from './IProjectRoot'; @@ -47,7 +47,7 @@ export class LocalFunctionsTreeItem extends AzureParentTreeItem { 'azFuncInvalidLocalFunction', async func => { const functionJsonPath: string = path.join(this.root.projectPath, func, functionJsonFileName); - const config: FunctionConfig = new FunctionConfig(await fse.readJSON(functionJsonPath)); + const config: ParsedFunctionJson = new ParsedFunctionJson(await fse.readJSON(functionJsonPath)); return new LocalFunctionTreeItem(this, func, config, functionJsonPath); }, (func: string) => func diff --git a/src/vsCodeConfig/README.md b/src/vsCodeConfig/README.md new file mode 100644 index 000000000..e3d1b7ccc --- /dev/null +++ b/src/vsCodeConfig/README.md @@ -0,0 +1 @@ +This folder contains logic related to VS Code project files, like "settings.json", "tasks.json", and "launch.json". diff --git a/test/ParsedFunctionJson.test.ts b/test/ParsedFunctionJson.test.ts new file mode 100644 index 000000000..af52cf47d --- /dev/null +++ b/test/ParsedFunctionJson.test.ts @@ -0,0 +1,240 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as assert from 'assert'; +import { HttpAuthLevel, ParsedFunctionJson } from '../extension.bundle'; + +// tslint:disable-next-line:max-func-body-length +suite('ParsedFunctionJson', () => { + test('null', () => { + const funcJson: ParsedFunctionJson = new ParsedFunctionJson(null); + assert.equal(funcJson.authLevel, HttpAuthLevel.function); + assert.equal(funcJson.bindings.length, 0); + assert.equal(funcJson.disabled, false); + assert.equal(funcJson.triggerBinding, undefined); + assert.equal(funcJson.isHttpTrigger, false); + assert.equal(funcJson.isTimerTrigger, false); + }); + + test('undefined', () => { + const funcJson: ParsedFunctionJson = new ParsedFunctionJson(undefined); + assert.equal(funcJson.authLevel, HttpAuthLevel.function); + assert.equal(funcJson.bindings.length, 0); + assert.equal(funcJson.disabled, false); + assert.equal(funcJson.triggerBinding, undefined); + assert.equal(funcJson.isHttpTrigger, false); + assert.equal(funcJson.isTimerTrigger, false); + }); + + test('empty object', () => { + const funcJson: ParsedFunctionJson = new ParsedFunctionJson({}); + assert.equal(funcJson.authLevel, HttpAuthLevel.function); + assert.equal(funcJson.bindings.length, 0); + assert.equal(funcJson.disabled, false); + assert.equal(funcJson.triggerBinding, undefined); + assert.equal(funcJson.isHttpTrigger, false); + assert.equal(funcJson.isTimerTrigger, false); + }); + + test('bindings is not array', () => { + const funcJson: ParsedFunctionJson = new ParsedFunctionJson({ bindings: 'test' }); + assert.equal(funcJson.authLevel, HttpAuthLevel.function); + assert.equal(funcJson.bindings.length, 0); + assert.equal(funcJson.disabled, false); + assert.equal(funcJson.triggerBinding, undefined); + assert.equal(funcJson.isHttpTrigger, false); + assert.equal(funcJson.isTimerTrigger, false); + }); + + test('disabled function', () => { + const funcJson: ParsedFunctionJson = new ParsedFunctionJson({ + disabled: true + }); + assert.equal(funcJson.authLevel, HttpAuthLevel.function); + assert.equal(funcJson.bindings.length, 0); + assert.equal(funcJson.disabled, true); + assert.equal(funcJson.triggerBinding, undefined); + assert.equal(funcJson.isHttpTrigger, false); + assert.equal(funcJson.isTimerTrigger, false); + }); + + test('trigger binding type is not http', () => { + const triggerBinding: {} = { + direction: 'in', + type: 'testTrigger' + }; + const funcJson: ParsedFunctionJson = new ParsedFunctionJson({ bindings: [triggerBinding] }); + assert.equal(funcJson.authLevel, HttpAuthLevel.function); + assert.equal(funcJson.bindings.length, 1); + assert.equal(funcJson.disabled, false); + assert.equal(funcJson.triggerBinding, triggerBinding); + assert.equal(funcJson.isHttpTrigger, false); + assert.equal(funcJson.isTimerTrigger, false); + }); + + test('http trigger', () => { + const triggerBinding: {} = { + direction: 'in', + type: 'httpTrigger' + }; + const funcJson: ParsedFunctionJson = new ParsedFunctionJson({ bindings: [triggerBinding] }); + assert.equal(funcJson.authLevel, HttpAuthLevel.function); + assert.equal(funcJson.bindings.length, 1); + assert.equal(funcJson.disabled, false); + assert.equal(funcJson.triggerBinding, triggerBinding); + assert.equal(funcJson.isHttpTrigger, true); + assert.equal(funcJson.isTimerTrigger, false); + }); + + test('http trigger weird casing', () => { + const triggerBinding: {} = { + direction: 'in', + type: 'hTtpTrigGer' + }; + const funcJson: ParsedFunctionJson = new ParsedFunctionJson({ bindings: [triggerBinding] }); + assert.equal(funcJson.authLevel, HttpAuthLevel.function); + assert.equal(funcJson.bindings.length, 1); + assert.equal(funcJson.disabled, false); + assert.equal(funcJson.triggerBinding, triggerBinding); + assert.equal(funcJson.isHttpTrigger, true); + assert.equal(funcJson.isTimerTrigger, false); + }); + + test('timer trigger', () => { + const triggerBinding: {} = { + direction: 'in', + type: 'timerTrigger' + }; + const funcJson: ParsedFunctionJson = new ParsedFunctionJson({ bindings: [triggerBinding] }); + assert.equal(funcJson.authLevel, HttpAuthLevel.function); + assert.equal(funcJson.bindings.length, 1); + assert.equal(funcJson.disabled, false); + assert.equal(funcJson.triggerBinding, triggerBinding); + assert.equal(funcJson.isHttpTrigger, false); + assert.equal(funcJson.isTimerTrigger, true); + }); + + test('timer trigger weird casing', () => { + const triggerBinding: {} = { + direction: 'in', + type: 'TiMerTriggER' + }; + const funcJson: ParsedFunctionJson = new ParsedFunctionJson({ bindings: [triggerBinding] }); + assert.equal(funcJson.authLevel, HttpAuthLevel.function); + assert.equal(funcJson.bindings.length, 1); + assert.equal(funcJson.disabled, false); + assert.equal(funcJson.triggerBinding, triggerBinding); + assert.equal(funcJson.isHttpTrigger, false); + assert.equal(funcJson.isTimerTrigger, true); + }); + + test('admin auth level', () => { + const triggerBinding: {} = { + direction: 'in', + type: 'httpTrigger', + authLevel: 'admin' + }; + const funcJson: ParsedFunctionJson = new ParsedFunctionJson({ bindings: [triggerBinding] }); + assert.equal(funcJson.authLevel, HttpAuthLevel.admin); + assert.equal(funcJson.bindings.length, 1); + assert.equal(funcJson.disabled, false); + assert.equal(funcJson.triggerBinding, triggerBinding); + assert.equal(funcJson.isHttpTrigger, true); + assert.equal(funcJson.isTimerTrigger, false); + }); + + test('function auth level', () => { + const triggerBinding: {} = { + direction: 'in', + type: 'httpTrigger', + authLevel: 'function' + }; + const funcJson: ParsedFunctionJson = new ParsedFunctionJson({ bindings: [triggerBinding] }); + assert.equal(funcJson.authLevel, HttpAuthLevel.function); + assert.equal(funcJson.bindings.length, 1); + assert.equal(funcJson.disabled, false); + assert.equal(funcJson.triggerBinding, triggerBinding); + assert.equal(funcJson.isHttpTrigger, true); + assert.equal(funcJson.isTimerTrigger, false); + }); + + test('anonymous auth level', () => { + const triggerBinding: {} = { + direction: 'in', + type: 'httpTrigger', + authLevel: 'anonymous' + }; + const funcJson: ParsedFunctionJson = new ParsedFunctionJson({ bindings: [triggerBinding] }); + assert.equal(funcJson.authLevel, HttpAuthLevel.anonymous); + assert.equal(funcJson.bindings.length, 1); + assert.equal(funcJson.disabled, false); + assert.equal(funcJson.triggerBinding, triggerBinding); + assert.equal(funcJson.isHttpTrigger, true); + assert.equal(funcJson.isTimerTrigger, false); + }); + + test('unrecognized auth level', () => { + const triggerBinding: {} = { + direction: 'in', + type: 'httpTrigger', + authLevel: 'testAuthLevel' + }; + const funcJson: ParsedFunctionJson = new ParsedFunctionJson({ bindings: [triggerBinding] }); + assert.throws( + () => funcJson.authLevel, + (error: Error) => error.message.includes('Unrecognized') && error.message.includes('testAuthLevel') + ); + assert.equal(funcJson.bindings.length, 1); + assert.equal(funcJson.disabled, false); + assert.equal(funcJson.triggerBinding, triggerBinding); + assert.equal(funcJson.isHttpTrigger, true); + assert.equal(funcJson.isTimerTrigger, false); + }); + + test('Multiple http bindings', () => { + const triggerBinding: {} = { + direction: 'in', + type: 'httpTrigger', + authLevel: 'admin' + }; + const funcJson: ParsedFunctionJson = new ParsedFunctionJson({ + bindings: [ + { + direction: 'out', + type: 'http', + authLevel: 'anonymous' + }, + { + direction: 'in', + type: 'http', + authLevel: 'anonymous' + }, + triggerBinding + ] + }); + // auth level from triggerBinding should be used + assert.equal(funcJson.authLevel, HttpAuthLevel.admin); + assert.equal(funcJson.bindings.length, 3); + assert.equal(funcJson.disabled, false); + assert.equal(funcJson.triggerBinding, triggerBinding); + assert.equal(funcJson.isHttpTrigger, true); + assert.equal(funcJson.isTimerTrigger, false); + }); + + // This happens for C# functions + test('generated function.json that doesn\'t have direction defined', () => { + const triggerBinding: {} = { + type: 'httpTrigger', + authLevel: 'admin' + }; + const funcJson: ParsedFunctionJson = new ParsedFunctionJson({ bindings: [triggerBinding] }); + assert.equal(funcJson.authLevel, HttpAuthLevel.admin); + assert.equal(funcJson.bindings.length, 1); + assert.equal(funcJson.disabled, false); + assert.equal(funcJson.triggerBinding, triggerBinding); + assert.equal(funcJson.isHttpTrigger, true); + assert.equal(funcJson.isTimerTrigger, false); + }); +}); diff --git a/test/convertStringToRuntime.test.ts b/test/convertStringToRuntime.test.ts index 571102cd4..222dd12ae 100644 --- a/test/convertStringToRuntime.test.ts +++ b/test/convertStringToRuntime.test.ts @@ -6,7 +6,7 @@ import * as assert from 'assert'; import { convertStringToRuntime, ProjectRuntime } from '../extension.bundle'; -suite('convertStringToRuntime Tests', () => { +suite('convertStringToRuntime', () => { const specificOne: string = '1.0.0'; test(specificOne, () => { assert.equal(convertStringToRuntime(specificOne), ProjectRuntime.v1); diff --git a/test/createFunction/createFunction.CSharp.test.ts b/test/createFunction/createFunction.CSharp.test.ts index 14a569ad7..4d4c09c04 100644 --- a/test/createFunction/createFunction.CSharp.test.ts +++ b/test/createFunction/createFunction.CSharp.test.ts @@ -23,7 +23,7 @@ class CSharpFunctionTester extends FunctionTesterBase { } // tslint:disable-next-line:no-function-expression max-func-body-length -suite('Create C# ~2 Function Tests', async function (this: ISuiteCallbackContext): Promise { +suite('Create C# ~2 Function', async function (this: ISuiteCallbackContext): Promise { this.timeout(40 * 1000); const csTester: CSharpFunctionTester = new CSharpFunctionTester(); diff --git a/test/createFunction/createFunction.CSharpScript.test.ts b/test/createFunction/createFunction.CSharpScript.test.ts index e40bbebe3..fd7cf1f3a 100644 --- a/test/createFunction/createFunction.CSharpScript.test.ts +++ b/test/createFunction/createFunction.CSharpScript.test.ts @@ -24,7 +24,7 @@ class CSharpScriptFunctionTester extends FunctionTesterBase { } } -suite('Create C# Script ~1 Function Tests', async () => { +suite('Create C# Script ~1 Function', async () => { const tester: CSharpScriptFunctionTester = new CSharpScriptFunctionTester(); suiteSetup(async () => { diff --git a/test/createFunction/createFunction.JavaScript.v1.test.ts b/test/createFunction/createFunction.JavaScript.v1.test.ts index faf2dfa30..350c0b7b3 100644 --- a/test/createFunction/createFunction.JavaScript.v1.test.ts +++ b/test/createFunction/createFunction.JavaScript.v1.test.ts @@ -26,7 +26,7 @@ class JSFunctionTester extends FunctionTesterBase { } // tslint:disable-next-line:max-func-body-length no-function-expression -suite('Create JavaScript ~1 Function Tests', async function (this: ISuiteCallbackContext): Promise { +suite('Create JavaScript ~1 Function', async function (this: ISuiteCallbackContext): Promise { const jsTester: JSFunctionTester = new JSFunctionTester(); suiteSetup(async () => { diff --git a/test/createNewProject.test.ts b/test/createNewProject.test.ts index b5ddc8ecd..3d9a6eebd 100644 --- a/test/createNewProject.test.ts +++ b/test/createNewProject.test.ts @@ -25,7 +25,7 @@ import { } from './validateProject'; // tslint:disable-next-line:no-function-expression max-func-body-length -suite('Create New Project Tests', async function (this: ISuiteCallbackContext): Promise { +suite('Create New Project', async function (this: ISuiteCallbackContext): Promise { this.timeout(60 * 1000); const javaProject: string = 'JavaProject'; diff --git a/test/fsUtils.test.ts b/test/fsUtils.test.ts index 8f4be800e..0491ad89e 100644 --- a/test/fsUtils.test.ts +++ b/test/fsUtils.test.ts @@ -8,7 +8,7 @@ import * as path from 'path'; import { isPathEqual, isSubpath } from '../extension.bundle'; // tslint:disable-next-line:max-func-body-length -suite('fsUtils Tests', () => { +suite('fsUtils', () => { test('isPathEqual, posix, true', () => { let fsPath1: string = '/test/'; let fsPath2: string = '/test/'; diff --git a/test/functionConfig.test.ts b/test/functionConfig.test.ts deleted file mode 100644 index a0f1902f3..000000000 --- a/test/functionConfig.test.ts +++ /dev/null @@ -1,192 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as assert from 'assert'; -import { FunctionConfig, HttpAuthLevel } from '../extension.bundle'; - -// tslint:disable-next-line:max-func-body-length -suite('Function Config Tests', () => { - test('null', () => { - const config: FunctionConfig = new FunctionConfig(null); - assert.equal(config.authLevel, HttpAuthLevel.function); - assert.equal(config.bindings.length, 0); - assert.equal(config.disabled, false); - assert.equal(config.inBinding, undefined); - assert.equal(config.isHttpTrigger, false); - }); - - test('undefined', () => { - const config: FunctionConfig = new FunctionConfig(undefined); - assert.equal(config.authLevel, HttpAuthLevel.function); - assert.equal(config.bindings.length, 0); - assert.equal(config.disabled, false); - assert.equal(config.inBinding, undefined); - assert.equal(config.isHttpTrigger, false); - }); - - test('empty object', () => { - const config: FunctionConfig = new FunctionConfig({}); - assert.equal(config.authLevel, HttpAuthLevel.function); - assert.equal(config.bindings.length, 0); - assert.equal(config.disabled, false); - assert.equal(config.inBinding, undefined); - assert.equal(config.isHttpTrigger, false); - }); - - test('bindings is not array', () => { - const config: FunctionConfig = new FunctionConfig({ bindings: 'test' }); - assert.equal(config.authLevel, HttpAuthLevel.function); - assert.equal(config.bindings.length, 0); - assert.equal(config.disabled, false); - assert.equal(config.inBinding, undefined); - assert.equal(config.isHttpTrigger, false); - }); - - test('disabled function', () => { - const config: FunctionConfig = new FunctionConfig({ - disabled: true - }); - assert.equal(config.authLevel, HttpAuthLevel.function); - assert.equal(config.bindings.length, 0); - assert.equal(config.disabled, true); - assert.equal(config.inBinding, undefined); - assert.equal(config.isHttpTrigger, false); - }); - - test('in binding type is undefined', () => { - const inBinding: {} = { - direction: 'in' - }; - const config: FunctionConfig = new FunctionConfig({ bindings: [inBinding] }); - assert.equal(config.authLevel, HttpAuthLevel.function); - assert.equal(config.bindings.length, 1); - assert.equal(config.disabled, false); - assert.equal(config.inBinding, inBinding); - assert.equal(config.isHttpTrigger, false); - }); - - test('in binding type is not http', () => { - const inBinding: {} = { - direction: 'in', - type: 'testType' - }; - const config: FunctionConfig = new FunctionConfig({ bindings: [inBinding] }); - assert.equal(config.authLevel, HttpAuthLevel.function); - assert.equal(config.bindings.length, 1); - assert.equal(config.disabled, false); - assert.equal(config.inBinding, inBinding); - assert.equal(config.isHttpTrigger, false); - }); - - test('http trigger', () => { - const inBinding: {} = { - direction: 'in', - type: 'httpTrigger' - }; - const config: FunctionConfig = new FunctionConfig({ bindings: [inBinding] }); - assert.equal(config.authLevel, HttpAuthLevel.function); - assert.equal(config.bindings.length, 1); - assert.equal(config.disabled, false); - assert.equal(config.inBinding, inBinding); - assert.equal(config.isHttpTrigger, true); - }); - - test('admin auth level', () => { - const inBinding: {} = { - direction: 'in', - type: 'httpTrigger', - authLevel: 'admin' - }; - const config: FunctionConfig = new FunctionConfig({ bindings: [inBinding] }); - assert.equal(config.authLevel, HttpAuthLevel.admin); - assert.equal(config.bindings.length, 1); - assert.equal(config.disabled, false); - assert.equal(config.inBinding, inBinding); - assert.equal(config.isHttpTrigger, true); - }); - - test('function auth level', () => { - const inBinding: {} = { - direction: 'in', - type: 'httpTrigger', - authLevel: 'function' - }; - const config: FunctionConfig = new FunctionConfig({ bindings: [inBinding] }); - assert.equal(config.authLevel, HttpAuthLevel.function); - assert.equal(config.bindings.length, 1); - assert.equal(config.disabled, false); - assert.equal(config.inBinding, inBinding); - assert.equal(config.isHttpTrigger, true); - }); - - test('anonymous auth level', () => { - const inBinding: {} = { - direction: 'in', - type: 'httpTrigger', - authLevel: 'anonymous' - }; - const config: FunctionConfig = new FunctionConfig({ bindings: [inBinding] }); - assert.equal(config.authLevel, HttpAuthLevel.anonymous); - assert.equal(config.bindings.length, 1); - assert.equal(config.disabled, false); - assert.equal(config.inBinding, inBinding); - assert.equal(config.isHttpTrigger, true); - }); - - test('unrecognized auth level', () => { - const inBinding: {} = { - direction: 'in', - type: 'httpTrigger', - authLevel: 'testAuthLevel' - }; - const config: FunctionConfig = new FunctionConfig({ bindings: [inBinding] }); - assert.throws( - () => config.authLevel, - (error: Error) => error.message.includes('Unrecognized') && error.message.includes('testAuthLevel') - ); - assert.equal(config.bindings.length, 1); - assert.equal(config.disabled, false); - assert.equal(config.inBinding, inBinding); - assert.equal(config.isHttpTrigger, true); - }); - - test('Multiple http bindings', () => { - const inBinding: {} = { - direction: 'in', - type: 'httpTrigger', - authLevel: 'admin' - }; - const config: FunctionConfig = new FunctionConfig({ - bindings: [ - { - direction: 'out', - type: 'httpTrigger', - authLevel: 'anonymous' - }, - inBinding - ] - }); - // auth level from 'in' inBinding should be used - assert.equal(config.authLevel, HttpAuthLevel.admin); - assert.equal(config.bindings.length, 2); - assert.equal(config.disabled, false); - assert.equal(config.inBinding, inBinding); - assert.equal(config.isHttpTrigger, true); - }); - - // This happens for C# functions - test('generated function.json that doesn\'t have in binding', () => { - const inBinding: {} = { - type: 'httpTrigger', - authLevel: 'admin' - }; - const config: FunctionConfig = new FunctionConfig({ bindings: [inBinding] }); - assert.equal(config.authLevel, HttpAuthLevel.admin); - assert.equal(config.bindings.length, 1); - assert.equal(config.disabled, false); - assert.equal(config.inBinding, inBinding); - assert.equal(config.isHttpTrigger, true); - }); -}); diff --git a/test/getResourcesPath.test.ts b/test/getResourcesPath.test.ts index f0a90e083..8cf91dec6 100644 --- a/test/getResourcesPath.test.ts +++ b/test/getResourcesPath.test.ts @@ -15,7 +15,7 @@ async function verifyLanguage(vscodeLanguage: string, fileName: string, template assert.equal(actual, path.join(templatesPath, 'resources', fileName)); } -suite('getResourcesPath Tests', async () => { +suite('getResourcesPath', async () => { let extraTemplatesPath: string; suiteSetup(async () => { extraTemplatesPath = path.join(testFolderPath, 'extraTemplates'); diff --git a/test/initProjectForVSCode.test.ts b/test/initProjectForVSCode.test.ts index 7d6dd427d..c79fce25b 100644 --- a/test/initProjectForVSCode.test.ts +++ b/test/initProjectForVSCode.test.ts @@ -13,7 +13,7 @@ import { testFolderPath } from './global.test'; import { getCSharpValidateOptions, getFSharpValidateOptions, getJavaScriptValidateOptions, getJavaValidateOptions, getPowerShellValidateOptions, getPythonValidateOptions, getTypeScriptValidateOptions, IValidateProjectOptions, validateProject } from './validateProject'; // tslint:disable-next-line:no-function-expression max-func-body-length -suite('Init Project For VS Code Tests', async function (this: ISuiteCallbackContext): Promise { +suite('Init Project For VS Code', async function (this: ISuiteCallbackContext): Promise { this.timeout(30 * 1000); const javaScriptProject: string = 'AutoDetectJavaScriptProject'; diff --git a/test/validateProject.ts b/test/validateProject.ts index 69c5a47fa..ff40bfde5 100644 --- a/test/validateProject.ts +++ b/test/validateProject.ts @@ -13,8 +13,7 @@ export function getJavaScriptValidateOptions(): IValidateProjectOptions { expectedSettings: { projectLanguage: ProjectLanguage.JavaScript, projectRuntime: ProjectRuntime.v2, - deploySubpath: '.', - preDeployTask: 'func: extensions install' + deploySubpath: '.' }, expectedPaths: [ ], @@ -135,7 +134,6 @@ export function getPythonValidateOptions(projectName: string, venvName: string): 'Attach to Python Functions' ], expectedTasks: [ - 'extensions install', 'pipInstall', 'host start' ] @@ -196,8 +194,7 @@ export function getPowerShellValidateOptions(): IValidateProjectOptions { expectedSettings: { projectLanguage: ProjectLanguage.PowerShell, projectRuntime: ProjectRuntime.v2, - deploySubpath: '.', - preDeployTask: 'func: extensions install' + deploySubpath: '.' }, expectedPaths: [ 'profile.ps1', diff --git a/test/venvUtils.test.ts b/test/venvUtils.test.ts index c00716d00..88fd01f17 100644 --- a/test/venvUtils.test.ts +++ b/test/venvUtils.test.ts @@ -11,7 +11,7 @@ import { cpUtils, ext, getGlobalSetting, Platform, pythonVenvSetting, updateGlob import { longRunningTestsEnabled, testFolderPath } from './global.test'; import { runWithSetting } from './runWithSetting'; -suite('venvUtils Tests', () => { +suite('venvUtils', () => { const command: string = 'do a thing'; const terminalSetting: string = 'terminal.integrated.shell.windows'; const venvName: string = '.env'; diff --git a/test/verifyRuntimeIsCompatible.test.ts b/test/verifyRuntimeIsCompatible.test.ts index 0d9b64ff6..8b56bd97d 100644 --- a/test/verifyRuntimeIsCompatible.test.ts +++ b/test/verifyRuntimeIsCompatible.test.ts @@ -8,7 +8,7 @@ import { TestUserInput } from 'vscode-azureextensionui'; import { ext, ProjectLanguage, ProjectRuntime, verifyRuntimeIsCompatible } from '../extension.bundle'; // tslint:disable-next-line: max-func-body-length -suite('verifyRuntimeIsCompatible Tests', () => { +suite('verifyRuntimeIsCompatible', () => { test('Local: ~1, Remote: none', async () => { const props: { [name: string]: string } = {}; await verifyRuntimeIsCompatible(ProjectRuntime.v1, ProjectLanguage.JavaScript, props);