This commit is contained in:
hossam-nasr 2024-07-16 16:41:34 -07:00
Родитель 09fa5c6987 a55fa50d6c
Коммит d179f12e41
18 изменённых файлов: 9824 добавлений и 3168 удалений

Просмотреть файл

@ -37,7 +37,7 @@ parameters:
- name: enableLongRunningTests
displayName: Enable Long Running Tests
type: boolean
default: false
default: true
extends:
template: v1/1ES.Official.PipelineTemplate.yml@1esPipelines

4
.vscode/launch.json поставляемый
Просмотреть файл

@ -74,9 +74,9 @@
"MOCHA_timeout": "0", // Disable time-outs
"DEBUGTELEMETRY": "v",
"NODE_DEBUG": "",
"ENABLE_LONG_RUNNING_TESTS": "",
"FUNC_PATH": "func",
"AZFUNC_UPDATE_BACKUP_TEMPLATES": ""
"AZFUNC_UPDATE_BACKUP_TEMPLATES": "",
"AzCode_EnableLongRunningTestsLocal": "",
}
},
{

Просмотреть файл

@ -1,6 +1,6 @@
# Change Log
## 1.15.1 - 2024-06-18
## 1.15.1 - 2024-06-19
### Changed
* [[4182]](https://github.com/microsoft/vscode-azurefunctions/pull/4182) Display a warning when attempting to deploy a containerized function app

12709
package-lock.json сгенерированный

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -2,7 +2,7 @@
"name": "vscode-azurefunctions",
"displayName": "Azure Functions",
"description": "%azureFunctions.description%",
"version": "1.15.1",
"version": "1.15.2-alpha.0",
"publisher": "ms-azuretools",
"icon": "resources/azure-functions.png",
"aiKey": "0c6ae279ed8443289764825290e4f9e2-1a736e7c-1324-4338-be46-fc2a58ae4d14-7255",
@ -1191,7 +1191,7 @@
"copy-webpack-plugin": "^6.0.3",
"eslint": "^8.42.0",
"eslint-plugin-import": "^2.27.5",
"gulp": "^4.0.2",
"gulp": "^5.0.0",
"gulp-chmod": "^2.0.0",
"gulp-decompress": "^2.0.3",
"gulp-filter": "^5.1.0",
@ -1219,7 +1219,7 @@
"@azure/core-client": "^1.7.3",
"@azure/core-rest-pipeline": "^1.11.0",
"@azure/storage-blob": "^12.5.0",
"@microsoft/vscode-azext-azureappservice": "^3.2.1",
"@microsoft/vscode-azext-azureappservice": "^3.3.0",
"@microsoft/vscode-azext-azureappsettings": "^0.2.1",
"@microsoft/vscode-azext-azureutils": "^3.0.0",
"@microsoft/vscode-azext-serviceconnector": "^0.1.3",

Двоичные данные
resources/backupTemplates/dotnet/~4/net8.0-isolated/item.nupkg Normal file

Двоичный файл не отображается.

Двоичные данные
resources/backupTemplates/dotnet/~4/net8.0-isolated/project.nupkg Normal file

Двоичный файл не отображается.

Просмотреть файл

@ -1 +1 @@
4.75.1
4.83.0

Просмотреть файл

@ -1 +1 @@
1.35.0
1.36.0

Просмотреть файл

@ -1 +1 @@
4.75.1
4.83.0

Просмотреть файл

@ -1,9 +1,7 @@
import { type Site } from "@azure/arm-appservice";
import { createHttpHeaders, createPipelineRequest } from "@azure/core-rest-pipeline";
import { createGenericClient, uiUtils, type AzExtPipelineResponse, type AzExtRequestPrepareOptions } from "@microsoft/vscode-azext-azureutils";
import { getResourceGroupFromId, uiUtils } from "@microsoft/vscode-azext-azureutils";
import { callWithTelemetryAndErrorHandling, nonNullProp, nonNullValue, nonNullValueAndProp, type IActionContext, type ISubscriptionContext } from "@microsoft/vscode-azext-utils";
import { type AppResource, type AppResourceResolver } from "@microsoft/vscode-azext-utils/hostapi";
import { type FunctionAppConfig } from "./commands/createFunctionApp/FunctionAppCreateStep";
import { ResolvedFunctionAppResource } from "./tree/ResolvedFunctionAppResource";
import { ResolvedContainerizedFunctionAppResource } from "./tree/containerizedFunctionApp/ResolvedContainerizedFunctionAppResource";
import { createWebSiteClient } from "./utils/azureClients";
@ -21,31 +19,26 @@ export class FunctionAppResolver implements AppResourceResolver {
if (this.siteCacheLastUpdated < Date.now() - 1000 * 3) {
this.siteCache.clear();
const sites = await uiUtils.listAllIterator(client.webApps.list());
const sites20231201 = await getSites20231201(context, subContext);
await Promise.all(sites.map(async (site): Promise<void> => {
const id = nonNullProp(site, 'id').toLowerCase();
// check for required properties that sometime don't exist in the LIST operation
if (!site.defaultHostName) {
// if this required property doesn't exist, try getting the full site payload
site = await client.webApps.get(nonNullProp(site, 'resourceGroup'), nonNullProp(site, 'name'))
this.siteCache.set(id, site);
}
const s = sites20231201.find(s => s.id?.toLowerCase() === site.id?.toLowerCase());
this.siteCache.set(id, Object.assign(site, { isFlex: !!s?.properties?.functionAppConfig }));
}));
this.siteCacheLastUpdated = Date.now();
for (const site of sites) {
this.siteCache.set(resource.id, site);
this.siteCacheLastUpdated = Date.now();
}
}
const site = this.siteCache.get(nonNullProp(resource, 'id').toLowerCase());
let site = this.siteCache.get(nonNullProp(resource, 'id').toLowerCase());
// check for required properties that sometime don't exist in the LIST operation
if (!site || !site.defaultHostName) {
// if this required property doesn't exist, try getting the full site payload
site = await client.webApps.get(getResourceGroupFromId(resource.id), resource.name);
this.siteCache.set(resource.id, site);
}
if (nonNullValueAndProp(site, 'kind') === 'functionapp,linux,container,azurecontainerapps') {
const fullSite = await client.webApps.get(nonNullValueAndProp(site, 'resourceGroup'), nonNullValueAndProp(site, 'name'));
return ResolvedContainerizedFunctionAppResource.createResolvedFunctionAppResource(context, subContext, fullSite);
}
return ResolvedFunctionAppResource.createResolvedFunctionAppResource(context, subContext, nonNullValue(site), site?.isFlex);
return ResolvedFunctionAppResource.createResolvedFunctionAppResource(context, subContext, nonNullValue(site));
});
}
@ -55,31 +48,3 @@ export class FunctionAppResolver implements AppResourceResolver {
&& !resource.kind?.includes('workflowapp'); // exclude logic apps
}
}
async function getSites20231201(context: IActionContext, subContext: ISubscriptionContext): Promise<(Site & { properties?: { functionAppConfig: FunctionAppConfig } })[]> {
try {
const headers = createHttpHeaders({
'Content-Type': 'application/json',
});
const armEndpoint = ensureEndingSlash(subContext.environment.resourceManagerEndpointUrl);
// we need the new api-version to get the functionAppConfig
const options: AzExtRequestPrepareOptions = {
url: `${armEndpoint}subscriptions/${subContext.subscriptionId}/providers/Microsoft.Web/sites?api-version=2023-12-01`,
method: 'GET',
headers
};
const client = await createGenericClient(context, subContext);
const result = await client.sendRequest(createPipelineRequest(options)) as AzExtPipelineResponse;
return (result.parsedBody as { value: unknown }).value as (Site & { properties?: { functionAppConfig: FunctionAppConfig } })[] ?? [];
} catch (_error) {
return [];
}
}
function ensureEndingSlash(url: string): string {
return url.endsWith('/') ? url : `${url}/`;
}

