mirror of
https://github.com/ckaczor/sqltoolsservice.git
synced 2026-01-14 01:25:40 -05:00
Insert sql bindings into Azure Functions (#1224)
* getting table name from a script * add InsertSqlInputBindingOperation * cleanup * move azure functions stuff out of dacfx service * cleanup * add tests * add another test * cleanup * add comments and connection string setting * addressing comments * change name to use add instead of insert
This commit is contained in:
@@ -26,6 +26,9 @@
|
||||
<PackageReference Update="Microsoft.SqlServer.Assessment" Version="[1.0.305]" />
|
||||
<PackageReference Update="Microsoft.SqlServer.Migration.Assessment" Version="1.0.20210714.5" />
|
||||
<PackageReference Update="Microsoft.Azure.OperationalInsights" Version="1.0.0" />
|
||||
<PackageReference Update="Microsoft.CodeAnalysis.CSharp" Version="3.10.0" />
|
||||
<PackageReference Update="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="3.10.0" />
|
||||
<PackageReference Update="Microsoft.CodeAnalysis.Workspaces.Common" Version="3.10.0" />
|
||||
|
||||
<PackageReference Update="Moq" Version="4.8.2" />
|
||||
<PackageReference Update="nunit" Version="3.12.0" />
|
||||
|
||||
@@ -0,0 +1,143 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using Microsoft.SqlTools.ServiceLayer.AzureFunctions.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.Utility;
|
||||
using Microsoft.SqlTools.Utility;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.AzureFunctions
|
||||
{
|
||||
/// <summary>
|
||||
/// Class to represent inserting a sql binding into an Azure Function
|
||||
/// </summary>
|
||||
class AddSqlBindingOperation
|
||||
{
|
||||
const string functionAttributeText = "FunctionName";
|
||||
|
||||
public AddSqlBindingParams Parameters { get; }
|
||||
|
||||
public AddSqlBindingOperation(AddSqlBindingParams parameters)
|
||||
{
|
||||
Validate.IsNotNull("parameters", parameters);
|
||||
this.Parameters = parameters;
|
||||
}
|
||||
|
||||
public ResultStatus AddBinding()
|
||||
{
|
||||
try
|
||||
{
|
||||
string text = File.ReadAllText(Parameters.filePath);
|
||||
|
||||
SyntaxTree tree = CSharpSyntaxTree.ParseText(text);
|
||||
CompilationUnitSyntax root = tree.GetCompilationUnitRoot();
|
||||
|
||||
// look for Azure Function to update
|
||||
IEnumerable<MethodDeclarationSyntax> azureFunctionMethods = from methodDeclaration in root.DescendantNodes().OfType<MethodDeclarationSyntax>()
|
||||
where methodDeclaration.AttributeLists.Count > 0
|
||||
where methodDeclaration.AttributeLists.Where(a => a.Attributes.Where(attr => attr.Name.ToString().Contains(functionAttributeText) && attr.ArgumentList.Arguments.First().ToString().Equals($"\"{Parameters.functionName}\"")).Count() == 1).Count() == 1
|
||||
select methodDeclaration;
|
||||
|
||||
if (azureFunctionMethods.Count() == 0)
|
||||
{
|
||||
return new ResultStatus()
|
||||
{
|
||||
Success = false,
|
||||
ErrorMessage = SR.CouldntFindAzureFunction(Parameters.functionName, Parameters.filePath)
|
||||
};
|
||||
}
|
||||
else if (azureFunctionMethods.Count() > 1)
|
||||
{
|
||||
return new ResultStatus()
|
||||
{
|
||||
Success = false,
|
||||
ErrorMessage = SR.MoreThanOneAzureFunctionWithName(Parameters.functionName, Parameters.filePath)
|
||||
};
|
||||
}
|
||||
|
||||
MethodDeclarationSyntax azureFunction = azureFunctionMethods.First();
|
||||
var newParam = this.Parameters.bindingType == BindingType.input ? this.GenerateInputBinding() : this.GenerateOutputBinding();
|
||||
|
||||
// Generate updated method with the new parameter
|
||||
// normalizewhitespace gets rid of any newline whitespace in the leading trivia, so we add that back
|
||||
var updatedMethod = azureFunction.AddParameterListParameters(newParam).NormalizeWhitespace().WithLeadingTrivia(azureFunction.GetLeadingTrivia()).WithTrailingTrivia(azureFunction.GetTrailingTrivia());
|
||||
|
||||
// Replace the node in the tree
|
||||
root = root.ReplaceNode(azureFunction, updatedMethod);
|
||||
|
||||
// write updated tree to file
|
||||
var workspace = new AdhocWorkspace();
|
||||
var syntaxTree = CSharpSyntaxTree.ParseText(root.ToString());
|
||||
var formattedNode = CodeAnalysis.Formatting.Formatter.Format(syntaxTree.GetRoot(), workspace);
|
||||
StringBuilder sb = new StringBuilder(formattedNode.ToString());
|
||||
string content = sb.ToString();
|
||||
File.WriteAllText(Parameters.filePath, content);
|
||||
|
||||
return new ResultStatus()
|
||||
{
|
||||
Success = true
|
||||
};
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return new ResultStatus()
|
||||
{
|
||||
Success = false,
|
||||
ErrorMessage = e.ToString()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a parameter for the sql input binding that looks like
|
||||
/// [Sql("select * from [dbo].[table1]", CommandType = System.Data.CommandType.Text, ConnectionStringSetting = "SqlConnectionString")] IEnumerable<Object> result
|
||||
/// </summary>
|
||||
private ParameterSyntax GenerateInputBinding()
|
||||
{
|
||||
// Create arguments for the Sql Input Binding attribute
|
||||
var argumentList = SyntaxFactory.AttributeArgumentList();
|
||||
argumentList = argumentList.AddArguments(SyntaxFactory.AttributeArgument(SyntaxFactory.IdentifierName($"\"select * from {Parameters.objectName}\"")));
|
||||
argumentList = argumentList.AddArguments(SyntaxFactory.AttributeArgument(SyntaxFactory.AssignmentExpression(SyntaxKind.SimpleAssignmentExpression, SyntaxFactory.IdentifierName("CommandType"), SyntaxFactory.IdentifierName("System.Data.CommandType.Text"))));
|
||||
argumentList = argumentList.AddArguments(SyntaxFactory.AttributeArgument(SyntaxFactory.AssignmentExpression(SyntaxKind.SimpleAssignmentExpression, SyntaxFactory.IdentifierName("ConnectionStringSetting"), SyntaxFactory.IdentifierName($"\"{Parameters.connectionStringSetting}\""))));
|
||||
|
||||
// Create Sql Binding attribute
|
||||
SyntaxList<AttributeListSyntax> attributesList = new SyntaxList<AttributeListSyntax>();
|
||||
attributesList = attributesList.Add(SyntaxFactory.AttributeList(SyntaxFactory.SingletonSeparatedList<AttributeSyntax>(SyntaxFactory.Attribute(SyntaxFactory.IdentifierName("Sql")).WithArgumentList(argumentList))));
|
||||
|
||||
// Create new parameter
|
||||
ParameterSyntax newParam = SyntaxFactory.Parameter(attributesList, new SyntaxTokenList(), SyntaxFactory.ParseTypeName("IEnumerable<Object>"), SyntaxFactory.Identifier("result"), null);
|
||||
return newParam;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a parameter for the sql output binding that looks like
|
||||
/// [Sql("[dbo].[table1]", ConnectionStringSetting = "SqlConnectionString")] out Object output
|
||||
/// </summary>
|
||||
private ParameterSyntax GenerateOutputBinding()
|
||||
{
|
||||
// Create arguments for the Sql Output Binding attribute
|
||||
var argumentList = SyntaxFactory.AttributeArgumentList();
|
||||
argumentList = argumentList.AddArguments(SyntaxFactory.AttributeArgument(SyntaxFactory.IdentifierName($"\"{Parameters.objectName}\"")));
|
||||
argumentList = argumentList.AddArguments(SyntaxFactory.AttributeArgument(SyntaxFactory.AssignmentExpression(SyntaxKind.SimpleAssignmentExpression, SyntaxFactory.IdentifierName("ConnectionStringSetting"), SyntaxFactory.IdentifierName($"\"{Parameters.connectionStringSetting}\""))));
|
||||
|
||||
SyntaxList<AttributeListSyntax> attributesList = new SyntaxList<AttributeListSyntax>();
|
||||
attributesList = attributesList.Add(SyntaxFactory.AttributeList(SyntaxFactory.SingletonSeparatedList<AttributeSyntax>(SyntaxFactory.Attribute(SyntaxFactory.IdentifierName("Sql")).WithArgumentList(argumentList))));
|
||||
|
||||
var syntaxTokenList = new SyntaxTokenList();
|
||||
syntaxTokenList = syntaxTokenList.Add(SyntaxFactory.Token(SyntaxKind.OutKeyword));
|
||||
|
||||
ParameterSyntax newParam = SyntaxFactory.Parameter(attributesList, syntaxTokenList, SyntaxFactory.ParseTypeName(typeof(Object).Name), SyntaxFactory.Identifier("output"), null);
|
||||
return newParam;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.SqlTools.Hosting.Protocol;
|
||||
using Microsoft.SqlTools.ServiceLayer.AzureFunctions.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.Hosting;
|
||||
using Microsoft.SqlTools.ServiceLayer.Utility;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.AzureFunctions
|
||||
{
|
||||
/// <summary>
|
||||
/// Main class for Azure Functions service
|
||||
/// </summary>
|
||||
class AzureFunctionsService
|
||||
{
|
||||
private static readonly Lazy<AzureFunctionsService> instance = new Lazy<AzureFunctionsService>(() => new AzureFunctionsService());
|
||||
|
||||
/// <summary>
|
||||
/// Gets the singleton instance object
|
||||
/// </summary>
|
||||
public static AzureFunctionsService Instance
|
||||
{
|
||||
get { return instance.Value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the service instance
|
||||
/// </summary>
|
||||
/// <param name="serviceHost"></param>
|
||||
public void InitializeService(ServiceHost serviceHost)
|
||||
{
|
||||
serviceHost.SetRequestHandler(AddSqlBindingRequest.Type, this.HandleAddSqlBindingRequest);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles request to add sql binding into Azure Functions
|
||||
/// </summary>
|
||||
public async Task HandleAddSqlBindingRequest(AddSqlBindingParams parameters, RequestContext<ResultStatus> requestContext)
|
||||
{
|
||||
try
|
||||
{
|
||||
AddSqlBindingOperation operation = new AddSqlBindingOperation(parameters);
|
||||
ResultStatus result = operation.AddBinding();
|
||||
|
||||
await requestContext.SendResult(result);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
await requestContext.SendError(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.SqlTools.Hosting.Protocol.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.SchemaCompare.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.Utility;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.AzureFunctions.Contracts
|
||||
{
|
||||
/// <summary>
|
||||
/// Binding types for sql bindings for Azure Functions
|
||||
/// </summary>
|
||||
public enum BindingType
|
||||
{
|
||||
input,
|
||||
output
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parameters for adding a sql binding
|
||||
/// </summary>
|
||||
public class AddSqlBindingParams
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the filePath
|
||||
/// </summary>
|
||||
public string filePath { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the binding type
|
||||
/// </summary>
|
||||
public BindingType bindingType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the function name
|
||||
/// </summary>
|
||||
public string functionName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the object name
|
||||
/// </summary>
|
||||
public string objectName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the connection string setting
|
||||
/// </summary>
|
||||
public string connectionStringSetting { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines the Add Sql Binding request
|
||||
/// </summary>
|
||||
class AddSqlBindingRequest
|
||||
{
|
||||
public static readonly RequestType<AddSqlBindingParams, ResultStatus> Type =
|
||||
RequestType<AddSqlBindingParams, ResultStatus>.Create("azureFunctions/sqlBinding");
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ using Microsoft.SqlTools.Hosting;
|
||||
using Microsoft.SqlTools.Hosting.Protocol;
|
||||
using Microsoft.SqlTools.ServiceLayer.Admin;
|
||||
using Microsoft.SqlTools.ServiceLayer.Agent;
|
||||
using Microsoft.SqlTools.ServiceLayer.AzureFunctions;
|
||||
using Microsoft.SqlTools.ServiceLayer.Cms;
|
||||
using Microsoft.SqlTools.ServiceLayer.Connection;
|
||||
using Microsoft.SqlTools.ServiceLayer.DacFx;
|
||||
@@ -133,6 +134,9 @@ namespace Microsoft.SqlTools.ServiceLayer
|
||||
SchemaCompare.SchemaCompareService.Instance.InitializeService(serviceHost);
|
||||
serviceProvider.RegisterSingleService(SchemaCompareService.Instance);
|
||||
|
||||
AzureFunctions.AzureFunctionsService.Instance.InitializeService(serviceHost);
|
||||
serviceProvider.RegisterSingleService(AzureFunctionsService.Instance);
|
||||
|
||||
ServerConfigService.Instance.InitializeService(serviceHost);
|
||||
serviceProvider.RegisterSingleService(ServerConfigService.Instance);
|
||||
|
||||
|
||||
@@ -3278,6 +3278,16 @@ namespace Microsoft.SqlTools.ServiceLayer
|
||||
return Keys.GetString(Keys.SqlAssessmentUnsuppoertedEdition, editionCode);
|
||||
}
|
||||
|
||||
public static string CouldntFindAzureFunction(string functionName, string fileName)
|
||||
{
|
||||
return Keys.GetString(Keys.CouldntFindAzureFunction, functionName, fileName);
|
||||
}
|
||||
|
||||
public static string MoreThanOneAzureFunctionWithName(string functionName, string fileName)
|
||||
{
|
||||
return Keys.GetString(Keys.MoreThanOneAzureFunctionWithName, functionName, fileName);
|
||||
}
|
||||
|
||||
[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
public class Keys
|
||||
{
|
||||
@@ -4555,6 +4565,12 @@ namespace Microsoft.SqlTools.ServiceLayer
|
||||
public const string SqlAssessmentUnsuppoertedEdition = "SqlAssessmentUnsuppoertedEdition";
|
||||
|
||||
|
||||
public const string CouldntFindAzureFunction = "CouldntFindAzureFunction";
|
||||
|
||||
|
||||
public const string MoreThanOneAzureFunctionWithName = "MoreThanOneAzureFunctionWithName";
|
||||
|
||||
|
||||
private Keys()
|
||||
{ }
|
||||
|
||||
|
||||
@@ -1854,4 +1854,14 @@
|
||||
<comment>.
|
||||
Parameters: 0 - editionCode (int) </comment>
|
||||
</data>
|
||||
<data name="CouldntFindAzureFunction" xml:space="preserve">
|
||||
<value>Couldn't find Azure function with FunctionName {0} in {1}</value>
|
||||
<comment>.
|
||||
Parameters: 0 - functionName (string), 1 - fileName (string) </comment>
|
||||
</data>
|
||||
<data name="MoreThanOneAzureFunctionWithName" xml:space="preserve">
|
||||
<value>More than one Azure function found with the FunctionName {0} in {1}</value>
|
||||
<comment>.
|
||||
Parameters: 0 - functionName (string), 1 - fileName (string) </comment>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
@@ -848,4 +848,9 @@ SchemaCompareSessionNotFound = Could not find the schema compare session to canc
|
||||
SqlAssessmentGenerateScriptTaskName = Generate SQL Assessment script
|
||||
SqlAssessmentQueryInvalidOwnerUri = Not connected to a server
|
||||
SqlAssessmentConnectingError = Cannot connect to the server
|
||||
SqlAssessmentUnsuppoertedEdition(int editionCode) = Unsupported engine edition {0}
|
||||
SqlAssessmentUnsuppoertedEdition(int editionCode) = Unsupported engine edition {0}
|
||||
|
||||
############################################################################
|
||||
# Azure Functions
|
||||
CouldntFindAzureFunction(string functionName, string fileName) = Couldn't find Azure function with FunctionName '{0}' in {1}
|
||||
MoreThanOneAzureFunctionWithName(string functionName, string fileName) = More than one Azure function found with the FunctionName '{0}' in {1}
|
||||
@@ -2161,6 +2161,16 @@
|
||||
<target state="new">No External Streaming Job creation TSQL found (EXEC sp_create_streaming_job statement).</target>
|
||||
<note></note>
|
||||
</trans-unit>
|
||||
<trans-unit id="CouldntFindAzureFunction">
|
||||
<source>Couldn't find Azure function with FunctionName {0} in {1}</source>
|
||||
<target state="new">Couldn't find Azure function with FunctionName {0} in {1}</target>
|
||||
<note></note>
|
||||
</trans-unit>
|
||||
<trans-unit id="MoreThanOneAzureFunctionWithName">
|
||||
<source>More than one Azure function found with the FunctionName {0} in {1}</source>
|
||||
<target state="new">More than one Azure function found with the FunctionName {0} in {1}</target>
|
||||
<note></note>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
</xliff>
|
||||
@@ -21,6 +21,9 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.Workspaces.Common" />
|
||||
<PackageReference Include="Microsoft.SqlServer.DACFx" />
|
||||
<PackageReference Include="Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider" />
|
||||
<PackageReference Include="System.Text.Encoding.CodePages" />
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Company.Namespace.Models;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Azure.WebJobs;
|
||||
using Microsoft.Azure.WebJobs.Extensions.Http;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Company.Namespace
|
||||
{
|
||||
public class ArtistsApi
|
||||
{
|
||||
private ILogger<ArtistsApi> _logger;
|
||||
|
||||
/// <summary> Initializes a new instance of ArtistsApi. </summary>
|
||||
/// <param name="logger"> Class logger. </param>
|
||||
/// <exception cref="ArgumentNullException"> <paramref name="logger"/> is null. </exception>
|
||||
public ArtistsApi(ILogger<ArtistsApi> logger)
|
||||
{
|
||||
if (logger == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <summary> Returns a list of artists. </summary>
|
||||
/// <param name="req"> Raw HTTP Request. </param>
|
||||
/// <param name="cancellationToken"> The cancellation token provided on Function shutdown. </param>
|
||||
[FunctionName("GetArtists_get")]
|
||||
public IActionResult GetArtists([HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "artists")] HttpRequest req, [Sql("select * from [dbo].[table1]", CommandType = System.Data.CommandType.Text, ConnectionStringSetting = "SqlConnectionString")] IEnumerable<Object> result)
|
||||
{
|
||||
_logger.LogInformation("HTTP trigger function processed a request.");
|
||||
// TODO: Handle Documented Responses.
|
||||
// Spec Defines: HTTP 200
|
||||
// Spec Defines: HTTP 400
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <summary> Lets a user post a new artist. </summary>
|
||||
/// <param name="body"> The Artist to use. </param>
|
||||
/// <param name="req"> Raw HTTP Request. </param>
|
||||
/// <param name="cancellationToken"> The cancellation token provided on Function shutdown. </param>
|
||||
/// <exception cref="ArgumentNullException"> <paramref name="body"/> is null. </exception>
|
||||
[FunctionName("NewArtist_post")]
|
||||
public IActionResult NewArtist([HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "artists")] Artist body, HttpRequest req)
|
||||
{
|
||||
_logger.LogInformation("HTTP trigger function processed a request.");
|
||||
// TODO: Handle Documented Responses.
|
||||
// Spec Defines: HTTP 200
|
||||
// Spec Defines: HTTP 400
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Company.Namespace.Models;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Azure.WebJobs;
|
||||
using Microsoft.Azure.WebJobs.Extensions.Http;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Company.Namespace
|
||||
{
|
||||
public class ArtistsApi
|
||||
{
|
||||
private ILogger<ArtistsApi> _logger;
|
||||
|
||||
/// <summary> Initializes a new instance of ArtistsApi. </summary>
|
||||
/// <param name="logger"> Class logger. </param>
|
||||
/// <exception cref="ArgumentNullException"> <paramref name="logger"/> is null. </exception>
|
||||
public ArtistsApi(ILogger<ArtistsApi> logger)
|
||||
{
|
||||
if (logger == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <summary> Returns a list of artists. </summary>
|
||||
/// <param name="req"> Raw HTTP Request. </param>
|
||||
/// <param name="cancellationToken"> The cancellation token provided on Function shutdown. </param>
|
||||
[FunctionName("GetArtists_get")]
|
||||
public IActionResult GetArtists([HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "artists")] HttpRequest req)
|
||||
{
|
||||
_logger.LogInformation("HTTP trigger function processed a request.");
|
||||
// TODO: Handle Documented Responses.
|
||||
// Spec Defines: HTTP 200
|
||||
// Spec Defines: HTTP 400
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <summary> Lets a user post a new artist. </summary>
|
||||
/// <param name="body"> The Artist to use. </param>
|
||||
/// <param name="req"> Raw HTTP Request. </param>
|
||||
/// <param name="cancellationToken"> The cancellation token provided on Function shutdown. </param>
|
||||
/// <exception cref="ArgumentNullException"> <paramref name="body"/> is null. </exception>
|
||||
[FunctionName("GetArtists_get")]
|
||||
public IActionResult NewArtist([HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "artists")] Artist body, HttpRequest req)
|
||||
{
|
||||
_logger.LogInformation("HTTP trigger function processed a request.");
|
||||
// TODO: Handle Documented Responses.
|
||||
// Spec Defines: HTTP 200
|
||||
// Spec Defines: HTTP 400
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Company.Namespace.Models;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Azure.WebJobs;
|
||||
using Microsoft.Azure.WebJobs.Extensions.Http;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Company.Namespace
|
||||
{
|
||||
public class ArtistsApi
|
||||
{
|
||||
private ILogger<ArtistsApi> _logger;
|
||||
|
||||
/// <summary> Initializes a new instance of ArtistsApi. </summary>
|
||||
/// <param name="logger"> Class logger. </param>
|
||||
/// <exception cref="ArgumentNullException"> <paramref name="logger"/> is null. </exception>
|
||||
public ArtistsApi(ILogger<ArtistsApi> logger)
|
||||
{
|
||||
if (logger == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <summary> Returns a list of artists. </summary>
|
||||
/// <param name="req"> Raw HTTP Request. </param>
|
||||
/// <param name="cancellationToken"> The cancellation token provided on Function shutdown. </param>
|
||||
[FunctionName("GetArtists_get")]
|
||||
public IActionResult GetArtists([HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "artists")] HttpRequest req)
|
||||
{
|
||||
_logger.LogInformation("HTTP trigger function processed a request.");
|
||||
// TODO: Handle Documented Responses.
|
||||
// Spec Defines: HTTP 200
|
||||
// Spec Defines: HTTP 400
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <summary> Lets a user post a new artist. </summary>
|
||||
/// <param name="body"> The Artist to use. </param>
|
||||
/// <param name="req"> Raw HTTP Request. </param>
|
||||
/// <param name="cancellationToken"> The cancellation token provided on Function shutdown. </param>
|
||||
/// <exception cref="ArgumentNullException"> <paramref name="body"/> is null. </exception>
|
||||
[FunctionName("NewArtist_post")]
|
||||
public IActionResult NewArtist([HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "artists")] Artist body, HttpRequest req)
|
||||
{
|
||||
_logger.LogInformation("HTTP trigger function processed a request.");
|
||||
// TODO: Handle Documented Responses.
|
||||
// Spec Defines: HTTP 200
|
||||
// Spec Defines: HTTP 400
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Company.Namespace.Models;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Azure.WebJobs;
|
||||
using Microsoft.Azure.WebJobs.Extensions.Http;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Company.Namespace
|
||||
{
|
||||
public class ArtistsApi
|
||||
{
|
||||
private ILogger<ArtistsApi> _logger;
|
||||
|
||||
/// <summary> Initializes a new instance of ArtistsApi. </summary>
|
||||
/// <param name="logger"> Class logger. </param>
|
||||
/// <exception cref="ArgumentNullException"> <paramref name="logger"/> is null. </exception>
|
||||
public ArtistsApi(ILogger<ArtistsApi> logger)
|
||||
{
|
||||
if (logger == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <summary> Returns a list of artists. </summary>
|
||||
/// <param name="req"> Raw HTTP Request. </param>
|
||||
/// <param name="cancellationToken"> The cancellation token provided on Function shutdown. </param>
|
||||
[FunctionName("GetArtists_get")]
|
||||
public IActionResult GetArtists([HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "artists")] HttpRequest req)
|
||||
{
|
||||
_logger.LogInformation("HTTP trigger function processed a request.");
|
||||
// TODO: Handle Documented Responses.
|
||||
// Spec Defines: HTTP 200
|
||||
// Spec Defines: HTTP 400
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <summary> Lets a user post a new artist. </summary>
|
||||
/// <param name="body"> The Artist to use. </param>
|
||||
/// <param name="req"> Raw HTTP Request. </param>
|
||||
/// <param name="cancellationToken"> The cancellation token provided on Function shutdown. </param>
|
||||
/// <exception cref="ArgumentNullException"> <paramref name="body"/> is null. </exception>
|
||||
[FunctionName("NewArtist_post")]
|
||||
public IActionResult NewArtist([HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "artists")] Artist body, HttpRequest req, [Sql("[dbo].[table1]", ConnectionStringSetting = "SqlConnectionString")] out Object output)
|
||||
{
|
||||
_logger.LogInformation("HTTP trigger function processed a request.");
|
||||
// TODO: Handle Documented Responses.
|
||||
// Spec Defines: HTTP 200
|
||||
// Spec Defines: HTTP 400
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
using Microsoft.SqlTools.Hosting.Protocol;
|
||||
using Microsoft.SqlTools.ServiceLayer.AzureFunctions;
|
||||
using Microsoft.SqlTools.ServiceLayer.AzureFunctions.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.Utility;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.AzureFunctions
|
||||
{
|
||||
class AzureFunctionsServiceTests
|
||||
{
|
||||
private string testAzureFunctionsFolder = Path.Combine("..", "..", "..", "AzureFunctions", "AzureFunctionTestFiles");
|
||||
|
||||
/// <summary>
|
||||
/// Verify input binding gets added
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void AddSqlInputBinding()
|
||||
{
|
||||
// copy the original file because the input binding will be inserted into the file
|
||||
string originalFile = Path.Join(testAzureFunctionsFolder, "AzureFunctionsNoBindings.ts");
|
||||
string testFile = Path.Join(Path.GetTempPath(), string.Format("InsertSqlInputBinding-{0}.ts", DateTime.Now.ToString("yyyy-dd-M--HH-mm-ss")));
|
||||
File.Copy(originalFile, testFile, true);
|
||||
|
||||
AddSqlBindingParams parameters = new AddSqlBindingParams
|
||||
{
|
||||
bindingType = BindingType.input,
|
||||
filePath = testFile,
|
||||
functionName = "GetArtists_get",
|
||||
objectName = "[dbo].[table1]",
|
||||
connectionStringSetting = "SqlConnectionString"
|
||||
};
|
||||
|
||||
AddSqlBindingOperation operation = new AddSqlBindingOperation(parameters);
|
||||
ResultStatus result = operation.AddBinding();
|
||||
|
||||
Assert.True(result.Success);
|
||||
Assert.IsNull(result.ErrorMessage);
|
||||
|
||||
string expectedFileText = File.ReadAllText(Path.Join(testAzureFunctionsFolder, "AzureFunctionsInputBinding.ts"));
|
||||
string actualFileText = File.ReadAllText(testFile);
|
||||
Assert.AreEqual(expectedFileText, actualFileText);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verify output binding gets added
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void AddSqlOutputBinding()
|
||||
{
|
||||
// copy the original file because the output binding will be inserted into the file
|
||||
string originalFile = Path.Join(testAzureFunctionsFolder, "AzureFunctionsNoBindings.ts");
|
||||
string testFile = Path.Join(Path.GetTempPath(), string.Format("InsertSqlOutputBinding-{0}.ts", DateTime.Now.ToString("yyyy-dd-M--HH-mm-ss")));
|
||||
File.Copy(originalFile, testFile, true);
|
||||
|
||||
AddSqlBindingParams parameters = new AddSqlBindingParams
|
||||
{
|
||||
bindingType = BindingType.output,
|
||||
filePath = testFile,
|
||||
functionName = "NewArtist_post",
|
||||
objectName = "[dbo].[table1]",
|
||||
connectionStringSetting = "SqlConnectionString"
|
||||
};
|
||||
|
||||
AddSqlBindingOperation operation = new AddSqlBindingOperation(parameters);
|
||||
ResultStatus result = operation.AddBinding();
|
||||
|
||||
Assert.True(result.Success);
|
||||
Assert.IsNull(result.ErrorMessage);
|
||||
|
||||
string expectedFileText = File.ReadAllText(Path.Join(testAzureFunctionsFolder, "AzureFunctionsOutputBinding.ts"));
|
||||
string actualFileText = File.ReadAllText(testFile);
|
||||
Assert.AreEqual(expectedFileText, actualFileText);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verify what happens when specified azure function isn't found
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void NoAzureFunctionForSqlBinding()
|
||||
{
|
||||
// copy the original file because the input binding will be inserted into the file
|
||||
string originalFile = Path.Join(testAzureFunctionsFolder, "AzureFunctionsNoBindings.ts");
|
||||
string testFile = Path.Join(Path.GetTempPath(), string.Format("NoAzureFunctionForSqlBinding-{0}.ts", DateTime.Now.ToString("yyyy-dd-M--HH-mm-ss")));
|
||||
File.Copy(originalFile, testFile, true);
|
||||
|
||||
AddSqlBindingParams parameters = new AddSqlBindingParams
|
||||
{
|
||||
bindingType = BindingType.input,
|
||||
filePath = testFile,
|
||||
functionName = "noExistingFunction",
|
||||
objectName = "[dbo].[table1]",
|
||||
connectionStringSetting = "SqlConnectionString"
|
||||
};
|
||||
|
||||
AddSqlBindingOperation operation = new AddSqlBindingOperation(parameters);
|
||||
ResultStatus result = operation.AddBinding();
|
||||
|
||||
Assert.False(result.Success);
|
||||
Assert.NotNull(result.ErrorMessage);
|
||||
Assert.True(result.ErrorMessage.Equals(SR.CouldntFindAzureFunction("noExistingFunction", testFile)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verify what happens when there's more than one Azure function with the specified name in the file
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void MoreThanOneAzureFunctionWithSpecifiedName()
|
||||
{
|
||||
// copy the original file because the input binding will be inserted into the file
|
||||
string originalFile = Path.Join(testAzureFunctionsFolder, "AzureFunctionsMultipleSameFunction.ts");
|
||||
string testFile = Path.Join(Path.GetTempPath(), string.Format("MoreThanOneAzureFunctionWithSpecifiedName-{0}.ts", DateTime.Now.ToString("yyyy-dd-M--HH-mm-ss")));
|
||||
File.Copy(originalFile, testFile, true);
|
||||
|
||||
AddSqlBindingParams parameters = new AddSqlBindingParams
|
||||
{
|
||||
bindingType = BindingType.input,
|
||||
filePath = testFile,
|
||||
functionName = "GetArtists_get",
|
||||
objectName = "[dbo].[table1]",
|
||||
connectionStringSetting = "SqlConnectionString"
|
||||
};
|
||||
|
||||
AddSqlBindingOperation operation = new AddSqlBindingOperation(parameters);
|
||||
ResultStatus result = operation.AddBinding();
|
||||
|
||||
Assert.False(result.Success);
|
||||
Assert.NotNull(result.ErrorMessage);
|
||||
Assert.True(result.ErrorMessage.Equals(SR.MoreThanOneAzureFunctionWithName("GetArtists_get", testFile)));
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user