diff --git a/.gitignore b/.gitignore index 62c71f6..9c498d4 100644 --- a/.gitignore +++ b/.gitignore @@ -345,3 +345,6 @@ build-linux-64 # vscode src/AzureEventGridSimulator/.config/ + +/src/AzureEventGridSimulator/appsettings.debug.txt +/**/*/log_*.txt \ No newline at end of file diff --git a/README.md b/README.md index c03597e..bcfbdd5 100644 --- a/README.md +++ b/README.md @@ -156,7 +156,7 @@ ENTRYPOINT ["AzureEventGridSimulator.exe"] #### Customizable Run command Alternatively, you can specify the configuration file, you have to map you config file -${PWD} - your curretn folder +${PWD} - your current folder C:\temp\ - folder inside the container `docker run -it --rm -v ${PWD}:C:\temp\ {TAG_NAME} --entrypoint AzureEventGridSimulator.exe --ConfigFile=C:\temp\{NAME OF YOUR CONFIGURATION FILE}` diff --git a/src/UnitTests/UnitTests.csproj b/src/AzureEventGridSimulator.Tests/AzureEventGridSimulator.Tests.csproj similarity index 67% rename from src/UnitTests/UnitTests.csproj rename to src/AzureEventGridSimulator.Tests/AzureEventGridSimulator.Tests.csproj index c97f2bd..4068a52 100644 --- a/src/UnitTests/UnitTests.csproj +++ b/src/AzureEventGridSimulator.Tests/AzureEventGridSimulator.Tests.csproj @@ -1,17 +1,19 @@ - + - netcoreapp3.1 + net5.0 true false - UnitTests + default + + - - + + all diff --git a/src/AzureEventGridSimulator.Tests/Integration/BasicTests.cs b/src/AzureEventGridSimulator.Tests/Integration/BasicTests.cs new file mode 100644 index 0000000..224ee9c --- /dev/null +++ b/src/AzureEventGridSimulator.Tests/Integration/BasicTests.cs @@ -0,0 +1,49 @@ +using System; +using System.Net; +using System.Net.Http; +using System.Text; +using System.Threading.Tasks; +using Azure.Messaging.EventGrid; +using AzureEventGridSimulator.Domain; +using Microsoft.AspNetCore.Mvc.Testing; +using Newtonsoft.Json; +using Shouldly; +using Xunit; + +namespace AzureEventGridSimulator.Tests.Integration +{ + public class BasicTests + : IClassFixture + { + private readonly TestContextFixture _factory; + + public BasicTests(TestContextFixture factory) + { + _factory = factory; + } + + [Fact] + public async Task GivenAValidEvent_WhenPublished_ThenItShouldBeAccepted() + { + // Arrange + var client = _factory.CreateClient(new WebApplicationFactoryClientOptions + { + BaseAddress = new Uri("https://localhost:60101") + }); + + client.DefaultRequestHeaders.Add(Constants.AegSasKeyHeader, "TheLocal+DevelopmentKey="); + client.DefaultRequestHeaders.Add(Constants.AegEventTypeHeader, Constants.NotificationEventType); + + var testEvent = new EventGridEvent("subject", "eventType", "1.0", new { Blah = 1 }); + var json = JsonConvert.SerializeObject(new[] { testEvent }, Formatting.Indented); + + // Act + var jsonContent = new StringContent(json, Encoding.UTF8, "application/json"); + var response = await client.PostAsync("/api/events", jsonContent); + + // Assert + response.EnsureSuccessStatusCode(); + response.StatusCode.ShouldBe(HttpStatusCode.OK); + } + } +} diff --git a/src/AzureEventGridSimulator.Tests/Integration/TestContextFixture.cs b/src/AzureEventGridSimulator.Tests/Integration/TestContextFixture.cs new file mode 100644 index 0000000..f01e14f --- /dev/null +++ b/src/AzureEventGridSimulator.Tests/Integration/TestContextFixture.cs @@ -0,0 +1,44 @@ +using System.IO; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Mvc.Testing; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Xunit; + +namespace AzureEventGridSimulator.Tests.Integration +{ + // ReSharper disable once ClassNeverInstantiated.Global + public class TestContextFixture : WebApplicationFactory, IAsyncLifetime + { + public Task InitializeAsync() + { + return Task.CompletedTask; + } + + public Task DisposeAsync() + { + Dispose(); + return Task.CompletedTask; + } + + protected override void ConfigureWebHost(IWebHostBuilder builder) + { + builder.UseEnvironment(Environments.Development); + + builder.ConfigureAppConfiguration((_, configurationBuilder) => + { + configurationBuilder + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.tests.json", false, true); + }); + + builder.ConfigureLogging(logging => + { + logging.ClearProviders(); + logging.AddConsole(); + }); + } + } +} diff --git a/src/UnitTests/ConfigurationLoadingTests.cs b/src/AzureEventGridSimulator.Tests/Unit/ConfigurationLoadingTests.cs similarity index 84% rename from src/UnitTests/ConfigurationLoadingTests.cs rename to src/AzureEventGridSimulator.Tests/Unit/ConfigurationLoadingTests.cs index c2ba01b..1fd35a0 100644 --- a/src/UnitTests/ConfigurationLoadingTests.cs +++ b/src/AzureEventGridSimulator.Tests/Unit/ConfigurationLoadingTests.cs @@ -4,14 +4,14 @@ using Shouldly; using Xunit; -namespace UnitTests +namespace AzureEventGridSimulator.Tests.Unit { public class ConfigurationLoadingTests { [Fact] public void TestConfigurationLoad() { - var json = @" + const string json = @" { ""topics"": [{ ""name"": ""MyAwesomeTopic"", @@ -51,9 +51,9 @@ public void TestConfigurationLoad() settings.ShouldNotBeNull(); settings.Topics.ShouldNotBeNull(); settings.Topics.ShouldAllBe(t => - t.Subscribers.All(s => s.Filter != null) && - t.Subscribers.All(s => s.Filter.AdvancedFilters != null) - ); + t.Subscribers.All(s => s.Filter != null) && + t.Subscribers.All(s => s.Filter.AdvancedFilters != null) + ); Should.NotThrow(() => { settings.Validate(); }); } diff --git a/src/UnitTests/Filtering/AdvancedFilterEventAcceptanceTests.cs b/src/AzureEventGridSimulator.Tests/Unit/Filtering/AdvancedFilterEventAcceptanceTests.cs similarity index 83% rename from src/UnitTests/Filtering/AdvancedFilterEventAcceptanceTests.cs rename to src/AzureEventGridSimulator.Tests/Unit/Filtering/AdvancedFilterEventAcceptanceTests.cs index ee863cd..792ed3e 100644 --- a/src/UnitTests/Filtering/AdvancedFilterEventAcceptanceTests.cs +++ b/src/AzureEventGridSimulator.Tests/Unit/Filtering/AdvancedFilterEventAcceptanceTests.cs @@ -5,11 +5,11 @@ using Shouldly; using Xunit; -namespace UnitTests.Filtering +namespace AzureEventGridSimulator.Tests.Unit.Filtering { public class AdvancedFilterEventAcceptanceTests { - private static readonly EventGridEvent GridEvent = new EventGridEvent + private static readonly EventGridEvent _gridEvent = new() { Id = "EventId", Data = new { NumberValue = 1, IsTrue = true, Name = "StringValue", DoubleValue = 0.12345d, NumberMaxValue = ulong.MaxValue, SubObject = new { Id = 1, Name = "Test" } }, @@ -27,7 +27,7 @@ public void TestAdvancedFilteringSuccess(AdvancedFilterSetting filter) { var filterConfig = new FilterSetting { AdvancedFilters = new[] { filter } }; - filterConfig.AcceptsEvent(GridEvent).ShouldBeTrue($"{filter.Key} - {filter.OperatorType} - {filter.Value} - {filter.Values.Separate() }"); + filterConfig.AcceptsEvent(_gridEvent).ShouldBeTrue($"{filter.Key} - {filter.OperatorType} - {filter.Value} - {filter.Values.Separate()}"); } [Theory] @@ -36,7 +36,7 @@ public void TestAdvancedFilteringFailure(AdvancedFilterSetting filter) { var filterConfig = new FilterSetting { AdvancedFilters = new[] { filter } }; - filterConfig.AcceptsEvent(GridEvent).ShouldBeFalse($"{filter.Key} - {filter.OperatorType} - {filter.Value} - {filter.Values.Separate() }"); + filterConfig.AcceptsEvent(_gridEvent).ShouldBeFalse($"{filter.Key} - {filter.OperatorType} - {filter.Value} - {filter.Values.Separate()}"); } [Fact] @@ -44,8 +44,9 @@ public void TestSimpleEventDataFilteringSuccess() { var filterConfig = new FilterSetting { - AdvancedFilters = new[] { - new AdvancedFilterSetting { Key = "Data", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.NumberIn, Values = new object[]{ 1 } } + AdvancedFilters = new[] + { + new AdvancedFilterSetting { Key = "Data", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.NumberIn, Values = new object[] { 1 } } } }; var gridEvent = new EventGridEvent { Data = 1 }; @@ -58,7 +59,8 @@ public void TestSimpleEventDataFilteringUsingValueSuccess() { var filterConfig = new FilterSetting { - AdvancedFilters = new[] { + AdvancedFilters = new[] + { new AdvancedFilterSetting { Key = "Data", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.NumberGreaterThanOrEquals, Value = 1 }, new AdvancedFilterSetting { Key = "Data", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.NumberLessThanOrEquals, Value = 1 } } @@ -73,7 +75,8 @@ public void TestSimpleEventDataFilteringFailure() { var filterConfig = new FilterSetting { - AdvancedFilters = new[] { + AdvancedFilters = new[] + { new AdvancedFilterSetting { Key = "Data", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.NumberIn, Value = 1 } } }; diff --git a/src/UnitTests/Filtering/AdvancedFilterValidationTests.cs b/src/AzureEventGridSimulator.Tests/Unit/Filtering/AdvancedFilterValidationTests.cs similarity index 91% rename from src/UnitTests/Filtering/AdvancedFilterValidationTests.cs rename to src/AzureEventGridSimulator.Tests/Unit/Filtering/AdvancedFilterValidationTests.cs index baa6b2e..1c7f268 100644 --- a/src/UnitTests/Filtering/AdvancedFilterValidationTests.cs +++ b/src/AzureEventGridSimulator.Tests/Unit/Filtering/AdvancedFilterValidationTests.cs @@ -5,13 +5,13 @@ using Shouldly; using Xunit; -namespace UnitTests.Filtering +namespace AzureEventGridSimulator.Tests.Unit.Filtering { public class AdvancedFilterValidationTests { - private SimulatorSettings GetValidSimulatorSettings(AdvancedFilterSetting advancedFilter) + private static SimulatorSettings GetValidSimulatorSettings(AdvancedFilterSetting advancedFilter) { - return new SimulatorSettings + return new() { Topics = new[] { @@ -22,10 +22,10 @@ private SimulatorSettings GetValidSimulatorSettings(AdvancedFilterSetting advanc Port = 12345, Subscribers = new List { - new SubscriptionSettings + new() { Name = "SubscriberName", - Filter = new FilterSetting{ AdvancedFilters = new[]{ advancedFilter } } + Filter = new FilterSetting { AdvancedFilters = new[] { advancedFilter } } } }.ToArray() } @@ -142,7 +142,11 @@ public void TestFilterValidationWithSixValues() foreach (AdvancedFilterSetting.OperatorTypeEnum operatorType in Enum.GetValues(typeof(AdvancedFilterSetting.OperatorTypeEnum))) { var filterConfig = new AdvancedFilterSetting { Key = "Data", Values = new object[6], OperatorType = operatorType }; - if (new[] { AdvancedFilterSetting.OperatorTypeEnum.NumberIn, AdvancedFilterSetting.OperatorTypeEnum.NumberNotIn, AdvancedFilterSetting.OperatorTypeEnum.StringIn, AdvancedFilterSetting.OperatorTypeEnum.StringNotIn }.Contains(operatorType)) + if (new[] + { + AdvancedFilterSetting.OperatorTypeEnum.NumberIn, AdvancedFilterSetting.OperatorTypeEnum.NumberNotIn, AdvancedFilterSetting.OperatorTypeEnum.StringIn, + AdvancedFilterSetting.OperatorTypeEnum.StringNotIn + }.Contains(operatorType)) { var exception = Should.Throw(() => GetValidSimulatorSettings(filterConfig).Validate()); @@ -169,7 +173,7 @@ public void TestFilterValidationWithSingleDepthKey() [Fact] public void TestFilterValidationWithGrandchildKey() { - // following the announcement here https://azure.microsoft.com/en-us/updates/advanced-filtering-generally-available-in-event-grid/ this should now work + // following the announcement here https://azure.microsoft.com/en-us/updates/advanced-filtering-generally-available-in-event-grid/ this should now work Should.NotThrow(() => { var filterConfig = new AdvancedFilterSetting { Key = "Data.Key1.SubKey", Value = "SomeValue" }; diff --git a/src/UnitTests/Filtering/FilterSettingsValidationTests.cs b/src/AzureEventGridSimulator.Tests/Unit/Filtering/FilterSettingsValidationTests.cs similarity index 88% rename from src/UnitTests/Filtering/FilterSettingsValidationTests.cs rename to src/AzureEventGridSimulator.Tests/Unit/Filtering/FilterSettingsValidationTests.cs index eda24b6..49a38a1 100644 --- a/src/UnitTests/Filtering/FilterSettingsValidationTests.cs +++ b/src/AzureEventGridSimulator.Tests/Unit/Filtering/FilterSettingsValidationTests.cs @@ -4,13 +4,13 @@ using Shouldly; using Xunit; -namespace UnitTests.Filtering +namespace AzureEventGridSimulator.Tests.Unit.Filtering { public class FilterSettingsValidationTests { - private SimulatorSettings GetValidSimulatorSettings(FilterSetting filter) + private static SimulatorSettings GetValidSimulatorSettings(FilterSetting filter) { - return new SimulatorSettings + return new() { Topics = new[] { @@ -21,7 +21,7 @@ private SimulatorSettings GetValidSimulatorSettings(FilterSetting filter) Port = 12345, Subscribers = new List { - new SubscriptionSettings + new() { Name = "SubscriberName", Filter = filter @@ -32,9 +32,9 @@ private SimulatorSettings GetValidSimulatorSettings(FilterSetting filter) }; } - private AdvancedFilterSetting GetValidAdvancedFilter() + private static AdvancedFilterSetting GetValidAdvancedFilter() { - return new AdvancedFilterSetting + return new() { Key = "key", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.BoolEquals, diff --git a/src/AzureEventGridSimulator.Tests/Unit/Filtering/NegativeFilterTestCaseContainer.cs b/src/AzureEventGridSimulator.Tests/Unit/Filtering/NegativeFilterTestCaseContainer.cs new file mode 100644 index 0000000..fa55115 --- /dev/null +++ b/src/AzureEventGridSimulator.Tests/Unit/Filtering/NegativeFilterTestCaseContainer.cs @@ -0,0 +1,192 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using AzureEventGridSimulator.Infrastructure.Settings; + +// ReSharper disable StringLiteralTypo + +namespace AzureEventGridSimulator.Tests.Unit.Filtering +{ + internal class NegativeFilterTestCaseContainer : IEnumerable + { + public IEnumerator GetEnumerator() + { + var list = new List(); + list.AddRange(GetNegativeIdFilterConfigurations().Select(c => new object[] { c })); + list.AddRange(GetNegativeTopicFilterConfigurations().Select(c => new object[] { c })); + list.AddRange(GetNegativeSubjectFilterConfigurations().Select(c => new object[] { c })); + list.AddRange(GetNegativeEventTypeFilterConfigurations().Select(c => new object[] { c })); + list.AddRange(GetNegativeDataVersionFilterConfigurations().Select(c => new object[] { c })); + list.AddRange(GetNegativeEventDataFilterConfigurations().Select(c => new object[] { c })); + list.AddRange(GetNegativeEventIdFilterConfigurations().Select(c => new object[] { c })); + list.AddRange(GetNegativeSourceFilterConfigurations().Select(c => new object[] { c })); + list.AddRange(GetNegativeEventTypeVersionFilterConfigurations().Select(c => new object[] { c })); + return list.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + private static IEnumerable GetNegativeIdFilterConfigurations() + { + return new[] + { + new AdvancedFilterSetting { Key = "Id", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringBeginsWith, Value = null }, + new AdvancedFilterSetting { Key = "Id", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringBeginsWith, Value = string.Empty }, + new AdvancedFilterSetting { Key = "Id", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringBeginsWith, Value = "A" }, + new AdvancedFilterSetting { Key = "Id", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringBeginsWith, Value = "a" }, + new AdvancedFilterSetting { Key = "Id", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringContains, Value = null }, + new AdvancedFilterSetting { Key = "Id", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringContains, Value = string.Empty }, + new AdvancedFilterSetting { Key = "Id", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringContains, Value = "a" }, + new AdvancedFilterSetting { Key = "Id", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringContains, Value = "TEN" }, + new AdvancedFilterSetting { Key = "Id", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringEndsWith, Value = "b" }, + new AdvancedFilterSetting { Key = "Id", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringEndsWith, Value = string.Empty }, + new AdvancedFilterSetting { Key = "Id", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringEndsWith, Value = null }, + new AdvancedFilterSetting { Key = "Id", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringEndsWith, Value = "B" }, + new AdvancedFilterSetting { Key = "Id", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringIn, Values = null }, + new AdvancedFilterSetting { Key = "Id", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringIn, Values = Array.Empty() }, + new AdvancedFilterSetting { Key = "Id", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringIn, Values = new[] { "notCorrect" } }, + new AdvancedFilterSetting { Key = "Id", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringIn, Values = new[] { "different", "not_found", "Another" } }, + new AdvancedFilterSetting { Key = "Id", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringNotIn, Values = new[] { "different", "EventID", "Another" } }, + new AdvancedFilterSetting { Key = "Id", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringNotIn, Values = new[] { "different", "EventId", "Another" } } + }; + } + + private static IEnumerable GetNegativeTopicFilterConfigurations() + { + return new[] + { + new AdvancedFilterSetting { Key = "Topic", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringBeginsWith, Value = "HE" }, + new AdvancedFilterSetting { Key = "Topic", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringBeginsWith, Value = "he_" }, + new AdvancedFilterSetting { Key = "Topic", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringContains, Value = "everest" }, + new AdvancedFilterSetting { Key = "Topic", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringEndsWith, Value = "123" }, + new AdvancedFilterSetting { Key = "Topic", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringIn, Values = new object[] { "_event_" } }, + new AdvancedFilterSetting { Key = "Topic", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringNotIn, Values = new object[] { "THE_EVENT_TOPIC" } } + }; + } + + private static IEnumerable GetNegativeSubjectFilterConfigurations() + { + return new[] + { + new AdvancedFilterSetting { Key = "Subject", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringBeginsWith, Value = "E" }, + new AdvancedFilterSetting { Key = "Subject", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringBeginsWith, Value = "able" }, + new AdvancedFilterSetting { Key = "Subject", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringContains, Value = "x" }, + new AdvancedFilterSetting { Key = "Subject", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringEndsWith, Value = "sub" }, + new AdvancedFilterSetting { Key = "Subject", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringIn, Values = new object[] { "not_correct" } }, + new AdvancedFilterSetting { Key = "Subject", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringNotIn, Values = new object[] { "theeventsubject" } } + }; + } + + private static IEnumerable GetNegativeEventTypeFilterConfigurations() + { + return new[] + { + new AdvancedFilterSetting { Key = "EventType", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringBeginsWith, Value = "his" }, + new AdvancedFilterSetting { Key = "EventType", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringBeginsWith, Value = "hIs" }, + new AdvancedFilterSetting { Key = "EventType", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringContains, Value = ".." }, + new AdvancedFilterSetting { Key = "EventType", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringEndsWith, Value = "EVENTTYPE" }, + new AdvancedFilterSetting { Key = "EventType", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringIn, Values = Array.Empty() }, + new AdvancedFilterSetting + { + Key = "EventType", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringNotIn, + Values = new object[] { "Not-the-right-type", "this.is.a.test.event.type" } + } + }; + } + + private static IEnumerable GetNegativeDataVersionFilterConfigurations() + { + return new[] + { + new AdvancedFilterSetting { Key = "DataVersion", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringBeginsWith, Value = "a" }, + new AdvancedFilterSetting { Key = "DataVersion", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringContains, Value = "_" }, + new AdvancedFilterSetting { Key = "DataVersion", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringEndsWith, Value = "7" }, + new AdvancedFilterSetting { Key = "DataVersion", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringIn, Values = new object[] { "5.0.1" } }, + new AdvancedFilterSetting { Key = "DataVersion", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringNotIn, Values = new object[] { "5.0" } } + }; + } + + private static IEnumerable GetNegativeEventDataFilterConfigurations() + { + return new[] + { + new AdvancedFilterSetting { Key = "Data.NumberValue", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.NumberGreaterThan, Value = 2 }, + new AdvancedFilterSetting { Key = "Data.NumberValue", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.NumberGreaterThan, Value = null }, + new AdvancedFilterSetting { Key = "Data.NumberValue", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.NumberGreaterThan, Value = 1 }, + new AdvancedFilterSetting { Key = "Data.NumberValue", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.NumberGreaterThanOrEquals, Value = null }, + new AdvancedFilterSetting { Key = "Data.NumberValue", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.NumberGreaterThanOrEquals, Value = 1.01 }, + new AdvancedFilterSetting { Key = "Data.NumberValue", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.NumberGreaterThanOrEquals, Value = 5 }, + new AdvancedFilterSetting + { Key = "Data.NumberValue", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.NumberIn, Values = new object[] { 1.1, 2, 3.5, "stringValue", true } }, + new AdvancedFilterSetting { Key = "Data.NumberValue", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.NumberIn, Values = Array.Empty() }, + new AdvancedFilterSetting { Key = "Data.NumberValue", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.NumberIn, Values = null }, + new AdvancedFilterSetting + { Key = "Data.NumberValue", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.NumberNotIn, Values = new object[] { 0, 1, 2, 3.5, "stringValue", true } }, + // while the value is not in the array, the fact that the values in the array are not all parsable as numbers means the full evaluation cannot be completed and so by default we fail + new AdvancedFilterSetting + { Key = "Data.NumberValue", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.NumberNotIn, Values = new object[] { 0, 2, 3.5, "stringValue", true } }, + new AdvancedFilterSetting { Key = "Data.NumberValue", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.NumberLessThan, Value = 1 }, + new AdvancedFilterSetting { Key = "Data.NumberValue", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.NumberLessThan, Value = null }, + new AdvancedFilterSetting { Key = "Data.NumberValue", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.NumberLessThan, Value = 0.99999999 }, + new AdvancedFilterSetting { Key = "Data.NumberValue", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.NumberLessThanOrEquals, Value = null }, + new AdvancedFilterSetting { Key = "Data.NumberValue", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.NumberLessThanOrEquals, Value = 0.9 }, + new AdvancedFilterSetting { Key = "Data.NumberValue", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.NumberLessThanOrEquals, Value = -1 }, + new AdvancedFilterSetting { Key = "Data.IsTrue", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.BoolEquals, Value = null }, + new AdvancedFilterSetting { Key = "Data.IsTrue", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.BoolEquals, Value = false }, + new AdvancedFilterSetting { Key = "Data.Name", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringBeginsWith, Value = null }, + new AdvancedFilterSetting { Key = "Data.Name", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringBeginsWith, Value = "String_Value" }, + new AdvancedFilterSetting { Key = "Data.DoubleValue", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.NumberGreaterThan, Value = null }, + new AdvancedFilterSetting { Key = "Data.DoubleValue", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.NumberGreaterThan, Value = 0.12345 }, + new AdvancedFilterSetting { Key = "Data.DoubleValue", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.NumberGreaterThanOrEquals, Value = null }, + new AdvancedFilterSetting { Key = "Data.DoubleValue", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.NumberGreaterThanOrEquals, Value = 0.123451 }, + new AdvancedFilterSetting { Key = "Data.DoubleValue", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.NumberIn, Values = new object[] { 0.123451 } }, + new AdvancedFilterSetting { Key = "Data.DoubleValue", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.NumberLessThan, Value = 0.12345 }, + new AdvancedFilterSetting { Key = "Data.DoubleValue", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.NumberLessThanOrEquals, Value = 0.1234 }, + new AdvancedFilterSetting { Key = "Data.DoubleValue", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.NumberNotIn, Values = new object[] { 0.12345 } }, + new AdvancedFilterSetting { Key = "Data.NumberMaxValue", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.NumberGreaterThan, Value = ulong.MaxValue }, + new AdvancedFilterSetting { Key = "Data.NumberMaxValue", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.NumberIn, Values = new object[] { long.MaxValue } }, + new AdvancedFilterSetting { Key = "Data.NumberMaxValue", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.NumberLessThanOrEquals, Value = long.MaxValue }, + new AdvancedFilterSetting { Key = "Data.NumberMaxValue", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.NumberLessThan, Value = ulong.MaxValue }, + new AdvancedFilterSetting + { Key = "Data.NumberMaxValue", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.NumberNotIn, Values = new object[] { ulong.MaxValue } }, + new AdvancedFilterSetting + { Key = "Data.SubObject.Name", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringIn, Values = new object[] { "Testing", "does", "not", "exist" } }, + new AdvancedFilterSetting { Key = "Data.SubObject.Id", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.NumberIn, Values = new object[] { 10, 11, 12 } } + }; + } + + private static IEnumerable GetNegativeEventIdFilterConfigurations() + { + // everything with this key is considered negative at the moment given that the key will never be found on an event that doesn't not conform to the cloud schema + // special case for use with the cloud event schema (https://docs.microsoft.com/en-us/azure/event-grid/cloudevents-schema) + return new[] + { + new AdvancedFilterSetting { Key = "EventId" } + }; + } + + private static IEnumerable GetNegativeSourceFilterConfigurations() + { + // everything with this key is considered negative at the moment given that the key will never be found on an event that doesn't not conform to the cloud schema + // no positive tests are available for this key yet since no support for the cloud event schema is available at the moment + return new[] + { + new AdvancedFilterSetting { Key = "Source" } + }; + } + + private static IEnumerable GetNegativeEventTypeVersionFilterConfigurations() + { + // everything with this key is considered negative at the moment given that the key will never be found on an event that doesn't not conform to the cloud schema + // no positive tests are available for this key yet since no support for the cloud event schema is available at the moment + return new[] + { + new AdvancedFilterSetting { Key = "EventTypeVersion" } + }; + } + } +} diff --git a/src/UnitTests/Filtering/PositiveFilterTestCaseContainer.cs b/src/AzureEventGridSimulator.Tests/Unit/Filtering/PositiveFilterTestCaseContainer.cs similarity index 78% rename from src/UnitTests/Filtering/PositiveFilterTestCaseContainer.cs rename to src/AzureEventGridSimulator.Tests/Unit/Filtering/PositiveFilterTestCaseContainer.cs index 105bd9e..37c8be4 100644 --- a/src/UnitTests/Filtering/PositiveFilterTestCaseContainer.cs +++ b/src/AzureEventGridSimulator.Tests/Unit/Filtering/PositiveFilterTestCaseContainer.cs @@ -1,11 +1,14 @@ -using System.Collections; +using System; +using System.Collections; using System.Collections.Generic; using System.Linq; using AzureEventGridSimulator.Infrastructure.Settings; -namespace UnitTests.Filtering +// ReSharper disable StringLiteralTypo + +namespace AzureEventGridSimulator.Tests.Unit.Filtering { - class PositiveFilterTestCaseContainer : IEnumerable + internal class PositiveFilterTestCaseContainer : IEnumerable { public IEnumerator GetEnumerator() { @@ -19,16 +22,22 @@ public IEnumerator GetEnumerator() return list.GetEnumerator(); } - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } - private static AdvancedFilterSetting[] GetPositiveIdFilterConfigurations() + private static IEnumerable GetPositiveIdFilterConfigurations() { return new[] { new AdvancedFilterSetting { Key = "Id", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringBeginsWith, Value = "E" }, new AdvancedFilterSetting { Key = "Id", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringBeginsWith, Value = "Event" }, new AdvancedFilterSetting { Key = "Id", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringBeginsWith, Value = "Event" }, - new AdvancedFilterSetting { Key = "Id", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringBeginsWith, Value = "EVE" }, // according to the spec, string comparisons in advanced mode are always case insensitive + new AdvancedFilterSetting + { + Key = "Id", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringBeginsWith, Value = "EVE" + }, // according to the spec, string comparisons in advanced mode are always case insensitive new AdvancedFilterSetting { Key = "Id", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringContains, Value = "E" }, new AdvancedFilterSetting { Key = "Id", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringContains, Value = "ent" }, new AdvancedFilterSetting { Key = "Id", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringContains, Value = "ENT" }, @@ -38,73 +47,81 @@ private static AdvancedFilterSetting[] GetPositiveIdFilterConfigurations() new AdvancedFilterSetting { Key = "Id", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringEndsWith, Value = "Id" }, new AdvancedFilterSetting { Key = "Id", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringEndsWith, Value = "d" }, new AdvancedFilterSetting { Key = "Id", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringEndsWith, Value = "D" }, - new AdvancedFilterSetting { Key = "Id", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringIn, Values = new[] {"EventId" } }, - new AdvancedFilterSetting { Key = "Id", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringIn, Values = new[] {"eventid" } }, - new AdvancedFilterSetting { Key = "Id", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringIn, Values = new[] {"EVENTID" } }, - new AdvancedFilterSetting { Key = "Id", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringIn, Values = new[] {"different", "EVENTID", "Another" } }, - new AdvancedFilterSetting { Key = "Id", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringIn, Values = new object[] {"different", "EVENTID", "Another" } }, - new AdvancedFilterSetting { Key = "Id", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringNotIn, Values = new[] {"different", "notfound", "Another" } }, - new AdvancedFilterSetting { Key = "Id", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringNotIn, Values = null}, - new AdvancedFilterSetting { Key = "Id", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringNotIn, Values = new string[0]} + new AdvancedFilterSetting { Key = "Id", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringIn, Values = new[] { "EventId" } }, + new AdvancedFilterSetting { Key = "Id", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringIn, Values = new[] { "eventid" } }, + new AdvancedFilterSetting { Key = "Id", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringIn, Values = new[] { "EVENTID" } }, + new AdvancedFilterSetting { Key = "Id", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringIn, Values = new[] { "different", "EVENTID", "Another" } }, + new AdvancedFilterSetting + { Key = "Id", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringIn, Values = new object[] { "different", "EVENTID", "Another" } }, + new AdvancedFilterSetting { Key = "Id", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringNotIn, Values = new[] { "different", "notfound", "Another" } }, + new AdvancedFilterSetting { Key = "Id", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringNotIn, Values = null }, + new AdvancedFilterSetting { Key = "Id", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringNotIn, Values = Array.Empty() } }; } - private static AdvancedFilterSetting[] GetPositiveTopicFilterConfigurations() + private static IEnumerable GetPositiveTopicFilterConfigurations() { - return new[] { - new AdvancedFilterSetting { Key = "Topic", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringBeginsWith, Value ="THE" }, - new AdvancedFilterSetting { Key = "Topic", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringBeginsWith, Value ="the_" }, - new AdvancedFilterSetting { Key = "Topic", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringContains, Value ="event" }, - new AdvancedFilterSetting { Key = "Topic", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringEndsWith, Value ="Ic" }, - new AdvancedFilterSetting { Key = "Topic", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringIn, Values = new object[] {"the_event_topic" } }, - new AdvancedFilterSetting { Key = "Topic", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringNotIn, Values = new object[] {"not_the_right_one" } } + return new[] + { + new AdvancedFilterSetting { Key = "Topic", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringBeginsWith, Value = "THE" }, + new AdvancedFilterSetting { Key = "Topic", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringBeginsWith, Value = "the_" }, + new AdvancedFilterSetting { Key = "Topic", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringContains, Value = "event" }, + new AdvancedFilterSetting { Key = "Topic", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringEndsWith, Value = "Ic" }, + new AdvancedFilterSetting { Key = "Topic", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringIn, Values = new object[] { "the_event_topic" } }, + new AdvancedFilterSetting { Key = "Topic", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringNotIn, Values = new object[] { "not_the_right_one" } } }; } - private static AdvancedFilterSetting[] GetPositiveSubjectFilterConfigurations() + private static IEnumerable GetPositiveSubjectFilterConfigurations() { - return new[] { - new AdvancedFilterSetting { Key = "Subject", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringBeginsWith, Value ="THE" }, - new AdvancedFilterSetting { Key = "Subject", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringBeginsWith, Value ="theE" }, - new AdvancedFilterSetting { Key = "Subject", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringContains, Value ="event" }, - new AdvancedFilterSetting { Key = "Subject", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringEndsWith, Value ="Subject" }, + return new[] + { + new AdvancedFilterSetting { Key = "Subject", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringBeginsWith, Value = "THE" }, + new AdvancedFilterSetting { Key = "Subject", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringBeginsWith, Value = "theE" }, + new AdvancedFilterSetting { Key = "Subject", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringContains, Value = "event" }, + new AdvancedFilterSetting { Key = "Subject", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringEndsWith, Value = "Subject" }, new AdvancedFilterSetting { Key = "Subject", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringIn, Values = new object[] { "theeventsubject" } }, new AdvancedFilterSetting { Key = "Subject", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringNotIn, Values = new object[] { "NotTheEventSubject" } } }; } - private static AdvancedFilterSetting[] GetPositiveEventTypeFilterConfigurations() + private static IEnumerable GetPositiveEventTypeFilterConfigurations() { - return new[] { - new AdvancedFilterSetting { Key = "EventType", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringBeginsWith, Value ="this" }, - new AdvancedFilterSetting { Key = "EventType", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringBeginsWith, Value ="ThIs" }, - new AdvancedFilterSetting { Key = "EventType", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringContains, Value =".event." }, - new AdvancedFilterSetting { Key = "EventType", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringEndsWith, Value ="EVENT.TYPE" }, - new AdvancedFilterSetting { Key = "EventType", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringIn, Values = new object[] { "this.is.a.test.event.type" } }, + return new[] + { + new AdvancedFilterSetting { Key = "EventType", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringBeginsWith, Value = "this" }, + new AdvancedFilterSetting { Key = "EventType", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringBeginsWith, Value = "ThIs" }, + new AdvancedFilterSetting { Key = "EventType", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringContains, Value = ".event." }, + new AdvancedFilterSetting { Key = "EventType", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringEndsWith, Value = "EVENT.TYPE" }, + new AdvancedFilterSetting + { Key = "EventType", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringIn, Values = new object[] { "this.is.a.test.event.type" } }, new AdvancedFilterSetting { Key = "EventType", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringNotIn, Values = new object[] { "Not-the-right-type" } } }; } - private static AdvancedFilterSetting[] GetPositiveDataVersionFilterConfigurations() + private static IEnumerable GetPositiveDataVersionFilterConfigurations() { - return new[] { - new AdvancedFilterSetting { Key = "DataVersion", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringBeginsWith, Value ="5" }, - new AdvancedFilterSetting { Key = "DataVersion", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringContains, Value ="." }, - new AdvancedFilterSetting { Key = "DataVersion", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringEndsWith, Value ="0" }, + return new[] + { + new AdvancedFilterSetting { Key = "DataVersion", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringBeginsWith, Value = "5" }, + new AdvancedFilterSetting { Key = "DataVersion", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringContains, Value = "." }, + new AdvancedFilterSetting { Key = "DataVersion", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringEndsWith, Value = "0" }, new AdvancedFilterSetting { Key = "DataVersion", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringIn, Values = new object[] { "5.0" } }, new AdvancedFilterSetting { Key = "DataVersion", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringNotIn, Values = new object[] { "5" } } }; } - private static AdvancedFilterSetting[] GetPositiveEventDataFilterConfigurations() + private static IEnumerable GetPositiveEventDataFilterConfigurations() { - return new[] { + return new[] + { new AdvancedFilterSetting { Key = "Data.NumberValue", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.NumberGreaterThan, Value = 0 }, new AdvancedFilterSetting { Key = "Data.NumberValue", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.NumberGreaterThan, Value = 0.5 }, new AdvancedFilterSetting { Key = "Data.NumberValue", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.NumberGreaterThanOrEquals, Value = 0.5 }, new AdvancedFilterSetting { Key = "Data.NumberValue", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.NumberGreaterThanOrEquals, Value = 1 }, - new AdvancedFilterSetting { Key = "Data.NumberValue", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.NumberIn, Values = new object []{ 1.0, 2, 3.5, "stringValue", true } }, - new AdvancedFilterSetting { Key = "Data.NumberValue", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.NumberNotIn, Values = new object [0] }, + new AdvancedFilterSetting + { Key = "Data.NumberValue", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.NumberIn, Values = new object[] { 1.0, 2, 3.5, "stringValue", true } }, + new AdvancedFilterSetting { Key = "Data.NumberValue", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.NumberNotIn, Values = Array.Empty() }, new AdvancedFilterSetting { Key = "Data.NumberValue", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.NumberNotIn, Values = null }, new AdvancedFilterSetting { Key = "Data.NumberValue", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.NumberLessThan, Value = 1.1 }, new AdvancedFilterSetting { Key = "Data.NumberValue", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.NumberLessThan, Value = 2 }, @@ -112,9 +129,9 @@ private static AdvancedFilterSetting[] GetPositiveEventDataFilterConfigurations( new AdvancedFilterSetting { Key = "Data.NumberValue", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.NumberLessThanOrEquals, Value = 2 }, new AdvancedFilterSetting { Key = "Data.NumberValue", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.NumberLessThanOrEquals, Value = 1 }, new AdvancedFilterSetting { Key = "Data.IsTrue", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.BoolEquals, Value = true }, - new AdvancedFilterSetting { Key = "Data.Name", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringBeginsWith, Value = "StringValue"}, - new AdvancedFilterSetting { Key = "Data.DoubleValue", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.NumberGreaterThan, Value = 0.123449}, - new AdvancedFilterSetting { Key = "Data.DoubleValue", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.NumberGreaterThanOrEquals, Value = 0.12345}, + new AdvancedFilterSetting { Key = "Data.Name", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringBeginsWith, Value = "StringValue" }, + new AdvancedFilterSetting { Key = "Data.DoubleValue", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.NumberGreaterThan, Value = 0.123449 }, + new AdvancedFilterSetting { Key = "Data.DoubleValue", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.NumberGreaterThanOrEquals, Value = 0.12345 }, new AdvancedFilterSetting { Key = "Data.DoubleValue", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.NumberIn, Values = new object[] { 0.12345 } }, new AdvancedFilterSetting { Key = "Data.DoubleValue", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.NumberLessThan, Value = 0.123451 }, new AdvancedFilterSetting { Key = "Data.DoubleValue", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.NumberLessThanOrEquals, Value = 0.12345 }, @@ -123,7 +140,8 @@ private static AdvancedFilterSetting[] GetPositiveEventDataFilterConfigurations( new AdvancedFilterSetting { Key = "Data.NumberMaxValue", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.NumberGreaterThanOrEquals, Value = ulong.MaxValue }, new AdvancedFilterSetting { Key = "Data.NumberMaxValue", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.NumberIn, Values = new object[] { ulong.MaxValue } }, new AdvancedFilterSetting { Key = "Data.NumberMaxValue", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.NumberLessThanOrEquals, Value = ulong.MaxValue }, - new AdvancedFilterSetting { Key = "Data.NumberMaxValue", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.NumberNotIn, Values = new object[] { long.MaxValue } }, + new AdvancedFilterSetting + { Key = "Data.NumberMaxValue", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.NumberNotIn, Values = new object[] { long.MaxValue } }, new AdvancedFilterSetting { Key = "Data.SubObject.Name", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringIn, Values = new object[] { "Test" } }, new AdvancedFilterSetting { Key = "Data.SubObject.Id", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.NumberIn, Values = new object[] { 1 } } }; diff --git a/src/UnitTests/Filtering/SimpleFilterEventAcceptanceTests.cs b/src/AzureEventGridSimulator.Tests/Unit/Filtering/SimpleFilterEventAcceptanceTests.cs similarity index 85% rename from src/UnitTests/Filtering/SimpleFilterEventAcceptanceTests.cs rename to src/AzureEventGridSimulator.Tests/Unit/Filtering/SimpleFilterEventAcceptanceTests.cs index 665bd18..9521da0 100644 --- a/src/UnitTests/Filtering/SimpleFilterEventAcceptanceTests.cs +++ b/src/AzureEventGridSimulator.Tests/Unit/Filtering/SimpleFilterEventAcceptanceTests.cs @@ -4,7 +4,7 @@ using Shouldly; using Xunit; -namespace UnitTests.Filtering +namespace AzureEventGridSimulator.Tests.Unit.Filtering { public class SimpleFilterEventAcceptanceTests { @@ -18,9 +18,9 @@ public void TestDefaultFilterSettingsAcceptsDefaultGridEvent() } [Theory] - [InlineData(data: null)] - [InlineData(data: new object[] { new[] { "All" } })] - [InlineData(data: new object[] { new[] { "This.is.a.test" } })] + [InlineData(null)] + [InlineData(new object[] { new[] { "All" } })] + [InlineData(new object[] { new[] { "This.is.a.test" } })] public void TestEventTypeFilteringSuccess(string[] includedEventTypes) { var filterConfig = new FilterSetting { IncludedEventTypes = includedEventTypes }; @@ -30,12 +30,12 @@ public void TestEventTypeFilteringSuccess(string[] includedEventTypes) } [Theory] - [InlineData(data: new object[] { new[] { "This" } })] - [InlineData(data: new object[] { new[] { "this.is.a.test" } })] - [InlineData(data: new object[] { new[] { "THIS.IS.A.TEST" } })] - [InlineData(data: new object[] { new[] { "this.is.a.test.event" } })] - [InlineData(data: new object[] { new[] { "this.is.a.testevent" } })] - [InlineData(data: new object[] { new string[0] })] + [InlineData(new object[] { new[] { "This" } })] + [InlineData(new object[] { new[] { "this.is.a.test" } })] + [InlineData(new object[] { new[] { "THIS.IS.A.TEST" } })] + [InlineData(new object[] { new[] { "this.is.a.test.event" } })] + [InlineData(new object[] { new[] { "this.is.a.testevent" } })] + [InlineData(new object[] { new string[0] })] public void TestEventTypeFilteringFailure(string[] includedEventTypes) { var filterConfig = new FilterSetting { IncludedEventTypes = includedEventTypes }; diff --git a/src/AzureEventGridSimulator.Tests/appsettings.tests.json b/src/AzureEventGridSimulator.Tests/appsettings.tests.json new file mode 100644 index 0000000..296acc0 --- /dev/null +++ b/src/AzureEventGridSimulator.Tests/appsettings.tests.json @@ -0,0 +1,17 @@ +{ + "topics": [ + { + "name": "ATopicWithATestSubscriber", + "port": 60101, + "key": "TheLocal+DevelopmentKey=", + "subscribers": [ + { + "name": "RequestCatcherSubscription", + "endpoint": "https://azureeventgridsimulator.requestcatcher.com", + "disableValidation": true, + "disabled": true + } + ] + } + ] +} diff --git a/src/AzureEventGridSimulator.sln b/src/AzureEventGridSimulator.sln index 491959e..a43f1fe 100644 --- a/src/AzureEventGridSimulator.sln +++ b/src/AzureEventGridSimulator.sln @@ -10,7 +10,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AzureEventGridSimulator", "AzureEventGridSimulator\AzureEventGridSimulator.csproj", "{EC446656-AD84-494F-90DC-125C8691319D}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnitTests", "UnitTests\UnitTests.csproj", "{DAFFD648-3273-4BF0-BE5D-27347C048603}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AzureEventGridSimulator.Tests", "AzureEventGridSimulator.Tests\AzureEventGridSimulator.Tests.csproj", "{DAFFD648-3273-4BF0-BE5D-27347C048603}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/src/AzureEventGridSimulator/AzureEventGridSimulator.csproj b/src/AzureEventGridSimulator/AzureEventGridSimulator.csproj index 29b534d..8a78e7e 100644 --- a/src/AzureEventGridSimulator/AzureEventGridSimulator.csproj +++ b/src/AzureEventGridSimulator/AzureEventGridSimulator.csproj @@ -1,8 +1,10 @@  - netcoreapp3.1 + net5.0 true + default + true @@ -13,18 +15,20 @@ - - + + + - + - + PreserveNewest + Never PreserveNewest diff --git a/src/AzureEventGridSimulator/Controllers/NotificationController.cs b/src/AzureEventGridSimulator/Controllers/NotificationController.cs index 067bc3f..95ce494 100644 --- a/src/AzureEventGridSimulator/Controllers/NotificationController.cs +++ b/src/AzureEventGridSimulator/Controllers/NotificationController.cs @@ -1,25 +1,21 @@ -using System.Linq; -using System.Net; +using System.Linq; using System.Threading.Tasks; +using AzureEventGridSimulator.Domain; using AzureEventGridSimulator.Domain.Commands; using AzureEventGridSimulator.Domain.Entities; -using AzureEventGridSimulator.Infrastructure; using AzureEventGridSimulator.Infrastructure.Extensions; using AzureEventGridSimulator.Infrastructure.Settings; using MediatR; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Http.Extensions; using Microsoft.AspNetCore.Mvc; using Newtonsoft.Json; namespace AzureEventGridSimulator.Controllers { [Route("/api/events")] + [ApiVersion(Constants.SupportedApiVersion)] [ApiController] public class NotificationController : ControllerBase { - private const string SUPPORTED_API_VERSION = "2018-01-01"; - private readonly IMediator _mediator; private readonly SimulatorSettings _simulatorSettings; @@ -31,22 +27,13 @@ public NotificationController(SimulatorSettings simulatorSettings, } [HttpPost] - public async Task Post([FromQuery(Name = "api-version")] string apiVersion) + public async Task Post() { - if (!string.IsNullOrWhiteSpace(apiVersion) && !string.Equals(SUPPORTED_API_VERSION, apiVersion)) - { - return BadRequest(new ErrorMessage( - HttpStatusCode.BadRequest, - $"The HTTP resource that matches the request URI '{HttpContext.Request.GetDisplayUrl()}' does not support the API version '{apiVersion}'.", - "UnsupportedApiVersion")); - } - - var topicSettingsForCurrentRequestPort = _simulatorSettings.Topics.First(t => t.Port == HttpContext.Connection.LocalPort); - var eventsFromCurrentRequestBody = JsonConvert.DeserializeObject(HttpContext.RequestBody().Result); + var topicSettingsForCurrentRequestPort = _simulatorSettings.Topics.First(t => t.Port == HttpContext.Request.Host.Port); + var eventsFromCurrentRequestBody = JsonConvert.DeserializeObject(await HttpContext.RequestBody()); await _mediator.Send(new SendNotificationEventsToSubscriberCommand(eventsFromCurrentRequestBody, topicSettingsForCurrentRequestPort)); - Response.Headers.Add("api-supported-versions", SUPPORTED_API_VERSION); return Ok(); } } diff --git a/src/AzureEventGridSimulator/Controllers/SubscriptionValidationController.cs b/src/AzureEventGridSimulator/Controllers/SubscriptionValidationController.cs index 5ac131c..329d3fd 100644 --- a/src/AzureEventGridSimulator/Controllers/SubscriptionValidationController.cs +++ b/src/AzureEventGridSimulator/Controllers/SubscriptionValidationController.cs @@ -2,6 +2,7 @@ using System.Linq; using System.Net; using System.Threading.Tasks; +using AzureEventGridSimulator.Domain; using AzureEventGridSimulator.Domain.Commands; using AzureEventGridSimulator.Infrastructure; using AzureEventGridSimulator.Infrastructure.Settings; @@ -11,11 +12,12 @@ namespace AzureEventGridSimulator.Controllers { [Route("/validate")] + [ApiVersion(Constants.SupportedApiVersion)] [ApiController] public class SubscriptionValidationController : ControllerBase { - private readonly SimulatorSettings _simulatorSettings; private readonly IMediator _mediator; + private readonly SimulatorSettings _simulatorSettings; public SubscriptionValidationController(SimulatorSettings simulatorSettings, IMediator mediator) @@ -27,7 +29,7 @@ public SubscriptionValidationController(SimulatorSettings simulatorSettings, [HttpGet] public async Task Get(Guid id) { - var topicSettingsForCurrentRequestPort = _simulatorSettings.Topics.First(t => t.Port == HttpContext.Connection.LocalPort); + var topicSettingsForCurrentRequestPort = _simulatorSettings.Topics.First(t => t.Port == HttpContext.Request.Host.Port); var isValid = await _mediator.Send(new ValidateSubscriptionCommand(topicSettingsForCurrentRequestPort, id)); if (!isValid) diff --git a/src/AzureEventGridSimulator/Domain/Commands/SendNotificationEventsToSubscriberCommandHandler.cs b/src/AzureEventGridSimulator/Domain/Commands/SendNotificationEventsToSubscriberCommandHandler.cs index b95e2ba..86c319b 100644 --- a/src/AzureEventGridSimulator/Domain/Commands/SendNotificationEventsToSubscriberCommandHandler.cs +++ b/src/AzureEventGridSimulator/Domain/Commands/SendNotificationEventsToSubscriberCommandHandler.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Net.Http; using System.Text; @@ -14,6 +15,7 @@ namespace AzureEventGridSimulator.Domain.Commands { // ReSharper disable once UnusedMember.Global + // ReSharper disable once UnusedType.Global public class SendNotificationEventsToSubscriberCommandHandler : AsyncRequestHandler { private readonly IHttpClientFactory _httpClientFactory; @@ -72,24 +74,24 @@ protected override Task Handle(SendNotificationEventsToSubscriberCommand request return Task.CompletedTask; } - private async Task SendToSubscriber(SubscriptionSettings subscription, EventGridEvent[] events, string topicName) + private async Task SendToSubscriber(SubscriptionSettings subscription, IEnumerable events, string topicName) { try { if (subscription.Disabled) { - _logger.LogWarning("Subscription '{SubscriberName}' on topic '{TopicName}' is disabled and so Notification was skipped.", subscription.Name, topicName); + _logger.LogWarning("Subscription '{SubscriberName}' on topic '{TopicName}' is disabled and so Notification was skipped", subscription.Name, topicName); return; } if (!subscription.DisableValidation && subscription.ValidationStatus != SubscriptionValidationStatus.ValidationSuccessful) { - _logger.LogWarning("Subscription '{SubscriberName}' on topic '{TopicName}' can't receive events. It's still pending validation.", subscription.Name, topicName); + _logger.LogWarning("Subscription '{SubscriberName}' on topic '{TopicName}' can't receive events. It's still pending validation", subscription.Name, topicName); return; } - _logger.LogDebug("Sending to subscriber '{SubscriberName}' on topic '{TopicName}'.", subscription.Name, topicName); + _logger.LogDebug("Sending to subscriber '{SubscriberName}' on topic '{TopicName}'", subscription.Name, topicName); // "Event Grid sends the events to subscribers in an array that has a single event. This behaviour may change in the future." // https://docs.microsoft.com/en-us/azure/event-grid/event-schema @@ -101,11 +103,11 @@ private async Task SendToSubscriber(SubscriptionSettings subscription, EventGrid var json = JsonConvert.SerializeObject(new[] { evt }, Formatting.Indented); using var content = new StringContent(json, Encoding.UTF8, "application/json"); var httpClient = _httpClientFactory.CreateClient(); - httpClient.DefaultRequestHeaders.Add("aeg-event-type", "Notification"); - httpClient.DefaultRequestHeaders.Add("aeg-subscription-name", subscription.Name.ToUpperInvariant()); - httpClient.DefaultRequestHeaders.Add("aeg-data-version", evt.DataVersion); - httpClient.DefaultRequestHeaders.Add("aeg-metadata-version", evt.MetadataVersion); - httpClient.DefaultRequestHeaders.Add("aeg-delivery-count", "0"); // TODO implement re-tries + httpClient.DefaultRequestHeaders.Add(Constants.AegEventTypeHeader, Constants.NotificationEventType); + httpClient.DefaultRequestHeaders.Add(Constants.AegSubscriptionNameHeader, subscription.Name.ToUpperInvariant()); + httpClient.DefaultRequestHeaders.Add(Constants.AegDataVersionHeader, evt.DataVersion); + httpClient.DefaultRequestHeaders.Add(Constants.AegMetadataVersionHeader, evt.MetadataVersion); + httpClient.DefaultRequestHeaders.Add(Constants.AegDeliveryCountHeader, "0"); // TODO implement re-tries httpClient.Timeout = TimeSpan.FromSeconds(60); await httpClient.PostAsync(subscription.Endpoint, content) @@ -113,13 +115,13 @@ await httpClient.PostAsync(subscription.Endpoint, content) } else { - _logger.LogDebug("Event {EventId} filtered out for subscriber '{SubscriberName}'.", evt.Id, subscription.Name); + _logger.LogDebug("Event {EventId} filtered out for subscriber '{SubscriberName}'", evt.Id, subscription.Name); } } } catch (Exception ex) { - _logger.LogError(ex, "Failed to send to subscriber '{SubscriberName}'.", subscription.Name); + _logger.LogError(ex, "Failed to send to subscriber '{SubscriberName}'", subscription.Name); } } @@ -127,12 +129,12 @@ private void LogResult(Task task, EventGridEvent evt, Subsc { if (task.IsCompletedSuccessfully && task.Result.IsSuccessStatusCode) { - _logger.LogDebug("Event {EventId} sent to subscriber '{SubscriberName}' on topic '{TopicName}' successfully.", evt.Id, subscription.Name, topicName); + _logger.LogDebug("Event {EventId} sent to subscriber '{SubscriberName}' on topic '{TopicName}' successfully", evt.Id, subscription.Name, topicName); } else { _logger.LogError(task.Exception?.GetBaseException(), - "Failed to send event {EventId} to subscriber '{SubscriberName}', '{TaskStatus}', '{Reason}'.", + "Failed to send event {EventId} to subscriber '{SubscriberName}', '{TaskStatus}', '{Reason}'", evt.Id, subscription.Name, task.Status.ToString(), diff --git a/src/AzureEventGridSimulator/Domain/Commands/ValidateAllSubscriptionsCommand.cs b/src/AzureEventGridSimulator/Domain/Commands/ValidateAllSubscriptionsCommand.cs index 980b3d4..dadc57e 100644 --- a/src/AzureEventGridSimulator/Domain/Commands/ValidateAllSubscriptionsCommand.cs +++ b/src/AzureEventGridSimulator/Domain/Commands/ValidateAllSubscriptionsCommand.cs @@ -4,6 +4,5 @@ namespace AzureEventGridSimulator.Domain.Commands { public class ValidateAllSubscriptionsCommand : IRequest { - } } diff --git a/src/AzureEventGridSimulator/Domain/Commands/ValidateAllSubscriptionsCommandHandler.cs b/src/AzureEventGridSimulator/Domain/Commands/ValidateAllSubscriptionsCommandHandler.cs index 3137fa1..5c3ac17 100644 --- a/src/AzureEventGridSimulator/Domain/Commands/ValidateAllSubscriptionsCommandHandler.cs +++ b/src/AzureEventGridSimulator/Domain/Commands/ValidateAllSubscriptionsCommandHandler.cs @@ -14,10 +14,11 @@ namespace AzureEventGridSimulator.Domain.Commands { + // ReSharper disable once UnusedType.Global public class ValidateAllSubscriptionsCommandHandler : IRequestHandler { - private readonly ILogger _logger; private readonly IHttpClientFactory _httpClientFactory; + private readonly ILogger _logger; private readonly SimulatorSettings _simulatorSettings; private readonly ValidationIpAddress _validationIpAddress; @@ -53,7 +54,7 @@ private async Task ValidateSubscription(TopicSettings topic, SubscriptionSetting try { - _logger.LogDebug("Sending subscription validation event to subscriber '{SubscriberName}'.", subscription.Name); + _logger.LogDebug("Sending subscription validation event to subscriber '{SubscriberName}'", subscription.Name); var evt = new EventGridEvent { @@ -74,11 +75,11 @@ private async Task ValidateSubscription(TopicSettings topic, SubscriptionSetting var json = JsonConvert.SerializeObject(new[] { evt }, Formatting.Indented); using var content = new StringContent(json, Encoding.UTF8, "application/json"); using var httpClient = _httpClientFactory.CreateClient(); - httpClient.DefaultRequestHeaders.Add("aeg-event-type", "SubscriptionValidation"); - httpClient.DefaultRequestHeaders.Add("aeg-subscription-name", subscription.Name.ToUpperInvariant()); - httpClient.DefaultRequestHeaders.Add("aeg-data-version", evt.DataVersion); - httpClient.DefaultRequestHeaders.Add("aeg-metadata-version", evt.MetadataVersion); - httpClient.DefaultRequestHeaders.Add("aeg-delivery-count", "0"); // TODO implement re-tries + httpClient.DefaultRequestHeaders.Add(Constants.AegEventTypeHeader, Constants.ValidationEventType); + httpClient.DefaultRequestHeaders.Add(Constants.AegSubscriptionNameHeader, subscription.Name.ToUpperInvariant()); + httpClient.DefaultRequestHeaders.Add(Constants.AegDataVersionHeader, evt.DataVersion); + httpClient.DefaultRequestHeaders.Add(Constants.AegMetadataVersionHeader, evt.MetadataVersion); + httpClient.DefaultRequestHeaders.Add(Constants.AegDeliveryCountHeader, "0"); // TODO implement re-tries httpClient.Timeout = TimeSpan.FromSeconds(60); subscription.ValidationStatus = SubscriptionValidationStatus.ValidationEventSent; @@ -92,18 +93,18 @@ private async Task ValidateSubscription(TopicSettings topic, SubscriptionSetting if (validationResponse.ValidationResponse == subscription.ValidationCode) { subscription.ValidationStatus = SubscriptionValidationStatus.ValidationSuccessful; - _logger.LogInformation("Successfully validated subscriber '{SubscriberName}'.", subscription.Name); + _logger.LogInformation("Successfully validated subscriber '{SubscriberName}'", subscription.Name); return; } } catch (Exception ex) { - _logger.LogError("Failed to validate subscriber '{SubscriberName}'. Note that subscriber must be started before the simulator. Or you can disable validation for this subscriber via settings: '{Error}'", subscription.Name, ex.Message); + _logger.LogError("Failed to validate subscriber '{SubscriberName}'. Note that subscriber must be started before the simulator. Or you can disable validation for this subscriber via settings: '{Error}'", + subscription.Name, ex.Message); _logger.LogInformation("'{SubscriberName}' manual validation url: {ValidationUrl}", subscription.Name, validationUrl); } subscription.ValidationStatus = SubscriptionValidationStatus.ValidationFailed; } - } } diff --git a/src/AzureEventGridSimulator/Domain/Commands/ValidateSubscriptionCommandHandler.cs b/src/AzureEventGridSimulator/Domain/Commands/ValidateSubscriptionCommandHandler.cs index bf8e0bc..2923625 100644 --- a/src/AzureEventGridSimulator/Domain/Commands/ValidateSubscriptionCommandHandler.cs +++ b/src/AzureEventGridSimulator/Domain/Commands/ValidateSubscriptionCommandHandler.cs @@ -7,7 +7,7 @@ namespace AzureEventGridSimulator.Domain.Commands { - // ReSharper disable once UnusedMember.Global + // ReSharper disable once UnusedType.Global public class ValidateSubscriptionCommandHandler : IRequestHandler { private readonly ILogger _logger; @@ -26,12 +26,12 @@ public Task Handle(ValidateSubscriptionCommand request, CancellationToken !subscriber.ValidationPeriodExpired) { subscriber.ValidationStatus = SubscriptionValidationStatus.ValidationSuccessful; - _logger.LogInformation("Subscription {SubscriptionName} on topic {TopicName} was successfully validated.", subscriber.Name, request.Topic.Name); + _logger.LogInformation("Subscription {SubscriptionName} on topic {TopicName} was successfully validated", subscriber.Name, request.Topic.Name); return Task.FromResult(true); } - _logger.LogWarning("Validation failed for code {ValidationCode} on topic {TopicName}.", request.ValidationCode, request.Topic?.Name); + _logger.LogWarning("Validation failed for code {ValidationCode} on topic {TopicName}", request.ValidationCode, request.Topic?.Name); return Task.FromResult(false); } } diff --git a/src/AzureEventGridSimulator/Domain/Constants.cs b/src/AzureEventGridSimulator/Domain/Constants.cs new file mode 100644 index 0000000..1016b2a --- /dev/null +++ b/src/AzureEventGridSimulator/Domain/Constants.cs @@ -0,0 +1,22 @@ +namespace AzureEventGridSimulator.Domain +{ + public static class Constants + { + // Headers + public const string AegSasTokenHeader = "aeg-sas-token"; + public const string AegSasKeyHeader = "aeg-sas-key"; + public const string AegEventTypeHeader = "aeg-event-type"; + public const string AegSubscriptionNameHeader = "aeg-subscription-name"; + public const string AegDataVersionHeader = "aeg-data-version"; + public const string AegMetadataVersionHeader = "aeg-metadata-version"; + public const string AegDeliveryCountHeader = "aeg-delivery-count"; + + // Event Types + public const string NotificationEventType = "Notification"; + public const string ValidationEventType = "SubscriptionValidation"; + + // Other + public const string SupportedApiVersion = "2018-01-01"; + public const string SasAuthorizationType = "SharedAccessSignature"; + } +} diff --git a/src/AzureEventGridSimulator/Domain/Entities/EventGridEvent.cs b/src/AzureEventGridSimulator/Domain/Entities/EventGridEvent.cs index e5238d5..40bf84b 100644 --- a/src/AzureEventGridSimulator/Domain/Entities/EventGridEvent.cs +++ b/src/AzureEventGridSimulator/Domain/Entities/EventGridEvent.cs @@ -41,10 +41,10 @@ public class EventGridEvent public string EventTime { get; set; } [JsonIgnore] - public DateTime EventTimeParsed => DateTime.Parse(EventTime); + private DateTime EventTimeParsed => DateTime.Parse(EventTime); [JsonIgnore] - public bool EventTimeIsValid => DateTime.TryParse(EventTime, out _); + private bool EventTimeIsValid => DateTime.TryParse(EventTime, out _); /// /// Gets or sets the schema version of the data object. diff --git a/src/AzureEventGridSimulator/Domain/Services/SubscriptionValidationRequest.cs b/src/AzureEventGridSimulator/Domain/Services/SubscriptionValidationRequest.cs index 0ee1b4f..1afc6c7 100644 --- a/src/AzureEventGridSimulator/Domain/Services/SubscriptionValidationRequest.cs +++ b/src/AzureEventGridSimulator/Domain/Services/SubscriptionValidationRequest.cs @@ -11,4 +11,4 @@ public class SubscriptionValidationRequest [JsonProperty(PropertyName = "validationUrl", Required = Required.Default)] public string ValidationUrl { get; set; } } -} \ No newline at end of file +} diff --git a/src/AzureEventGridSimulator/Domain/Services/SubscriptionValidationResponse.cs b/src/AzureEventGridSimulator/Domain/Services/SubscriptionValidationResponse.cs index 6e88b3b..40729d6 100644 --- a/src/AzureEventGridSimulator/Domain/Services/SubscriptionValidationResponse.cs +++ b/src/AzureEventGridSimulator/Domain/Services/SubscriptionValidationResponse.cs @@ -8,4 +8,4 @@ public class SubscriptionValidationResponse [JsonProperty(PropertyName = "validationResponse", Required = Required.Always)] public Guid ValidationResponse { get; set; } } -} \ No newline at end of file +} diff --git a/src/AzureEventGridSimulator/Infrastructure/Extensions/CollectionExtensions.cs b/src/AzureEventGridSimulator/Infrastructure/Extensions/CollectionExtensions.cs index 87614f0..ef663e0 100644 --- a/src/AzureEventGridSimulator/Infrastructure/Extensions/CollectionExtensions.cs +++ b/src/AzureEventGridSimulator/Infrastructure/Extensions/CollectionExtensions.cs @@ -15,7 +15,7 @@ public static string Separate(this ICollection collection, string separato { toStringFunction ??= t => t.ToString(); - return string.Join(separator, (collection ?? new T[0]).Select(c => toStringFunction(c))); + return string.Join(separator, (collection ?? Array.Empty()).Select(c => toStringFunction(c))); } } } diff --git a/src/AzureEventGridSimulator/Infrastructure/Extensions/HttpContextExtensions.cs b/src/AzureEventGridSimulator/Infrastructure/Extensions/HttpContextExtensions.cs index 10e61ad..c82b26b 100644 --- a/src/AzureEventGridSimulator/Infrastructure/Extensions/HttpContextExtensions.cs +++ b/src/AzureEventGridSimulator/Infrastructure/Extensions/HttpContextExtensions.cs @@ -1,6 +1,9 @@ using System.IO; +using System.Net; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; +using Microsoft.Net.Http.Headers; +using Newtonsoft.Json; namespace AzureEventGridSimulator.Infrastructure.Extensions { @@ -8,9 +11,24 @@ public static class HttpContextExtensions { public static async Task RequestBody(this HttpContext context) { - var reader = new StreamReader(context.Request.Body); + var reader = new StreamReader(context.Request.Body); reader.BaseStream.Seek(0, SeekOrigin.Begin); - return await reader.ReadToEndAsync(); + + var responseString = await reader.ReadToEndAsync(); + reader.BaseStream.Seek(0, SeekOrigin.Begin); + + return responseString; + } + + public static async Task WriteErrorResponse(this HttpContext context, HttpStatusCode statusCode, string errorMessage, string code) + { + var error = new ErrorMessage(statusCode, errorMessage, code); + + context.Response.Headers.Add(HeaderNames.ContentType, "application/json"); + + context.Response.StatusCode = (int)statusCode; + // ReSharper disable once MethodHasAsyncOverload + await context.Response.WriteAsync(JsonConvert.SerializeObject(error, Formatting.Indented)); } } } diff --git a/src/AzureEventGridSimulator/Infrastructure/Extensions/HttpResponseExtensions.cs b/src/AzureEventGridSimulator/Infrastructure/Extensions/HttpResponseExtensions.cs deleted file mode 100644 index 29e8c04..0000000 --- a/src/AzureEventGridSimulator/Infrastructure/Extensions/HttpResponseExtensions.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.Net; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Http; -using Newtonsoft.Json; - -namespace AzureEventGridSimulator.Infrastructure.Extensions -{ - public static class HttpResponseExtensions - { - public static async Task ErrorResponse(this HttpResponse response, HttpStatusCode statusCode, string errorMessage, string code) - { - var error = new ErrorMessage(statusCode, errorMessage, code); - - response.Headers.Add("Content-type", "application/json"); - - response.StatusCode = (int)statusCode; - // ReSharper disable once MethodHasAsyncOverload - await response.WriteAsync(JsonConvert.SerializeObject(error, Formatting.Indented)); - } - } -} diff --git a/src/AzureEventGridSimulator/Infrastructure/Extensions/SubscriptionSettingsFilterExtensions.cs b/src/AzureEventGridSimulator/Infrastructure/Extensions/SubscriptionSettingsFilterExtensions.cs index c94ed4b..3ae4f06 100644 --- a/src/AzureEventGridSimulator/Infrastructure/Extensions/SubscriptionSettingsFilterExtensions.cs +++ b/src/AzureEventGridSimulator/Infrastructure/Extensions/SubscriptionSettingsFilterExtensions.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics.CodeAnalysis; using System.Linq; using AzureEventGridSimulator.Domain.Entities; using AzureEventGridSimulator.Infrastructure.Settings; @@ -32,12 +33,12 @@ public static bool AcceptsEvent(this FilterSetting filter, EventGridEvent gridEv && (string.IsNullOrWhiteSpace(filter.SubjectEndsWith) || gridEvent.Subject.EndsWith(filter.SubjectEndsWith, filter.IsSubjectCaseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase)); - retVal = retVal && (filter.AdvancedFilters ?? new AdvancedFilterSetting[0]).All(af => af.AcceptsEvent(gridEvent)); + retVal = retVal && (filter.AdvancedFilters ?? Array.Empty()).All(af => af.AcceptsEvent(gridEvent)); return retVal; } - public static bool AcceptsEvent(this AdvancedFilterSetting filter, EventGridEvent gridEvent) + private static bool AcceptsEvent(this AdvancedFilterSetting filter, EventGridEvent gridEvent) { var retVal = filter == null; @@ -67,53 +68,55 @@ public static bool AcceptsEvent(this AdvancedFilterSetting filter, EventGridEven retVal = Try(() => value.ToNumber() <= filter.Value.ToNumber()); break; case AdvancedFilterSetting.OperatorTypeEnum.NumberIn: - retVal = Try(() => (filter.Values ?? new object[0]).Select(v => v.ToNumber()).Contains(value.ToNumber())); + retVal = Try(() => (filter.Values ?? Array.Empty()).Select(v => v.ToNumber()).Contains(value.ToNumber())); break; case AdvancedFilterSetting.OperatorTypeEnum.NumberNotIn: - retVal = Try(() => !(filter.Values ?? new object[0]).Select(v => v.ToNumber()).Contains(value.ToNumber())); + retVal = Try(() => !(filter.Values ?? Array.Empty()).Select(v => v.ToNumber()).Contains(value.ToNumber())); break; case AdvancedFilterSetting.OperatorTypeEnum.BoolEquals: retVal = Try(() => Convert.ToBoolean(value) == Convert.ToBoolean(filter.Value)); break; case AdvancedFilterSetting.OperatorTypeEnum.StringContains: - { - // a string cannot be considered to contain null or and empty string - var valueAsString = value as string; - var filterValueAsString = filter.Value as string; - - retVal = Try(() => !string.IsNullOrEmpty(filterValueAsString) && - !string.IsNullOrEmpty(valueAsString) && - valueAsString.Contains(filterValueAsString, StringComparison.OrdinalIgnoreCase)); - } + { + // a string cannot be considered to contain null or and empty string + var valueAsString = value as string; + var filterValueAsString = filter.Value as string; + + retVal = Try(() => !string.IsNullOrEmpty(filterValueAsString) && + !string.IsNullOrEmpty(valueAsString) && + valueAsString.Contains(filterValueAsString, StringComparison.OrdinalIgnoreCase)); + } break; case AdvancedFilterSetting.OperatorTypeEnum.StringBeginsWith: - { - // null or empty values cannot be considered to be the beginning character of a string - var valueAsString = value as string; - var filterValueAsString = filter.Value as string; - - retVal = Try(() => !string.IsNullOrEmpty(filterValueAsString) && - !string.IsNullOrEmpty(valueAsString) && - valueAsString.StartsWith(filterValueAsString, StringComparison.OrdinalIgnoreCase)); - } + { + // null or empty values cannot be considered to be the beginning character of a string + var valueAsString = value as string; + var filterValueAsString = filter.Value as string; + + retVal = Try(() => !string.IsNullOrEmpty(filterValueAsString) && + !string.IsNullOrEmpty(valueAsString) && + valueAsString.StartsWith(filterValueAsString, StringComparison.OrdinalIgnoreCase)); + } break; case AdvancedFilterSetting.OperatorTypeEnum.StringEndsWith: - { - // null or empty values cannot be considered to be the end character of a string - var valueAsString = value as string; - var filterValueAsString = filter.Value as string; - - retVal = Try(() => !string.IsNullOrEmpty(filterValueAsString) && - !string.IsNullOrEmpty(valueAsString) && - valueAsString.EndsWith(filterValueAsString, StringComparison.OrdinalIgnoreCase)); - } + { + // null or empty values cannot be considered to be the end character of a string + var valueAsString = value as string; + var filterValueAsString = filter.Value as string; + + retVal = Try(() => !string.IsNullOrEmpty(filterValueAsString) && + !string.IsNullOrEmpty(valueAsString) && + valueAsString.EndsWith(filterValueAsString, StringComparison.OrdinalIgnoreCase)); + } break; case AdvancedFilterSetting.OperatorTypeEnum.StringIn: - retVal = Try(() => (filter.Values ?? new object[0]).Select(v => Convert.ToString(v)?.ToUpper()).Contains(Convert.ToString(value)?.ToUpper())); + retVal = Try(() => (filter.Values ?? Array.Empty()).Select(v => Convert.ToString(v)?.ToUpper()).Contains(Convert.ToString(value)?.ToUpper())); break; case AdvancedFilterSetting.OperatorTypeEnum.StringNotIn: - retVal = Try(() => !(filter.Values ?? new object[0]).Select(v => Convert.ToString(v)?.ToUpper()).Contains(Convert.ToString(value)?.ToUpper())); + retVal = Try(() => !(filter.Values ?? Array.Empty()).Select(v => Convert.ToString(v)?.ToUpper()).Contains(Convert.ToString(value)?.ToUpper())); break; + default: + throw new ArgumentOutOfRangeException(nameof(AdvancedFilterSetting.OperatorTypeEnum), "Unknown filter operator"); } return retVal; @@ -141,7 +144,8 @@ private static bool Try(Func function, bool valueOnException = false) } } - public static bool TryGetValue(this EventGridEvent gridEvent, string key, out object value) + [SuppressMessage("ReSharper", "InvertIf")] + private static bool TryGetValue(this EventGridEvent gridEvent, string key, out object value) { var retval = false; value = null; @@ -176,10 +180,10 @@ public static bool TryGetValue(this EventGridEvent gridEvent, string key, out ob break; default: var split = key.Split('.'); - if (split[0] == (nameof(gridEvent.Data)) && gridEvent.Data != null && split.Length > 1) + if (split[0] == nameof(gridEvent.Data) && gridEvent.Data != null && split.Length > 1) { var tmpValue = gridEvent.Data; - for (int i = 0; i < split.Length; i++) + for (var i = 0; i < split.Length; i++) { // look for the property on the grid event data object if (JObject.FromObject(tmpValue).TryGetValue(split[i], out var dataValue)) @@ -193,6 +197,7 @@ public static bool TryGetValue(this EventGridEvent gridEvent, string key, out ob } } } + break; } } diff --git a/src/AzureEventGridSimulator/Infrastructure/Middleware/EventGridMiddleware.cs b/src/AzureEventGridSimulator/Infrastructure/Middleware/EventGridMiddleware.cs index 5f73047..7a5c31f 100644 --- a/src/AzureEventGridSimulator/Infrastructure/Middleware/EventGridMiddleware.cs +++ b/src/AzureEventGridSimulator/Infrastructure/Middleware/EventGridMiddleware.cs @@ -11,6 +11,7 @@ namespace AzureEventGridSimulator.Infrastructure.Middleware { + // ReSharper disable once ClassNeverInstantiated.Global public class EventGridMiddleware { private readonly RequestDelegate _next; @@ -39,7 +40,7 @@ public async Task InvokeAsync(HttpContext context, } // This is the end of the line. - await context.Response.ErrorResponse(HttpStatusCode.BadRequest, "Request not supported.", null); + await context.WriteErrorResponse(HttpStatusCode.BadRequest, "Request not supported.", null); } private async Task ValidateSubscriptionValidationRequest(HttpContext context) @@ -48,7 +49,7 @@ private async Task ValidateSubscriptionValidationRequest(HttpContext context) if (string.IsNullOrWhiteSpace(id)) { - await context.Response.ErrorResponse(HttpStatusCode.BadRequest, "The request did not contain a validation code.", null); + await context.WriteErrorResponse(HttpStatusCode.BadRequest, "The request did not contain a validation code.", null); return; } @@ -56,11 +57,11 @@ private async Task ValidateSubscriptionValidationRequest(HttpContext context) } private async Task ValidateNotificationRequest(HttpContext context, - SimulatorSettings simulatorSettings, - SasKeyValidator sasHeaderValidator, - ILogger logger) + SimulatorSettings simulatorSettings, + SasKeyValidator sasHeaderValidator, + ILogger logger) { - var topic = simulatorSettings.Topics.First(t => t.Port == context.Connection.LocalPort); + var topic = simulatorSettings.Topics.First(t => t.Port == context.Request.Host.Port); // // Validate the key/ token supplied in the header. @@ -68,7 +69,7 @@ private async Task ValidateNotificationRequest(HttpContext context, if (!string.IsNullOrWhiteSpace(topic.Key) && !sasHeaderValidator.IsValid(context.Request.Headers, topic.Key)) { - await context.Response.ErrorResponse(HttpStatusCode.Unauthorized, "The request did not contain a valid aeg-sas-key or aeg-sas-token.", null); + await context.WriteErrorResponse(HttpStatusCode.Unauthorized, "The request did not contain a valid aeg-sas-key or aeg-sas-token.", null); return; } @@ -82,13 +83,13 @@ private async Task ValidateNotificationRequest(HttpContext context, const int maximumAllowedOverallMessageSizeInBytes = 1536000; const int maximumAllowedEventGridEventSizeInBytes = 66560; - logger.LogTrace("Message is {Bytes} in length.", requestBody.Length); + logger.LogTrace("Message is {Bytes} in length", requestBody.Length); if (requestBody.Length > maximumAllowedOverallMessageSizeInBytes) { - logger.LogError("Payload is larger than the allowed maximum."); + logger.LogError("Payload is larger than the allowed maximum"); - await context.Response.ErrorResponse(HttpStatusCode.RequestEntityTooLarge, "Payload is larger than the allowed maximum.", null); + await context.WriteErrorResponse(HttpStatusCode.RequestEntityTooLarge, "Payload is larger than the allowed maximum.", null); return; } @@ -97,17 +98,18 @@ private async Task ValidateNotificationRequest(HttpContext context, // ReSharper disable once MethodHasAsyncOverload var eventSize = JsonConvert.SerializeObject(evt, Formatting.None).Length; - logger.LogTrace("Event is {Bytes} in length.", eventSize); + logger.LogTrace("Event is {Bytes} in length", eventSize); - if (eventSize > maximumAllowedEventGridEventSizeInBytes) + if (eventSize <= maximumAllowedEventGridEventSizeInBytes) { - logger.LogError("Event is larger than the allowed maximum."); - - await context.Response.ErrorResponse(HttpStatusCode.RequestEntityTooLarge, "Event is larger than the allowed maximum.", null); - return; + continue; } - } + logger.LogError("Event is larger than the allowed maximum"); + + await context.WriteErrorResponse(HttpStatusCode.RequestEntityTooLarge, "Event is larger than the allowed maximum.", null); + return; + } // // Validate the properties of each event. @@ -120,26 +122,26 @@ private async Task ValidateNotificationRequest(HttpContext context, } catch (InvalidOperationException ex) { - logger.LogError(ex, "Event was not valid."); + logger.LogError(ex, "Event was not valid"); - await context.Response.ErrorResponse(HttpStatusCode.BadRequest, ex.Message, null); + await context.WriteErrorResponse(HttpStatusCode.BadRequest, ex.Message, null); return; } - } await _next(context); } - private bool IsNotificationRequest(HttpContext context) + private static bool IsNotificationRequest(HttpContext context) { return context.Request.Headers.Keys.Any(k => string.Equals(k, "Content-Type", StringComparison.OrdinalIgnoreCase)) && - context.Request.Headers["Content-Type"].Any(v => !string.IsNullOrWhiteSpace(v) && v.IndexOf("application/json", StringComparison.OrdinalIgnoreCase) >= 0) && + context.Request.Headers["Content-Type"].Any(v => !string.IsNullOrWhiteSpace(v) && v.Contains("application/json", StringComparison.OrdinalIgnoreCase)) && context.Request.Method == HttpMethods.Post && - string.Equals(context.Request.Path, "/api/events", StringComparison.OrdinalIgnoreCase); + (string.Equals(context.Request.Path, "/api/events", StringComparison.OrdinalIgnoreCase) || + string.Equals(context.Request.Path, "/", StringComparison.OrdinalIgnoreCase)); } - private bool IsValidationRequest(HttpContext context) + private static bool IsValidationRequest(HttpContext context) { return context.Request.Method == HttpMethods.Get && string.Equals(context.Request.Path, "/validate", StringComparison.OrdinalIgnoreCase) && diff --git a/src/AzureEventGridSimulator/Infrastructure/Middleware/RequestLoggingMiddleware.cs b/src/AzureEventGridSimulator/Infrastructure/Middleware/RequestLoggingMiddleware.cs new file mode 100644 index 0000000..14ad936 --- /dev/null +++ b/src/AzureEventGridSimulator/Infrastructure/Middleware/RequestLoggingMiddleware.cs @@ -0,0 +1,55 @@ +using System; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using AzureEventGridSimulator.Domain; +using AzureEventGridSimulator.Infrastructure.Extensions; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Logging; +using Microsoft.Net.Http.Headers; + +namespace AzureEventGridSimulator.Infrastructure.Middleware +{ + // ReSharper disable once ClassNeverInstantiated.Global + public class RequestLoggingMiddleware + { + private readonly RequestDelegate _next; + + public RequestLoggingMiddleware(RequestDelegate next) + { + _next = next; + } + + // ReSharper disable once UnusedMember.Global + public async Task InvokeAsync(HttpContext context, + ILogger logger) + { + var formattedRequest = await FormatRequestForLogging(context); + logger.LogDebug("Request received {Request}", formattedRequest); + + await _next(context); + } + + private static async Task FormatRequestForLogging(HttpContext context) + { + context.Request.EnableBuffering(); + var request = await context.RequestBody(); + var x = new StringBuilder(); + x.AppendLine($"{context.Request.Method} {context.Request.Path}{context.Request.QueryString} {context.Request.Protocol}"); + + var redactedHeaders = new[] { HeaderNames.Authorization, Constants.AegSasKeyHeader, Constants.AegSasTokenHeader }; + + foreach (var (key, stringValues) in context.Request.Headers + .Where(o => !string.IsNullOrWhiteSpace(o.Value.First()))) + { + var value = redactedHeaders.Any(o => string.Equals(key, o, StringComparison.OrdinalIgnoreCase)) ? "--REDACTED--" : stringValues.First(); + + x.AppendLine($"{key}: {value}"); + } + + x.AppendLine(""); + x.Append(request); + return x.ToString(); + } + } +} diff --git a/src/AzureEventGridSimulator/Infrastructure/Middleware/SasKeyValidator.cs b/src/AzureEventGridSimulator/Infrastructure/Middleware/SasKeyValidator.cs index 70bf7e3..af9133e 100644 --- a/src/AzureEventGridSimulator/Infrastructure/Middleware/SasKeyValidator.cs +++ b/src/AzureEventGridSimulator/Infrastructure/Middleware/SasKeyValidator.cs @@ -3,8 +3,10 @@ using System.Security.Cryptography; using System.Text; using System.Web; +using AzureEventGridSimulator.Domain; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; +using Microsoft.Net.Http.Headers; namespace AzureEventGridSimulator.Infrastructure.Middleware { @@ -20,11 +22,11 @@ public SasKeyValidator(ILogger logger) public bool IsValid(IHeaderDictionary requestHeaders, string topicKey) { if (requestHeaders - .Any(h => string.Equals("aeg-sas-key", h.Key, StringComparison.OrdinalIgnoreCase))) + .Any(h => string.Equals(Constants.AegSasKeyHeader, h.Key, StringComparison.OrdinalIgnoreCase))) { - if (!string.Equals(requestHeaders["aeg-sas-key"], topicKey)) + if (!string.Equals(requestHeaders[Constants.AegSasKeyHeader], topicKey)) { - _logger.LogError("'aeg-sas-key' value did not match configured value!"); + _logger.LogError("'aeg-sas-key' value did not match the expected value!"); return false; } @@ -33,12 +35,12 @@ public bool IsValid(IHeaderDictionary requestHeaders, string topicKey) } if (requestHeaders - .Any(h => string.Equals("aeg-sas-token", h.Key, StringComparison.OrdinalIgnoreCase))) + .Any(h => string.Equals(Constants.AegSasTokenHeader, h.Key, StringComparison.OrdinalIgnoreCase))) { - var token = requestHeaders["aeg-sas-token"]; + var token = requestHeaders[Constants.AegSasTokenHeader].First(); if (!TokenIsValid(token, topicKey)) { - _logger.LogError("'aeg-sas-key' value did not match configured value!"); + _logger.LogError("'aeg-sas-token' value did not match the expected value!"); return false; } @@ -46,6 +48,21 @@ public bool IsValid(IHeaderDictionary requestHeaders, string topicKey) return true; } + // ReSharper disable once InvertIf + if (requestHeaders + .Any(h => string.Equals(HeaderNames.Authorization, h.Key, StringComparison.OrdinalIgnoreCase))) + { + var token = requestHeaders[HeaderNames.Authorization].ToString(); + if (token.StartsWith(Constants.SasAuthorizationType) && !TokenIsValid(token.Replace(Constants.SasAuthorizationType, "").Trim(), topicKey)) + { + _logger.LogError("'Authorization: SharedAccessSignature' value did not match the expected value!"); + return false; + } + + _logger.LogTrace("'Authorization: SharedAccessSignature' header is valid"); + return true; + } + return false; } diff --git a/src/AzureEventGridSimulator/Infrastructure/Settings/AdvancedFilterSetting.cs b/src/AzureEventGridSimulator/Infrastructure/Settings/AdvancedFilterSetting.cs index 7566ad1..09c717c 100644 --- a/src/AzureEventGridSimulator/Infrastructure/Settings/AdvancedFilterSetting.cs +++ b/src/AzureEventGridSimulator/Infrastructure/Settings/AdvancedFilterSetting.cs @@ -8,18 +8,6 @@ namespace AzureEventGridSimulator.Infrastructure.Settings { public class AdvancedFilterSetting { - [JsonProperty(PropertyName = "operatorType", Required = Required.Always)] - public OperatorTypeEnum OperatorType { get; set; } - - [JsonProperty(PropertyName = "key", Required = Required.Always)] - public string Key { get; set; } - - [JsonProperty(PropertyName = "value", Required = Required.DisallowNull)] - public object Value { get; set; } - - [JsonProperty(PropertyName = "values", Required = Required.DisallowNull)] - public ICollection Values { get; set; } - public enum OperatorTypeEnum { NumberGreaterThan, @@ -36,6 +24,18 @@ public enum OperatorTypeEnum StringNotIn } + [JsonProperty(PropertyName = "operatorType", Required = Required.Always)] + public OperatorTypeEnum OperatorType { get; set; } + + [JsonProperty(PropertyName = "key", Required = Required.Always)] + public string Key { get; set; } + + [JsonProperty(PropertyName = "value", Required = Required.DisallowNull)] + public object Value { get; set; } + + [JsonProperty(PropertyName = "values", Required = Required.DisallowNull)] + public ICollection Values { get; set; } + internal void Validate() { if (string.IsNullOrWhiteSpace(Key)) @@ -60,7 +60,8 @@ internal void Validate() throw new ArgumentOutOfRangeException(nameof(Values), $"Advanced filtering limits strings to {maxStringLength} characters per string value"); } - if (new[] { OperatorTypeEnum.NumberIn, OperatorTypeEnum.NumberNotIn, OperatorTypeEnum.StringIn, OperatorTypeEnum.StringNotIn }.Contains(OperatorType) && Values?.Count > 5) + if (new[] { OperatorTypeEnum.NumberIn, OperatorTypeEnum.NumberNotIn, OperatorTypeEnum.StringIn, OperatorTypeEnum.StringNotIn }.Contains(OperatorType) && + Values?.Count > 5) { throw new ArgumentOutOfRangeException(nameof(OperatorType), "Advanced filtering limits filters to five values for in and not in operators"); } @@ -68,7 +69,8 @@ internal void Validate() public override string ToString() { - return string.Join(", ", Key, OperatorType, Value ?? "null", string.Join(", ", Values.HasItems() ? Values.Select(v => v.ToString()) : new[] { "null" }), Guid.NewGuid()); + return string.Join(", ", Key, OperatorType, Value ?? "null", string.Join(", ", Values.HasItems() ? Values.Select(v => v.ToString()) : new[] { "null" }), + Guid.NewGuid()); } } } diff --git a/src/AzureEventGridSimulator/Infrastructure/Settings/FilterSetting.cs b/src/AzureEventGridSimulator/Infrastructure/Settings/FilterSetting.cs index 9dd2b55..6cbaaba 100644 --- a/src/AzureEventGridSimulator/Infrastructure/Settings/FilterSetting.cs +++ b/src/AzureEventGridSimulator/Infrastructure/Settings/FilterSetting.cs @@ -28,7 +28,7 @@ internal void Validate() throw new ArgumentOutOfRangeException(nameof(AdvancedFilters), "Advanced filtering is limited to five advanced filters per event grid subscription."); } - foreach (var advancedFilter in AdvancedFilters?? new AdvancedFilterSetting[0]) + foreach (var advancedFilter in AdvancedFilters ?? Array.Empty()) { advancedFilter.Validate(); } diff --git a/src/AzureEventGridSimulator/Infrastructure/Settings/SimulatorSettings.cs b/src/AzureEventGridSimulator/Infrastructure/Settings/SimulatorSettings.cs index 4473c1a..263e081 100644 --- a/src/AzureEventGridSimulator/Infrastructure/Settings/SimulatorSettings.cs +++ b/src/AzureEventGridSimulator/Infrastructure/Settings/SimulatorSettings.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics.CodeAnalysis; using System.Linq; using Newtonsoft.Json; @@ -9,6 +10,7 @@ public class SimulatorSettings [JsonProperty(PropertyName = "topics", Required = Required.Always)] public TopicSettings[] Topics { get; set; } = Array.Empty(); + [SuppressMessage("ReSharper", "ParameterOnlyUsedForPreconditionCheck.Local")] public void Validate() { if (Topics.GroupBy(o => o.Port).Count() != Topics.Length) @@ -27,7 +29,8 @@ public void Validate() throw new InvalidOperationException("Each subscriber must have a unique name."); } - if (Topics.Select(t => t.Name).Concat(Topics.SelectMany(t => t.Subscribers).Select(s => s.Name)).Any(name => string.IsNullOrWhiteSpace(name) || name.ToArray().Any(c => !(char.IsLetterOrDigit(c) || c == '-')))) + if (Topics.Select(t => t.Name).Concat(Topics.SelectMany(t => t.Subscribers).Select(s => s.Name)) + .Any(name => string.IsNullOrWhiteSpace(name) || name.ToArray().Any(c => !(char.IsLetterOrDigit(c) || c == '-')))) { throw new InvalidOperationException("A topic/subscriber name can only contain letters, numbers, and dashes."); } diff --git a/src/AzureEventGridSimulator/Infrastructure/Settings/SubscriptionSettings.cs b/src/AzureEventGridSimulator/Infrastructure/Settings/SubscriptionSettings.cs index 30c50dd..0864c29 100644 --- a/src/AzureEventGridSimulator/Infrastructure/Settings/SubscriptionSettings.cs +++ b/src/AzureEventGridSimulator/Infrastructure/Settings/SubscriptionSettings.cs @@ -28,7 +28,7 @@ public class SubscriptionSettings public SubscriptionValidationStatus ValidationStatus { get; set; } [JsonIgnore] - public Guid ValidationCode => new Guid(Encoding.UTF8.GetBytes(Endpoint).Reverse().Take(16).ToArray()); + public Guid ValidationCode => new(Encoding.UTF8.GetBytes(Endpoint).Reverse().Take(16).ToArray()); [JsonIgnore] public bool ValidationPeriodExpired => DateTime.UtcNow > _expired; diff --git a/src/AzureEventGridSimulator/Infrastructure/ValidationIpAddress.cs b/src/AzureEventGridSimulator/Infrastructure/ValidationIpAddress.cs index f9b5b78..8d4076e 100644 --- a/src/AzureEventGridSimulator/Infrastructure/ValidationIpAddress.cs +++ b/src/AzureEventGridSimulator/Infrastructure/ValidationIpAddress.cs @@ -22,6 +22,9 @@ public override string ToString() return _ipAddress; } - public static implicit operator string(ValidationIpAddress d) => d.ToString(); + public static implicit operator string(ValidationIpAddress d) + { + return d.ToString(); + } } } diff --git a/src/AzureEventGridSimulator/Program.cs b/src/AzureEventGridSimulator/Program.cs index cfd46e7..add62e2 100644 --- a/src/AzureEventGridSimulator/Program.cs +++ b/src/AzureEventGridSimulator/Program.cs @@ -1,12 +1,14 @@ using System; +using System.IO; using System.Linq; using System.Net; +using System.Reflection; +using System.Security.Authentication; using System.Security.Cryptography.X509Certificates; using AzureEventGridSimulator.Infrastructure.Settings; using Microsoft.AspNetCore; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Serilog; using Serilog.Events; @@ -15,106 +17,173 @@ namespace AzureEventGridSimulator { public static class Program { + private static IWebHostBuilder ConfigureWebHost(string[] args, IConfiguration configuration) + { + return WebHost + .CreateDefaultBuilder(args) + .UseConfiguration(configuration) + .ConfigureLogging(builder => { builder.ClearProviders(); }) + .UseSerilog() + .UseKestrel(options => + { + var simulatorSettings = (SimulatorSettings)options.ApplicationServices.GetService(typeof(SimulatorSettings)); + + if (simulatorSettings?.Topics is null) + { + throw new InvalidOperationException("No settings found!"); + } + + var enabledTopics = simulatorSettings.Topics.Where(t => !t.Disabled); + + var cert = Environment.GetEnvironmentVariable("ASPNETCORE_Kestrel__Certificates__Default__Path"); + var certPass = Environment.GetEnvironmentVariable("ASPNETCORE_Kestrel__Certificates__Default__Password"); + + X509Certificate2 certificate = null; + if (string.IsNullOrWhiteSpace(cert) == false && string.IsNullOrWhiteSpace(certPass) == false) + { + // ReSharper disable once InconsistentLogPropertyNaming + Log.Warning("ASPNETCORE_Kestrel__Certificates__Default__Path is defined, using '{ASPNETCORE_Kestrel__Certificates__Default__Path}'", cert); + certificate = new X509Certificate2(cert, certPass); + } + + options.ConfigureHttpsDefaults(httpsOptions => { httpsOptions.SslProtocols = SslProtocols.Tls12; }); + + foreach (var topics in enabledTopics) + { + if (certificate != null) + { + options.Listen(IPAddress.Any, + topics.Port, + listenOptions => listenOptions + .UseHttps(httpsOptions => httpsOptions.ServerCertificateSelector = (_, _) => certificate)); + } + else + { + // Use the dev cert on localhost. We have to run on https (It's all Microsoft.Azure.EventGrid) supports). + options.ListenLocalhost(topics.Port, listenOptions => listenOptions.UseHttps()); + } + } + }); + } + public static void Main(string[] args) { try { - var host = WebHost - .CreateDefaultBuilder(args) - .ConfigureAppConfiguration((context, builder) => - { - var configRoot = builder.Build(); - - // System.IO.File.WriteAllText("appsettings.debug.txt", configRoot.GetDebugView()); - - var atLeastOneSinkExists = configRoot.GetSection("Serilog:WriteTo").GetChildren().ToArray().Any(); - - var logConfig = new LoggerConfiguration() - .Enrich.FromLogContext() - .Enrich.WithProperty("Environment", context.HostingEnvironment.EnvironmentName) - .Enrich.WithProperty("Application", nameof(AzureEventGridSimulator)) - .Enrich.WithMachineName() - .MinimumLevel.Is(LogEventLevel.Information) - .MinimumLevel.Override("Microsoft", LogEventLevel.Error) - .MinimumLevel.Override("System", LogEventLevel.Error) - .ReadFrom.Configuration(configRoot, "Serilog"); - - if (!atLeastOneSinkExists) - { - logConfig = logConfig.WriteTo.Console(); - } - - Log.Logger = logConfig.CreateLogger(); - - Log.Logger.Information("It's alive!"); - }) - .ConfigureLogging((context, builder) => { builder.ClearProviders(); }) - .UseSerilog() - .ConfigureAppConfiguration((context, builder) => - { - var configFileOverriddenFromCommandLine = context.Configuration.GetValue("ConfigFile"); - if (!string.IsNullOrWhiteSpace(configFileOverriddenFromCommandLine)) - { - // The path to the config file has been passed at the command line - // e.g. AzureEventGridSimulator.exe --ConfigFile=/path/to/config.json - builder.AddJsonFile(configFileOverriddenFromCommandLine, optional: false); - Log.Logger.Warning("Overriding settings with '{SettingsPath}'", configFileOverriddenFromCommandLine); - } - }) - .UseUrls("https://127.0.0.1:0") // The default which we'll override with the configured topics - .UseKestrel(options => - { - var simulatorSettings = (SimulatorSettings)options.ApplicationServices.GetService(typeof(SimulatorSettings)); - var enabledTopics = simulatorSettings.Topics.Where(t => !t.Disabled); - - var cert = Environment.GetEnvironmentVariable("ASPNETCORE_Kestrel__Certificates__Default__Path"); - var certPass = Environment.GetEnvironmentVariable("ASPNETCORE_Kestrel__Certificates__Default__Password"); - - X509Certificate2 certificate = null; - if (string.IsNullOrWhiteSpace(cert) == false && string.IsNullOrWhiteSpace(certPass) == false) { - Log.Logger.Warning("ASPNETCORE_Kestrel__Certificates__Default__Path is define, using '{ASPNETCORE_Kestrel__Certificates__Default__Path}'", cert); - certificate = new X509Certificate2(cert, certPass); - } - - foreach (var topics in enabledTopics) - { - options.Listen(IPAddress.Any, - topics.Port, - listenOptions => - { - if (certificate != null) { - listenOptions - .UseHttps(httpsOptions => httpsOptions.ServerCertificateSelector = (features, name) => certificate) - .UseConnectionLogging(); - } - else - { - listenOptions - .UseHttps(StoreName.My, "localhost", true) - .UseConnectionLogging(); - } - }); - } - }) - .Build(); - - try - { - host.Run(); - } - catch (Exception ex) - { - Log.Logger.Fatal($"Error running the Azure Event Grid Simulator: {ex.Message}"); - } + var webHost = CreateWebHostBuilder(args).Build(); + + webHost.Run(); } catch (Exception ex) { - Log.Logger.Fatal($"Failed to start the Azure Event Grid Simulator: {ex.Message}"); + Log.Fatal(ex, "Failed to start the Azure Event Grid Simulator"); } finally { Log.CloseAndFlush(); } } + + private static IWebHostBuilder CreateWebHostBuilder(string[] args) + { + var environmentName = GetHostingEnvironment(); + + var builder = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json", true, false) + .AddJsonFile($"appsettings.{environmentName}.json", true, false) + .AddEnvironmentVariables() + .AddCommandLine(args); + + var configFileOverriddenFromCommandLine = builder.Build().GetValue("ConfigFile"); + if (!string.IsNullOrWhiteSpace(configFileOverriddenFromCommandLine)) + { + // The path to the config file has been passed at the command line + // e.g. AzureEventGridSimulator.exe --ConfigFile=/path/to/config.json + builder.AddJsonFile(configFileOverriddenFromCommandLine, false, false); + Log.Warning("Overriding settings with '{SettingsPath}'", configFileOverriddenFromCommandLine); + } + + var config = builder.Build(); + + // You can uncomment this to get a dump of the current effective config settings. + // System.IO.File.WriteAllText("appsettings.debug.txt", config.GetDebugView()); + + CreateLogger(config, environmentName); + + var hostBuilder = ConfigureWebHost(args, config); + + return hostBuilder; + } + + private static string GetHostingEnvironment() + { + var environmentName = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT"); + + if (string.IsNullOrWhiteSpace(environmentName)) + { + environmentName = Environment.GetEnvironmentVariable("DOTNET_ENVIRONMENT"); + } + + if (string.IsNullOrWhiteSpace(environmentName)) + { + environmentName = "Production"; + } + + return environmentName; + } + + private static void CreateLogger(IConfiguration config, string environmentName) + { + var atLeastOneLogHasBeenConfigured = config.GetSection("Serilog:WriteTo").GetChildren().ToArray().Any(); + + var logConfig = new LoggerConfiguration() + .Enrich.FromLogContext() + .Enrich.WithProperty("MachineName", Environment.MachineName) + .Enrich.WithProperty("Environment", environmentName) + .Enrich.WithProperty("Application", nameof(AzureEventGridSimulator)) + .Enrich.WithProperty("Version", Assembly.GetExecutingAssembly().GetName().Version) + // The sensible defaults + .MinimumLevel.Is(LogEventLevel.Information) + .MinimumLevel.Override("Microsoft", LogEventLevel.Error) + .MinimumLevel.Override("System", LogEventLevel.Error) + // Override defaults from settings if any + .ReadFrom.Configuration(config, "Serilog"); + + if (!atLeastOneLogHasBeenConfigured) + { + logConfig = logConfig.WriteTo.Console(); + } + + // Serilog.Debugging.SelfLog.Enable(s => Console.WriteLine($"Serilog Debug -> {s}")); + + Log.Logger = logConfig.CreateLogger(); + + ShowSerilogUsingWarningIfNecessary(config); + } + + private static void ShowSerilogUsingWarningIfNecessary(IConfiguration config) + { + var usingNeedsToBeConfigured = config.GetSection("Serilog").Exists() && + !config.GetSection("Serilog:Using").Exists(); + // ReSharper disable once InvertIf + if (usingNeedsToBeConfigured) + { + // Warn the user about the necessity for the serilog using section with .net 5.0. + // https://github.com/serilog/serilog-settings-configuration#net-50-single-file-applications + Console.WriteLine(@"The Azure Event Grid simulator was unable to start: -" + Environment.NewLine); + Console.WriteLine(@" Serilog with .net 5.0 now requires a 'Using' section."); + Console.WriteLine(@" https://github.com/serilog/serilog-settings-configuration#net-50-single-file-applications" + + Environment.NewLine); + Console.WriteLine( + @"Please add the following to the Serilog config section and restart: -" + Environment.NewLine); + Console.WriteLine(@" ""Using"": [""Serilog.Sinks.Console"", ""Serilog.Sinks.File"", ""Serilog.Sinks.Seq""]" + + Environment.NewLine); + + Console.WriteLine(@"Any key to exit..."); + Console.ReadKey(); + Environment.Exit(-1); + } + } } } diff --git a/src/AzureEventGridSimulator/Startup.cs b/src/AzureEventGridSimulator/Startup.cs index d6a6136..76cfbc4 100644 --- a/src/AzureEventGridSimulator/Startup.cs +++ b/src/AzureEventGridSimulator/Startup.cs @@ -1,6 +1,8 @@ -using System.Linq; +using System; +using System.Linq; using System.Reflection; using System.Threading.Tasks; +using AzureEventGridSimulator.Domain; using AzureEventGridSimulator.Domain.Commands; using AzureEventGridSimulator.Infrastructure; using AzureEventGridSimulator.Infrastructure.Middleware; @@ -8,11 +10,13 @@ using MediatR; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Versioning; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Serilog; +using ILogger=Microsoft.Extensions.Logging.ILogger; namespace AzureEventGridSimulator { @@ -30,7 +34,7 @@ public void ConfigureServices(IServiceCollection services) var settings = new SimulatorSettings(); _configuration.Bind(settings); settings.Validate(); - services.AddSingleton(o => settings); + services.AddSingleton(_ => settings); services.AddMediatR(Assembly.GetExecutingAssembly()); services.AddHttpClient(); @@ -38,51 +42,60 @@ public void ConfigureServices(IServiceCollection services) services.AddScoped(); services.AddSingleton(); - services.AddControllers(options => options.EnableEndpointRouting = false) - .AddJsonOptions(options => - { - options.JsonSerializerOptions.WriteIndented = true; - }) + services.AddControllers(options => { options.EnableEndpointRouting = false; }) + .AddJsonOptions(options => { options.JsonSerializerOptions.WriteIndented = true; }) .SetCompatibilityVersion(CompatibilityVersion.Latest); + + services.AddApiVersioning(config => + { + config.DefaultApiVersion = new ApiVersion(DateTime.Parse(Constants.SupportedApiVersion, new ApiVersionFormatProvider())); + config.AssumeDefaultVersionWhenUnspecified = true; + config.ReportApiVersions = true; + }); } - public void Configure(IApplicationBuilder app, - IHostApplicationLifetime lifetime, - ILogger logger) + // ReSharper disable once UnusedMember.Global + // ReSharper disable once CA1822 + public static void Configure(IApplicationBuilder app, + IHostApplicationLifetime lifetime, + ILogger logger) { - lifetime.ApplicationStarted.Register(async () => await Task.CompletedTask.ContinueWith((t) => OnApplicationStarted(app, lifetime, logger))); + lifetime.ApplicationStarted.Register(async () => await Task.CompletedTask.ContinueWith(_ => OnApplicationStarted(app, lifetime, logger))); - // app.UseSerilogRequestLogging(); // Not using this for now + app.UseSerilogRequestLogging(); + app.UseMiddleware(); app.UseMiddleware(); app.UseMvc(); } - private static async Task OnApplicationStarted(IApplicationBuilder app, IHostApplicationLifetime lifetime, ILogger logger) + private static async Task OnApplicationStarted(IApplicationBuilder app, IHostApplicationLifetime lifetime, ILogger logger) { - var simulatorSettings = app.ApplicationServices.GetService(typeof(SimulatorSettings)) as SimulatorSettings; + logger.LogInformation("It's Alive !"); + + var simulatorSettings = (SimulatorSettings)app.ApplicationServices.GetService(typeof(SimulatorSettings)); if (simulatorSettings is null) { - logger.LogCritical("Settings are not found. The application will now exit."); + logger.LogCritical("Settings are not found. The application will now exit"); lifetime.StopApplication(); return; } if (!simulatorSettings.Topics.Any()) { - logger.LogCritical("There are no configured topics. The application will now exit."); + logger.LogCritical("There are no configured topics. The application will now exit"); lifetime.StopApplication(); return; } if (simulatorSettings.Topics.All(o => o.Disabled)) { - logger.LogCritical("All of the configured topics are disabled. The application will now exit."); + logger.LogCritical("All of the configured topics are disabled. The application will now exit"); lifetime.StopApplication(); return; } - if ((app.ApplicationServices.GetService(typeof(IMediator)) is IMediator mediator)) + if (app.ApplicationServices.GetService(typeof(IMediator)) is IMediator mediator) { await mediator.Send(new ValidateAllSubscriptionsCommand()); } diff --git a/src/AzureEventGridSimulator/appsettings.json b/src/AzureEventGridSimulator/appsettings.json index 2ea37f5..0bcc2a6 100644 --- a/src/AzureEventGridSimulator/appsettings.json +++ b/src/AzureEventGridSimulator/appsettings.json @@ -1,11 +1,15 @@ { "topics": [ { - "name": "ATopicWithNoSubscribers", - "port": 60102, + "name": "ATopicWithATestSubscriber", + "port": 60101, "key": "TheLocal+DevelopmentKey=", "subscribers": [ - // There are no subscribers defined yet, so you can send events to this topic but they won't be delivered to anyone + { + "name": "RequestCatcherSubscription", + "endpoint": "https://azureeventgridsimulator.requestcatcher.com/", + "disableValidation": true + } ] } ] diff --git a/src/AzureEventGridSimulator/example.appsettings.json b/src/AzureEventGridSimulator/example.appsettings.json index 2954e55..c7b1371 100644 --- a/src/AzureEventGridSimulator/example.appsettings.json +++ b/src/AzureEventGridSimulator/example.appsettings.json @@ -5,7 +5,6 @@ "name": "MyAwesomeTopic", "port": 60101, "key": "TheLocal+DevelopmentKey=", - "disabled": false, "subscribers": [ { "name": "LocalAzureFunctionSubscription", @@ -20,7 +19,7 @@ "port": 60102, "key": "TheLocal+DevelopmentKey=", "subscribers": [ - // There are no subscribers defined yet, so you can send events to this topic but they won't be delivered to anyone + ] }, @@ -28,24 +27,42 @@ "name": "ADisabledTopic", "port": 60103, "key": "TheLocal+DevelopmentKey=", - "disabled": true, // this topic is disabled so it's endpoint won't be created at startup + "disabled": true, "subscribers": [ { "name": "LocalWebApiSubscription", "endpoint": "https://localhost:5050/api/SomeApi" } ] - } + }, + { + "name": "RequestCatcherTopic", + "port": 60104, + "key": "TheLocal+DevelopmentKey=", + "subscribers": [ + { + "name": "RequestCatcherSubscription", + "endpoint": "https://azureeventgridsimulator.requestcatcher.com", + "disableValidation": true + } + ] + } + ], "Serilog": { "MinimumLevel": { "Default": "Information", "Override": { - "Microsoft": "Error", - "System": "Error" + "Microsoft": "Warning", + "System": "Warning" } }, + "Using": [ + "Serilog.Sinks.Console", + "Serilog.Sinks.File", + "Serilog.Sinks.Seq" + ], "WriteTo": [ { "Name": "Console" }, { diff --git a/src/UnitTests/Filtering/NegativeFilterTestCaseContainer.cs b/src/UnitTests/Filtering/NegativeFilterTestCaseContainer.cs deleted file mode 100644 index 631c66b..0000000 --- a/src/UnitTests/Filtering/NegativeFilterTestCaseContainer.cs +++ /dev/null @@ -1,169 +0,0 @@ -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using AzureEventGridSimulator.Infrastructure.Settings; - -namespace UnitTests.Filtering -{ - class NegativeFilterTestCaseContainer : IEnumerable - { - public IEnumerator GetEnumerator() - { - var list = new List(); - list.AddRange(GetNegativeIdFilterConfigurations().Select(c => new object[] { c })); - list.AddRange(GetNegativeTopicFilterConfigurations().Select(c => new object[] { c })); - list.AddRange(GetNegativeSubjectFilterConfigurations().Select(c => new object[] { c })); - list.AddRange(GetNegativeEventTypeFilterConfigurations().Select(c => new object[] { c })); - list.AddRange(GetNegativeDataVersionFilterConfigurations().Select(c => new object[] { c })); - list.AddRange(GetNegativeEventDataFilterConfigurations().Select(c => new object[] { c })); - list.AddRange(GetNegativeEventIdFilterConfigurations().Select(c => new object[] { c })); - list.AddRange(GetNegativeSourceFilterConfigurations().Select(c => new object[] { c })); - list.AddRange(GetNegativeEventTypeVersionFilterConfigurations().Select(c => new object[] { c })); - return list.GetEnumerator(); - } - - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - - private static AdvancedFilterSetting[] GetNegativeIdFilterConfigurations() - { - return new[] - { - new AdvancedFilterSetting { Key = "Id", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringBeginsWith, Value = null }, - new AdvancedFilterSetting { Key = "Id", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringBeginsWith, Value = string.Empty }, - new AdvancedFilterSetting { Key = "Id", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringBeginsWith, Value = "A" }, - new AdvancedFilterSetting { Key = "Id", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringBeginsWith, Value = "a" }, - new AdvancedFilterSetting { Key = "Id", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringContains, Value = null }, - new AdvancedFilterSetting { Key = "Id", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringContains, Value = string.Empty }, - new AdvancedFilterSetting { Key = "Id", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringContains, Value = "a" }, - new AdvancedFilterSetting { Key = "Id", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringContains, Value = "TEN" }, - new AdvancedFilterSetting { Key = "Id", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringEndsWith, Value = "b" }, - new AdvancedFilterSetting { Key = "Id", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringEndsWith, Value = string.Empty }, - new AdvancedFilterSetting { Key = "Id", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringEndsWith, Value = null }, - new AdvancedFilterSetting { Key = "Id", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringEndsWith, Value = "B" }, - new AdvancedFilterSetting { Key = "Id", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringIn, Values = null }, - new AdvancedFilterSetting { Key = "Id", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringIn, Values = new string[0] }, - new AdvancedFilterSetting { Key = "Id", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringIn, Values = new[] {"notCorrect" } }, - new AdvancedFilterSetting { Key = "Id", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringIn, Values = new[] {"different", "not_found", "Another" } }, - new AdvancedFilterSetting { Key = "Id", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringNotIn, Values = new[] {"different", "EventID", "Another" } }, - new AdvancedFilterSetting { Key = "Id", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringNotIn, Values = new[] {"different", "EventId", "Another" } }, - }; - } - - private static AdvancedFilterSetting[] GetNegativeTopicFilterConfigurations() - { - return new[] { - new AdvancedFilterSetting { Key = "Topic", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringBeginsWith, Value ="HE" }, - new AdvancedFilterSetting { Key = "Topic", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringBeginsWith, Value ="he_" }, - new AdvancedFilterSetting { Key = "Topic", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringContains, Value ="everest" }, - new AdvancedFilterSetting { Key = "Topic", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringEndsWith, Value ="123" }, - new AdvancedFilterSetting { Key = "Topic", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringIn, Values = new object[] {"_event_" } }, - new AdvancedFilterSetting { Key = "Topic", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringNotIn, Values = new object[] { "THE_EVENT_TOPIC" } } - }; - } - - private static AdvancedFilterSetting[] GetNegativeSubjectFilterConfigurations() - { - return new[] { - new AdvancedFilterSetting { Key = "Subject", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringBeginsWith, Value ="E" }, - new AdvancedFilterSetting { Key = "Subject", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringBeginsWith, Value ="able" }, - new AdvancedFilterSetting { Key = "Subject", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringContains, Value ="x" }, - new AdvancedFilterSetting { Key = "Subject", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringEndsWith, Value ="sub" }, - new AdvancedFilterSetting { Key = "Subject", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringIn, Values = new object[] { "not_correct" } }, - new AdvancedFilterSetting { Key = "Subject", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringNotIn, Values = new object[] { "theeventsubject" } } - }; - } - - private static AdvancedFilterSetting[] GetNegativeEventTypeFilterConfigurations() - { - return new[] { - new AdvancedFilterSetting { Key = "EventType", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringBeginsWith, Value ="his" }, - new AdvancedFilterSetting { Key = "EventType", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringBeginsWith, Value ="hIs" }, - new AdvancedFilterSetting { Key = "EventType", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringContains, Value =".." }, - new AdvancedFilterSetting { Key = "EventType", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringEndsWith, Value ="EVENTTYPE" }, - new AdvancedFilterSetting { Key = "EventType", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringIn, Values = new object[0] }, - new AdvancedFilterSetting { Key = "EventType", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringNotIn, Values = new object[] { "Not-the-right-type", "this.is.a.test.event.type" } } - }; - } - - private static AdvancedFilterSetting[] GetNegativeDataVersionFilterConfigurations() - { - return new[] { - new AdvancedFilterSetting { Key = "DataVersion", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringBeginsWith, Value ="a" }, - new AdvancedFilterSetting { Key = "DataVersion", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringContains, Value ="_" }, - new AdvancedFilterSetting { Key = "DataVersion", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringEndsWith, Value ="7" }, - new AdvancedFilterSetting { Key = "DataVersion", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringIn, Values = new object[] { "5.0.1" } }, - new AdvancedFilterSetting { Key = "DataVersion", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringNotIn, Values = new object[] { "5.0" } } - }; - } - - private static AdvancedFilterSetting[] GetNegativeEventDataFilterConfigurations() - { - return new[] { - new AdvancedFilterSetting { Key = "Data.NumberValue", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.NumberGreaterThan, Value = 2 }, - new AdvancedFilterSetting { Key = "Data.NumberValue", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.NumberGreaterThan, Value = null }, - new AdvancedFilterSetting { Key = "Data.NumberValue", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.NumberGreaterThan, Value = 1 }, - new AdvancedFilterSetting { Key = "Data.NumberValue", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.NumberGreaterThanOrEquals, Value = null}, - new AdvancedFilterSetting { Key = "Data.NumberValue", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.NumberGreaterThanOrEquals, Value = 1.01 }, - new AdvancedFilterSetting { Key = "Data.NumberValue", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.NumberGreaterThanOrEquals, Value = 5 }, - new AdvancedFilterSetting { Key = "Data.NumberValue", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.NumberIn, Values = new object []{ 1.1, 2, 3.5, "stringValue", true } }, - new AdvancedFilterSetting { Key = "Data.NumberValue", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.NumberIn, Values = new object [0] }, - new AdvancedFilterSetting { Key = "Data.NumberValue", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.NumberIn, Values = null }, - new AdvancedFilterSetting { Key = "Data.NumberValue", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.NumberNotIn, Values = new object []{ 0, 1, 2, 3.5, "stringValue", true } }, - // while the value is not in the array, the fact that the values in the array are not all parsable as numbers means the full evaluation cannot be completed and so by default we fail - new AdvancedFilterSetting { Key = "Data.NumberValue", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.NumberNotIn, Values = new object []{ 0, 2, 3.5, "stringValue", true } }, - new AdvancedFilterSetting { Key = "Data.NumberValue", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.NumberLessThan, Value = 1 }, - new AdvancedFilterSetting { Key = "Data.NumberValue", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.NumberLessThan, Value = null }, - new AdvancedFilterSetting { Key = "Data.NumberValue", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.NumberLessThan, Value = 0.99999999 }, - new AdvancedFilterSetting { Key = "Data.NumberValue", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.NumberLessThanOrEquals, Value = null }, - new AdvancedFilterSetting { Key = "Data.NumberValue", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.NumberLessThanOrEquals, Value = 0.9 }, - new AdvancedFilterSetting { Key = "Data.NumberValue", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.NumberLessThanOrEquals, Value = -1 }, - new AdvancedFilterSetting { Key = "Data.IsTrue", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.BoolEquals, Value = null }, - new AdvancedFilterSetting { Key = "Data.IsTrue", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.BoolEquals, Value = false }, - new AdvancedFilterSetting { Key = "Data.Name", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringBeginsWith, Value = null }, - new AdvancedFilterSetting { Key = "Data.Name", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringBeginsWith, Value = "String_Value" }, - new AdvancedFilterSetting { Key = "Data.DoubleValue", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.NumberGreaterThan, Value = null }, - new AdvancedFilterSetting { Key = "Data.DoubleValue", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.NumberGreaterThan, Value = 0.12345}, - new AdvancedFilterSetting { Key = "Data.DoubleValue", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.NumberGreaterThanOrEquals, Value = null }, - new AdvancedFilterSetting { Key = "Data.DoubleValue", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.NumberGreaterThanOrEquals, Value = 0.123451 }, - new AdvancedFilterSetting { Key = "Data.DoubleValue", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.NumberIn, Values = new object[] { 0.123451 } }, - new AdvancedFilterSetting { Key = "Data.DoubleValue", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.NumberLessThan, Value = 0.12345 }, - new AdvancedFilterSetting { Key = "Data.DoubleValue", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.NumberLessThanOrEquals, Value = 0.1234 }, - new AdvancedFilterSetting { Key = "Data.DoubleValue", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.NumberNotIn, Values = new object[] { 0.12345 } }, - new AdvancedFilterSetting { Key = "Data.NumberMaxValue", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.NumberGreaterThan, Value = ulong.MaxValue }, - new AdvancedFilterSetting { Key = "Data.NumberMaxValue", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.NumberIn, Values = new object[] { long.MaxValue } }, - new AdvancedFilterSetting { Key = "Data.NumberMaxValue", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.NumberLessThanOrEquals, Value = long.MaxValue }, - new AdvancedFilterSetting { Key = "Data.NumberMaxValue", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.NumberLessThan, Value = ulong.MaxValue }, - new AdvancedFilterSetting { Key = "Data.NumberMaxValue", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.NumberNotIn, Values = new object[] { ulong.MaxValue } }, - new AdvancedFilterSetting { Key = "Data.SubObject.Name", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.StringIn, Values = new object[] { "Testing", "does", "not", "exist" } }, - new AdvancedFilterSetting { Key = "Data.SubObject.Id", OperatorType = AdvancedFilterSetting.OperatorTypeEnum.NumberIn, Values = new object[] { 10, 11, 12 } } - }; - } - - private static AdvancedFilterSetting[] GetNegativeEventIdFilterConfigurations() - { - // everything with this key is considered negative at the moment given that the key will never be found on an event that doesn not conform to the cloud schema - // special case for use with the cloud event schema (https://docs.microsoft.com/en-us/azure/event-grid/cloudevents-schema) - return new[] { - new AdvancedFilterSetting { Key = "EventId" } - }; - } - - private static AdvancedFilterSetting[] GetNegativeSourceFilterConfigurations() - { - // everything with this key is considered negative at the moment given that the key will never be found on an event that doesn not conform to the cloud schema - // no positive tests are available for this key yet since no support for the cloud event schema is available at the moment - return new[] { - new AdvancedFilterSetting { Key = "Source" } - }; - } - - private static AdvancedFilterSetting[] GetNegativeEventTypeVersionFilterConfigurations() - { - // everything with this key is considered negative at the moment given that the key will never be found on an event that doesn not conform to the cloud schema - // no positive tests are available for this key yet since no support for the cloud event schema is available at the moment - return new[] { - new AdvancedFilterSetting { Key = "EventTypeVersion" } - }; - } - } -} diff --git a/src/publish-linux-64.bat b/src/publish-linux-64.bat index 077d5ef..0f52a8b 100644 --- a/src/publish-linux-64.bat +++ b/src/publish-linux-64.bat @@ -8,7 +8,7 @@ ECHO REMOVE OUTPUT FOLDER "build-linux-64" rd /S /Q "build-linux-64" ECHO PUBLISHING SOLUTION -dotnet publish ./AzureEventGridSimulator/AzureEventGridSimulator.csproj -o "../build-linux-64" -v q -c Release --self-contained true -p:PublishSingleFile=true -p:PublishTrimmed=true -f netcoreapp3.1 -r linux-x64 /nologo +dotnet publish ./AzureEventGridSimulator/AzureEventGridSimulator.csproj -o "../build-linux-64" -c Release --self-contained true -p:PublishReadyToRun=false -p:IncludeNativeLibrariesForSelfExtract=true -p:PublishSingleFile=false -p:PublishTrimmed=false -p:TrimUnusedDependencies=true -r linux-x64 ECHO DONE! pause diff --git a/src/publish-osx-x64.bat b/src/publish-osx-x64.bat index c2664cd..c7e561b 100644 --- a/src/publish-osx-x64.bat +++ b/src/publish-osx-x64.bat @@ -8,7 +8,7 @@ ECHO REMOVE OUTPUT FOLDER "build-osx-64" rd /S /Q "build-osx-64" ECHO PUBLISHING SOLUTION -dotnet publish ./AzureEventGridSimulator/AzureEventGridSimulator.csproj -o "../build-osx-64" -v q -c Release --self-contained true -p:PublishSingleFile=true -p:PublishTrimmed=true -f netcoreapp3.1 -r osx-x64 /nologo +dotnet publish ./AzureEventGridSimulator/AzureEventGridSimulator.csproj -o "../build-osx-64" -c Release --self-contained true -p:PublishReadyToRun=false -p:IncludeNativeLibrariesForSelfExtract=true -p:PublishSingleFile=false -p:PublishTrimmed=false -p:TrimUnusedDependencies=true -r osx-x64 ECHO DONE! pause diff --git a/src/publish-win-64.bat b/src/publish-win-64.bat index 86dd1cf..a215f17 100644 --- a/src/publish-win-64.bat +++ b/src/publish-win-64.bat @@ -8,7 +8,7 @@ ECHO REMOVE OUTPUT FOLDER "build-win-64" rd /S /Q "build-win-64" ECHO PUBLISHING SOLUTION -dotnet publish ./AzureEventGridSimulator/AzureEventGridSimulator.csproj -o "../build-win-64" -v q -c Release --self-contained true -p:PublishSingleFile=true -p:PublishTrimmed=true -f netcoreapp3.1 -r win-x64 /nologo +dotnet publish ./AzureEventGridSimulator/AzureEventGridSimulator.csproj -o "../build-win-64" -v q -c Release --self-contained true -p:PublishReadyToRun=false -p:IncludeNativeLibrariesForSelfExtract=true -p:PublishSingleFile=true -p:PublishTrimmed=false -p:TrimUnusedDependencies=true -f net5.0 -r win-x64 /nologo ECHO DONE! pause