Просмотреть файл

@ -4,10 +4,9 @@
*--------------------------------------------------------------------------------------------*/
import { type NameValuePair, type Site, type SiteConfig, type WebSiteManagementClient } from '@azure/arm-appservice';
import { createHttpHeaders, createPipelineRequest, type RequestBodyType } from '@azure/core-rest-pipeline';
import { BlobServiceClient } from '@azure/storage-blob';
import { ParsedSite, WebsiteOS, type CustomLocation, type IAppServiceWizardContext } from '@microsoft/vscode-azext-azureappservice';
import { LocationListStep, createGenericClient, type AzExtPipelineResponse, type AzExtRequestPrepareOptions } from '@microsoft/vscode-azext-azureutils';
import { LocationListStep } from '@microsoft/vscode-azext-azureutils';
import { AzureWizardExecuteStep, parseError, randomUtils } from '@microsoft/vscode-azext-utils';
import { type AppResource } from '@microsoft/vscode-azext-utils/hostapi';
import { type Progress } from 'vscode';
@ -45,9 +44,7 @@ export class FunctionAppCreateStep extends AzureWizardExecuteStep<IFunctionAppWi
const siteName: string = nonNullProp(context, 'newSiteName');
const rgName: string = nonNullProp(nonNullProp(context, 'resourceGroup'), 'name');
context.site = context.newFlexSku ?
await this.createFlexFunctionApp(context, rgName, siteName, context.newFlexSku) :
await this.createFunctionApp(context, rgName, siteName, stack);
context.site = await this.createFunctionApp(context, rgName, siteName, stack);
context.activityResult = context.site as AppResource;
const site = new ParsedSite(context.site, context);
@ -103,26 +100,22 @@ export class FunctionAppCreateStep extends AzureWizardExecuteStep<IFunctionAppWi
private async getNewFlexSite(context: IFlexFunctionAppWizardContext, sku: Sku): Promise<Site> {
const location = await LocationListStep.getLocation(context, webProvider);
const site: Site & { properties: FlexFunctionAppProperties } = {
const site: Site = {
name: context.newSiteName,
kind: getSiteKind(context),
location: nonNullProp(location, 'name'),
properties: {
name: context.newSiteName,
serverFarmId: context.plan?.id,
clientAffinityEnabled: false,
siteConfig: await this.getNewSiteConfig(context)
},
serverFarmId: context.plan?.id,
clientAffinityEnabled: false,
siteConfig: await this.getNewSiteConfig(context)
};
site.properties.sku = 'FlexConsumption';
site.properties.functionAppConfig = {
site.functionAppConfig = {
deployment: {
storage: {
type: 'blobContainer',
value: `${context.storageAccount?.primaryEndpoints?.blob}app-package-${context.newSiteName?.substring(0, 32)}-${randomUtils.getRandomHexString(7)}`,
authentication: {
userAssignedIdentityResourceId: null,
userAssignedIdentityResourceId: undefined,
type: 'StorageAccountConnectionString',
storageAccountConnectionStringName: 'DEPLOYMENT_STORAGE_CONNECTION_STRING'
}
@ -136,7 +129,7 @@ export class FunctionAppCreateStep extends AzureWizardExecuteStep<IFunctionAppWi
maximumInstanceCount: context.newFlexMaximumInstanceCount ?? sku.maximumInstanceCount.defaultValue,
instanceMemoryMB: context.newFlexInstanceMemoryMB ?? sku.instanceMemoryMB.find(im => im.isDefault)?.size ?? 2048,
alwaysReady: [],
triggers: null
triggers: undefined
},
}
@ -226,39 +219,19 @@ export class FunctionAppCreateStep extends AzureWizardExecuteStep<IFunctionAppWi
return newSiteConfig;
}
async createFunctionApp(context: IFunctionAppWizardContext, rgName: string, siteName: string, stack: FullFunctionAppStack): Promise<Site> {
async createFunctionApp(context: IFlexFunctionAppWizardContext, rgName: string, siteName: string, stack: FullFunctionAppStack): Promise<Site> {
const client: WebSiteManagementClient = await createWebSiteClient(context);
return await client.webApps.beginCreateOrUpdateAndWait(rgName, siteName, await this.getNewSite(context, stack));
}
const site = context.newFlexSku ?
await this.getNewFlexSite(context, context.newFlexSku) :
await this.getNewSite(context, stack);
const result = await client.webApps.beginCreateOrUpdateAndWait(rgName, siteName, site);
async createFlexFunctionApp(context: IFunctionAppWizardContext, rgName: string, siteName: string, sku: Sku): Promise<Site> {
const headers = createHttpHeaders({
'Content-Type': 'application/json',
});
const options: AzExtRequestPrepareOptions = {
url: `https://management.azure.com/subscriptions/${context.subscriptionId}/resourceGroups/${rgName}/providers/Microsoft.Web/sites/${siteName}?api-version=2023-12-01`,
method: 'PUT',
body: JSON.stringify(await this.getNewFlexSite(context, sku)) as unknown as RequestBodyType,
headers
};
const client = await createGenericClient(context, context);
const result = await client.sendRequest(createPipelineRequest(options)) as AzExtPipelineResponse;
if (result && result.status >= 200 && result.status < 300) {
const site = result.parsedBody as Site & { properties: FlexFunctionAppProperties };
if (context.newFlexSku) {
const storageConnectionString: string = (await getStorageConnectionString(context)).connectionString;
await tryCreateStorageContainer(site, storageConnectionString);
const client: WebSiteManagementClient = await createWebSiteClient(context);
// the payload for the new API version "2023-12-01" is incompatiable with our current SiteClient so get the old payload
try {
return await client.webApps.get(rgName, siteName);
} catch (_) {
// ignore error and fall thru to throw
}
await tryCreateStorageContainer(result, storageConnectionString);
}
throw new Error(parseError(result.parsedBody).message || localize('failedToCreateFlexFunctionApp', 'Failed to create flex function app "{0}".', siteName));
return result;
}
}
@ -280,49 +253,22 @@ function getSiteKind(context: IAppServiceWizardContext): string {
}
// storage container is needed for flex deployment, but it is not created automatically
async function tryCreateStorageContainer(site: Site & { properties: FlexFunctionAppProperties }, storageConnectionString: string): Promise<void> {
async function tryCreateStorageContainer(site: Site, storageConnectionString: string): Promise<void> {
const blobClient = BlobServiceClient.fromConnectionString(storageConnectionString);
const containerName = site.properties?.functionAppConfig?.deployment.storage.value.split('/').pop();
if (containerName) {
const client = blobClient.getContainerClient(containerName);
if (!await client.exists()) {
await blobClient.createContainer(containerName);
const containerUrl: string | undefined = site.functionAppConfig?.deployment?.storage?.value;
if (containerUrl) {
const containerName = containerUrl.split('/').pop();
if (containerName) {
const client = blobClient.getContainerClient(containerName);
if (!await client.exists()) {
await blobClient.createContainer(containerName);
} else {
ext.outputChannel.appendLog(localize('deploymentStorageExists', 'Deployment storage container "{0}" already exists.', containerName));
return;
}
}
}
ext.outputChannel.appendLog(localize('noDeploymentStorage', 'No deployment storage specified in function app.'));
return;
}
type FlexFunctionAppProperties = {
containerSize?: number,
sku?: 'FlexConsumption',
name?: string,
serverFarmId?: string,
clientAffinityEnabled?: boolean,
siteConfig: SiteConfig,
reserved?: boolean,
functionAppConfig?: FunctionAppConfig
};
// TODO: Temporary until we can get the SDK updated
export type FunctionAppConfig = {
deployment: {
storage: {
type: string;
value: string;
authentication: {
type: string;
userAssignedIdentityResourceId: string | null;
storageAccountConnectionStringName: string | null;
};
}
},
runtime: {
name?: string,
version?: string
},
scaleAndConcurrency: {
alwaysReady: number[],
maximumInstanceCount: number,
instanceMemoryMB: number,
triggers: null
}
};

Просмотреть файл

@ -63,12 +63,12 @@ export class ResolvedFunctionAppResource extends ResolvedFunctionAppBase impleme
tooltip?: string | undefined;
commandArgs?: unknown[] | undefined;
public constructor(subscription: ISubscriptionContext, site: Site, isFlex?: boolean) {
public constructor(subscription: ISubscriptionContext, site: Site) {
super(new ParsedSite(site, subscription))
this.data = this.site.rawSite;
this._subscription = subscription;
this.contextValuesToAdd = [];
this._isFlex = !!isFlex;
this._isFlex = !!site.functionAppConfig;
if (this._isFlex) {
this.contextValuesToAdd.push(ResolvedFunctionAppResource.flexContextValue);
} else if (this.site.isSlot) {
@ -91,8 +91,8 @@ export class ResolvedFunctionAppResource extends ResolvedFunctionAppBase impleme
}
}
public static createResolvedFunctionAppResource(context: IActionContext, subscription: ISubscriptionContext, site: Site, isFlex?: boolean): ResolvedFunctionAppResource {
const resource = new ResolvedFunctionAppResource(subscription, site, isFlex);
public static createResolvedFunctionAppResource(context: IActionContext, subscription: ISubscriptionContext, site: Site): ResolvedFunctionAppResource {
const resource = new ResolvedFunctionAppResource(subscription, site);
void resource.site.createClient(context).then(async (client) => resource.data.siteConfig = await client.getSiteConfig())
return resource;
}

Просмотреть файл

@ -7,11 +7,11 @@ import { runWithInputs } from '@microsoft/vscode-azext-dev';
import { type apiUtils } from "@microsoft/vscode-azext-utils";
import * as path from 'path';
import { extensions, type Extension } from "vscode";
import { ProjectLanguage, extensionId, nonNullValue, registerOnActionStartHandler } from '../extension.bundle';
import { FuncVersion, ProjectLanguage, extensionId, nonNullValue, registerOnActionStartHandler } from '../extension.bundle';
// eslint-disable-next-line no-restricted-imports
import { type AzureFunctionsExtensionApi } from '../src/vscode-azurefunctions.api';
import { getTestWorkspaceFolder, testFolderPath } from './global.test';
import { getJavaScriptValidateOptions, validateProject, type IValidateProjectOptions } from './project/validateProject';
import { getCSharpValidateOptions, getJavaScriptValidateOptions, validateProject, type IValidateProjectOptions } from './project/validateProject';
suite(`AzureFunctionsExtensionApi`, () => {
let api: AzureFunctionsExtensionApi;
@ -73,4 +73,49 @@ suite(`AzureFunctionsExtensionApi`, () => {
);
await validateProject(folderPath, validateOptions);
});
test('createFunction dotnet with targetFramework', async () => {
const functionName: string = 'endpoint1';
const language: string = ProjectLanguage.CSharp;
const workspaceFolder = getTestWorkspaceFolder();
const projectSubpath = 'api';
const folderPath: string = path.join(workspaceFolder, projectSubpath);
await runWithInputs('api.createFunction', [language, /6/i, 'Company.Function', 'Anonymous'], registerOnActionStartHandler, async () => {
await api.createFunction({
folderPath,
functionName,
templateId: 'HttpTrigger',
languageFilter: /^C\#$/i,
functionSettings: { authLevel: 'anonymous' },
targetFramework: ['net8.0', 'net7.0', 'net6.0']
});
});
const validateOptions: IValidateProjectOptions = getCSharpValidateOptions('net6.0', FuncVersion.v4, 1, projectSubpath, workspaceFolder);
// Exclude .git because the test workspace folders are already inside a git repo so we don't do git init.
validateOptions.excludedPaths?.push('.git');
await validateProject(folderPath, validateOptions);
});
// Intentionally pass a version (8) that hasn't been specified in targetFramework (6 & 7) to verify it isn't a possible pick. In the correct case (when 8 isn't a pick) we throw an error. api.createFunction swallows the error and returns undefined.
// In the incorrect case (when 8 is a pick) the test fails since the 2 provided test inputs have already been used, but there are more prompts.
test('createFunction with language not in targetFramework', async () => {
const functionName: string = 'endpoint1';
const language: string = ProjectLanguage.CSharp;
const workspaceFolder = getTestWorkspaceFolder();
const projectSubpath = 'api';
const folderPath: string = path.join(workspaceFolder, projectSubpath);
await runWithInputs('api.createFunction', [language, /8/i], registerOnActionStartHandler, async () => {
await api.createFunction({
folderPath,
functionName,
templateId: 'HttpTrigger',
languageFilter: /^C\#$/i,
functionSettings: { authLevel: 'anonymous' },
targetFramework: ['net7.0', 'net6.0']
})
});
});
});

Просмотреть файл

@ -16,7 +16,10 @@ import { CentralTemplateProvider, deploySubpathSetting, envUtils, ext, FuncVersi
*/
export const testFolderPath: string = path.join(os.tmpdir(), `azFuncTest${getRandomHexString()}`);
export const longRunningTestsEnabled: boolean = envUtils.isEnvironmentVariableSet(process.env.AzCode_UseAzureFederatedCredentials);
const longRunningLocalTestsEnabled: boolean = envUtils.isEnvironmentVariableSet(process.env.AzCode_EnableLongRunningTestsLocal);
const longRunningRemoteTestsEnabled: boolean = envUtils.isEnvironmentVariableSet(process.env.AzCode_UseAzureFederatedCredentials);
export const longRunningTestsEnabled: boolean = longRunningLocalTestsEnabled || longRunningRemoteTestsEnabled;
export const updateBackupTemplates: boolean = envUtils.isEnvironmentVariableSet(process.env.AZFUNC_UPDATE_BACKUP_TEMPLATES);
export const skipStagingTemplateSource: boolean = envUtils.isEnvironmentVariableSet(process.env.SKIP_STAGING_TEMPLATE_SOURCE);

Просмотреть файл

@ -27,10 +27,11 @@ const testCases: CreateProjectAndDeployTestCase[] = [
{ title: 'JavaScript (Model V4)', ...getJavaScriptValidateOptions(true, undefined, undefined, undefined, NodeModelVersion.v4), createProjectInputs: [/Model V4/], deployInputs: [getRotatingNodeVersion()], languageModelVersion: NodeModelVersion.v4 },
{ title: 'TypeScript (Model V3)', ...getTypeScriptValidateOptions(), createProjectInputs: [/Model V3/], deployInputs: [getRotatingNodeVersion()], languageModelVersion: NodeModelVersion.v3 },
{ title: 'TypeScript (Model V4)', ...getTypeScriptValidateOptions({ modelVersion: NodeModelVersion.v4 }), createProjectInputs: [/Model V4/], deployInputs: [getRotatingNodeVersion()], languageModelVersion: NodeModelVersion.v4 },
// Disable Ballerina tests until we figure out how to install Ballerina on the new pipelines
// Temporarily disable Ballerina tests until we can install Ballerina on the new pipelines
// https://github.com/microsoft/vscode-azurefunctions/issues/4210
// { title: 'Ballerina', ...getBallerinaValidateOptions(), createProjectInputs: ["JVM"], deployInputs: [/java.*11/i] },
// All C# tests on mac and .NET 6 on windows are consistently timing out for some unknown reason. Will skip for now
// { title: 'C# .NET Core 3.1', buildMachineOsToSkip: 'darwin', ...getCSharpValidateOptions('netcoreapp3.1'), createProjectInputs: [/net.*3/i], deployInputs: [/net.*3/i], createFunctionInputs: ['Company.Function'] },
{ title: 'C# .NET Framework', buildMachineOsToSkip: 'darwin', ...getCSharpValidateOptions('net48'), createProjectInputs: [/net.*Framework/i], deployInputs: [/net.*Framework/i], createFunctionInputs: ['Company.Function'] },
{ title: 'C# .NET 8', ...getCSharpValidateOptions('net8.0', FuncVersion.v4), createProjectInputs: [/net.*8/i], deployInputs: [/net.*8/i], createFunctionInputs: ['Company.Function'] },
{ title: 'PowerShell', ...getPowerShellValidateOptions(), deployInputs: [/powershell.*7.4/i] },
{ title: 'Python (Model V1)', ...getPythonValidateOptions('.venv'), createProjectInputs: [/Model V1/, /3\.9/], deployInputs: [getRotatingPythonVersion()], languageModelVersion: PythonModelVersion.v1 },
@ -128,7 +129,6 @@ async function addRoutePrefixToProject(testWorkspacePath: string, routePrefix: s
}
async function validateFunctionUrl(appName: string, functionName: string, routePrefix: string): Promise<void> {
// first input matches any item except local project (aka it should match the test subscription)
const inputs: (string | RegExp)[] = [appName, functionName];
let functionUrl: string | undefined;
@ -145,6 +145,7 @@ async function validateFunctionUrl(appName: string, functionName: string, routeP
assert.ok(functionUrl?.includes(routePrefix), `Function url "${functionUrl}" did not include routePrefix "${routePrefix}".`);
const client: ServiceClient = await createGenericClient(await createTestActionContext(), undefined);
// PowerShell functions expect Name capitalized, so we set both
const response = await client.sendRequest(createPipelineRequest({ method: 'POST', url: functionUrl!, body: JSON.stringify({ name: "World", Name: "World" }) }));
const body: string = nonNullProp(response, 'bodyAsText');
assert.ok((body.includes('Hello') && body.includes('World')) || body.includes('Welcome'), 'Expected function response to include "Hello World" or "Welcome"');

Просмотреть файл

@ -12,8 +12,8 @@ import { runWithFuncSetting } from '../runWithSetting';
import { createAndValidateProject } from './createAndValidateProject';
import { PythonModelVersion, getPythonValidateOptions } from './validateProject';
const modelV1Input = /Model V1/;
const modelV2Input = /Model V2/;
const modelV1Input: RegExp = /Model V1/;
const modelV2Input: RegExp = /Model V2/;
suite('Create New Python Project (Model V1)', () => {
test('skip venv', async () => {

Просмотреть файл

@ -98,7 +98,7 @@ export function getTypeScriptValidateOptions(options?: { version?: FuncVersion,
return result;
}
export function getCSharpValidateOptions(targetFramework: string, version: FuncVersion = defaultTestFuncVersion, numCsproj: number = 1): IValidateProjectOptions {
export function getCSharpValidateOptions(targetFramework: string, version: FuncVersion = defaultTestFuncVersion, numCsproj: number = 1, projectSubpath?: string, workspaceFolder?: string): IValidateProjectOptions {
return {
language: ProjectLanguage.CSharp,
version,
@ -106,17 +106,17 @@ export function getCSharpValidateOptions(targetFramework: string, version: FuncV
'azureFunctions.projectLanguage': ProjectLanguage.CSharp,
'azureFunctions.projectRuntime': version,
'azureFunctions.preDeployTask': 'publish (functions)',
'azureFunctions.deploySubpath': `bin/Release/${targetFramework}/publish`,
'azureFunctions.deploySubpath': `${projectSubpath ? `${projectSubpath}/` : ''}bin/Release/${targetFramework}/publish`,
'debug.internalConsoleOptions': 'neverOpen',
},
expectedPaths: [
{ globPattern: '*.csproj', numMatches: numCsproj }
{ globPattern: `${projectSubpath ? `${projectSubpath}/` : ''}*.csproj`, numMatches: numCsproj }
],
expectedExtensionRecs: [
'ms-dotnettools.csharp'
],
excludedPaths: [
'.funcignore'
path.join(projectSubpath ?? '', '.funcignore')
],
expectedDebugConfigs: [
'Attach to .NET Functions'
@ -127,7 +127,8 @@ export function getCSharpValidateOptions(targetFramework: string, version: FuncV
'clean release (functions)',
'publish (functions)',
'host start'
]
],
workspaceFolder
};
}