Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions test-outofproc/.editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,7 @@
[*.cs]

dotnet_diagnostic.CS0659.severity = silent # overrides Object.Equals but does not override Object.GetHashCode() - not necessary for our samples
dotnet_diagnostic.CA1711.severity = silent # Identifiers should not have incorrect suffix - Fine for tests
dotnet_diagnostic.CA1848.severity = silent # For improved performance, use the LoggerMessage delegates instead of calling - Fine for tests
dotnet_diagnostic.CA2254.severity = silent # The logging message template should not vary between calls to 'LoggerExtensions.LogInformation - Fine for tests

6 changes: 5 additions & 1 deletion test-outofproc/GlobalSuppressions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,8 @@
[assembly: SuppressMessage("Style", "IDE0060:Remove unused parameter", Justification = "Unused parameter is required by functions binding", Scope = "member", Target = "~M:DotnetIsolatedTests.GetProductsColumnTypesSerialization.Run(Microsoft.AspNetCore.Http.HttpRequest,System.Collections.Generic.IEnumerable{DotnetIsolatedTests.Common.ProductColumnTypes})~System.Collections.Generic.IEnumerable{DotnetIsolatedTests.Common.ProductColumnTypes}")]
[assembly: SuppressMessage("Style", "IDE0060:Remove unused parameter", Justification = "Unused parameter is required by functions binding", Scope = "member", Target = "~M:DotnetIsolatedTests.AddProductIncorrectCasing.Run(Microsoft.Azure.Functions.Worker.Http.HttpRequestData)~DotnetIsolatedTests.Common.ProductIncorrectCasing")]
[assembly: SuppressMessage("Style", "IDE0060:Remove unused parameter", Justification = "Unused parameter is required by functions binding", Scope = "member", Target = "~M:DotnetIsolatedTests.AddProductUnsupportedTypes.Run(Microsoft.AspNetCore.Http.HttpRequest)~DotnetIsolatedTests.Common.ProductUnsupportedTypes")]
[assembly: SuppressMessage("Style", "IDE0060:Remove unused parameter", Justification = "Unused parameter is required by functions binding", Scope = "member", Target = "~M:DotnetIsolatedTests.AddProductDefaultPKAndDifferentColumnOrder.Run(Microsoft.AspNetCore.Http.HttpRequest)~DotnetIsolatedTests.Common.ProductDefaultPKWithDifferentColumnOrder")]
[assembly: SuppressMessage("Style", "IDE0060:Remove unused parameter", Justification = "Unused parameter is required by functions binding", Scope = "member", Target = "~M:DotnetIsolatedTests.AddProductDefaultPKAndDifferentColumnOrder.Run(Microsoft.AspNetCore.Http.HttpRequest)~DotnetIsolatedTests.Common.ProductDefaultPKWithDifferentColumnOrder")]
[assembly: SuppressMessage("Style", "IDE0060:Remove unused parameter", Justification = "Unused parameter is required by functions binding", Scope = "member", Target = "~M:DotnetIsolatedTests.PrimaryKeyNotPresentTrigger.Run(System.Collections.Generic.IReadOnlyList{Microsoft.Azure.Functions.Worker.Extensions.Sql.SqlChange{DotnetIsolatedTests.Common.Product})")]
[assembly: SuppressMessage("Style", "IDE0060:Remove unused parameter", Justification = "Unused parameter is required by functions binding", Scope = "member", Target = "~M:DotnetIsolatedTests.ReservedPrimaryKeyColumnNamesTrigger.Run(System.Collections.Generic.IReadOnlyList{Microsoft.Azure.Functions.Worker.Extensions.Sql.SqlChange{DotnetIsolatedTests.Common.Product})")]
[assembly: SuppressMessage("Style", "IDE0060:Remove unused parameter", Justification = "Unused parameter is required by functions binding", Scope = "member", Target = "~M:DotnetIsolatedTests.TableNotPresentTrigger.Run(System.Collections.Generic.IReadOnlyList{Microsoft.Azure.Functions.Worker.Extensions.Sql.SqlChange{DotnetIsolatedTests.Common.Product})")]
[assembly: SuppressMessage("Style", "IDE0060:Remove unused parameter", Justification = "Unused parameter is required by functions binding", Scope = "member", Target = "~M:DotnetIsolatedTests.UnsupportedColumnTypesTrigger.Run(System.Collections.Generic.IReadOnlyList{Microsoft.Azure.Functions.Worker.Extensions.Sql.SqlChange{DotnetIsolatedTests.Common.Product})")]
36 changes: 36 additions & 0 deletions test-outofproc/MultiFunctionTrigger.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

using System.Collections.Generic;
using DotnetIsolatedTests.Common;
using Microsoft.Extensions.Logging;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Extensions.Sql;


namespace DotnetIsolatedTests
{
/// <summary>
/// Used to ensure correct functionality with multiple user functions tracking the same table.
/// </summary>
public static class MultiFunctionTrigger
{
[Function(nameof(MultiFunctionTrigger1))]
public static void MultiFunctionTrigger1(
[SqlTrigger("[dbo].[Products]", "SqlConnectionString")]
IReadOnlyList<SqlChange<Product>> products,
ILogger logger)
{
logger.LogInformation("Trigger1 Changes: " + Utils.JsonSerializeObject(products));
}

[Function(nameof(MultiFunctionTrigger2))]
public static void MultiFunctionTrigger2(
[SqlTrigger("[dbo].[Products]", "SqlConnectionString")]
IReadOnlyList<SqlChange<Product>> products,
ILogger logger)
{
logger.LogInformation("Trigger2 Changes: " + Utils.JsonSerializeObject(products));
}
}
}
25 changes: 25 additions & 0 deletions test-outofproc/PrimaryKeyNotPresentTrigger.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using DotnetIsolatedTests.Common;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Extensions.Sql;

namespace DotnetIsolatedTests
{
public static class PrimaryKeyNotPresentTrigger
{
/// <summary>
/// Used in verification of the error message when the user table does not contain primary key.
/// </summary>
[Function(nameof(PrimaryKeyNotPresentTrigger))]
public static void Run(
[SqlTrigger("[dbo].[ProductsWithoutPrimaryKey]", "SqlConnectionString")]
IReadOnlyList<SqlChange<Product>> products)
{
throw new NotImplementedException("Associated test case should fail before the function is invoked.");
}
}
}
36 changes: 36 additions & 0 deletions test-outofproc/ProductsTriggerWithValidation.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using DotnetIsolatedTests.Common;
using Microsoft.Extensions.Logging;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Extensions.Sql;

namespace DotnetIsolatedTests
{
public static class ProductsTriggerWithValidation
{
private static readonly Action<ILogger, string, Exception> _loggerMessage = LoggerMessage.Define<string>(LogLevel.Information, eventId: new EventId(0, "INFO"), formatString: "{Message}");

/// <summary>
/// Simple trigger function with additional logic to allow for verifying that the expected number
/// of changes was received in each batch.
/// </summary>
[Function(nameof(ProductsTriggerWithValidation))]
public static void Run(
[SqlTrigger("[dbo].[Products]", "SqlConnectionString")]
IReadOnlyList<SqlChange<Product>> changes,
FunctionContext context)
{
string expectedMaxBatchSize = Environment.GetEnvironmentVariable("TEST_EXPECTED_MAX_BATCH_SIZE");
if (!string.IsNullOrEmpty(expectedMaxBatchSize) && int.Parse(expectedMaxBatchSize, null) != changes.Count)
{
throw new InvalidOperationException($"Invalid max batch size, got {changes.Count} changes but expected {expectedMaxBatchSize}");
}
// The output is used to inspect the trigger binding parameter in test methods.
_loggerMessage(context.GetLogger("ProductsTriggerWithValidation"), "SQL Changes: " + Utils.JsonSerializeObject(changes), null);
}
}
}
26 changes: 26 additions & 0 deletions test-outofproc/ReservedPrimaryKeyColumnNamesTrigger.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using DotnetIsolatedTests.Common;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Extensions.Sql;

namespace DotnetIsolatedTests
{
public static class ReservedPrimaryKeyColumnNamesTrigger
{
/// <summary>
/// Used in verification of the error message when the user table contains one or more primary keys with names
/// conflicting with column names in the leases table.
/// </summary>
[Function(nameof(ReservedPrimaryKeyColumnNamesTrigger))]
public static void Run(
[SqlTrigger("[dbo].[ProductsWithReservedPrimaryKeyColumnNames]", "SqlConnectionString")]
IReadOnlyList<SqlChange<Product>> products)
{
throw new NotImplementedException("Associated test case should fail before the function is invoked.");
}
}
}
25 changes: 25 additions & 0 deletions test-outofproc/TableNotPresentTrigger.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using DotnetIsolatedTests.Common;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Extensions.Sql;

namespace DotnetIsolatedTests
{
public static class TableNotPresentTrigger
{
/// <summary>
/// Used in verification of the error message when the user table is not present in the database.
/// </summary>
[Function(nameof(TableNotPresentTrigger))]
public static void Run(
[SqlTrigger("[dbo].[TableNotPresent]", "SqlConnectionString")]
IReadOnlyList<SqlChange<Product>> products)
{
throw new NotImplementedException("Associated test case should fail before the function is invoked.");
}
}
}
36 changes: 36 additions & 0 deletions test-outofproc/TriggerWithException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using DotnetIsolatedTests.Common;
using Microsoft.Extensions.Logging;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Extensions.Sql;

namespace DotnetIsolatedTests
{
public static class TriggerWithException
{
public const string ExceptionMessage = "TriggerWithException test exception";
private static bool threwException = false;

/// <summary>
/// Used in verification that exceptions thrown by functions cause the trigger to retry calling the function
/// once the lease timeout has expired
/// </summary>
[Function(nameof(TriggerWithException))]
public static void Run(
[SqlTrigger("[dbo].[Products]", "SqlConnectionString")]
IReadOnlyList<SqlChange<Product>> changes,
ILogger logger)
{
if (!threwException)
{
threwException = true;
throw new InvalidOperationException(ExceptionMessage);
}
logger.LogInformation("SQL Changes: " + Utils.JsonSerializeObject(changes));
}
}
}
25 changes: 25 additions & 0 deletions test-outofproc/UnsupportedColumnTypesTrigger.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using DotnetIsolatedTests.Common;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Extensions.Sql;

namespace DotnetIsolatedTests
{
public static class UnsupportedColumnTypesTrigger
{
/// <summary>
/// Used in verification of the error message when the user table contains columns of unsupported SQL types.
/// </summary>
[Function(nameof(UnsupportedColumnTypesTrigger))]
public static void Run(
[SqlTrigger("[dbo].[ProductsWithUnsupportedColumnTypes]", "SqlConnectionString")]
IReadOnlyList<SqlChange<Product>> products)
{
throw new NotImplementedException("Associated test case should fail before the function is invoked.");
}
}
}
68 changes: 68 additions & 0 deletions test-outofproc/Utils.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

using System.IO;
using System.Text;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;

namespace DotnetIsolatedTests
{
public static class Utils
{
/// <summary>
/// Default JSON serializer settings to use
/// </summary>
private static readonly JsonSerializerSettings _defaultJsonSerializationSettings;

static Utils()
{
_defaultJsonSerializationSettings = new JsonSerializerSettings
{
ContractResolver = new DefaultContractResolver()
};
}

/// <summary>
/// Serializes the specified object into a JSON string.
/// </summary>
/// <param name="obj">The object to serialize</param>
/// <param name="settings">The specific settings to use, uses a simple set of default settings if not specified</param>
/// <returns>The serialized JSON string</returns>
/// <remarks>This will NOT use any global settings to avoid picking up changes that may have been made by other code running in the host (such as user functions)</remarks>
public static string JsonSerializeObject(object obj, JsonSerializerSettings settings = null)
{
settings ??= _defaultJsonSerializationSettings;
// Following the Newtonsoft implementation in JsonConvert of creating a new JsonSerializer each time.
// https://github.com/JamesNK/Newtonsoft.Json/blob/57025815e564d36821acf778e2c00d02225aab35/Src/Newtonsoft.Json/JsonConvert.cs#L612
// If performance ends up being an issue could look into creating a single instance of the serializer for each setting.
var serializer = JsonSerializer.Create(settings);
// 256 is value used by Newtonsoft by default - helps avoid having to expand it too many times for larger strings
// https://github.com/JamesNK/Newtonsoft.Json/blob/57025815e564d36821acf778e2c00d02225aab35/Src/Newtonsoft.Json/JsonConvert.cs#L659
var sb = new StringBuilder(256);
var sw = new StringWriter(sb);
using JsonWriter writer = new JsonTextWriter(sw);
serializer.Serialize(writer, obj);
return sb.ToString();
}

/// <summary>
/// Deserializes the JSON string into an instance of the specified type
/// </summary>
/// <typeparam name="T">The type to deserialize into</typeparam>
/// <param name="json">The string containing the JSON</param>
/// <param name="settings">The specific settings to use, uses a simple set of default settings if not specified</param>
/// <returns>The instance of T being deserialized</returns>
/// <remarks>This will NOT use any global settings to avoid picking up changes that may have been made by other code running in the host (such as user functions)</remarks>
public static T JsonDeserializeObject<T>(string json, JsonSerializerSettings settings = null)
{
settings ??= _defaultJsonSerializationSettings;
// Following the Newtonsoft implementation in JsonConvert of creating a new JsonSerializer each time.
// https://github.com/JamesNK/Newtonsoft.Json/blob/57025815e564d36821acf778e2c00d02225aab35/Src/Newtonsoft.Json/JsonConvert.cs#L821
// If performance ends up being an issue could look into creating a single instance of the serializer for each setting.
var serializer = JsonSerializer.Create(settings);
using JsonReader reader = new JsonTextReader(new StringReader(json));
return serializer.Deserialize<T>(reader);
}
}
}
4 changes: 2 additions & 2 deletions test/Integration/SqlTriggerBindingIntegrationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ await this.WaitForProductChanges(
/// </summary>
[Theory]
[SqlInlineData()]
[UnsupportedLanguages(SupportedLanguages.Java, SupportedLanguages.OutOfProc)]
[UnsupportedLanguages(SupportedLanguages.Java)]
public async Task BatchSizeOverrideTriggerTest(SupportedLanguages lang)
{
// Use enough items to require 4 batches to be processed but then
Expand Down Expand Up @@ -124,7 +124,7 @@ await this.WaitForProductChanges(
/// </summary>
[Theory]
[SqlInlineData()]
[UnsupportedLanguages(SupportedLanguages.Java, SupportedLanguages.OutOfProc)]
[UnsupportedLanguages(SupportedLanguages.Java)]
public async Task MaxBatchSizeOverrideTriggerTest(SupportedLanguages lang)
{
// Use enough items to require 4 batches to be processed but then
Expand Down