diff --git a/src/Logging/Loggly/Loggly/FormattedIdConverter .cs b/src/Logging/Loggly/Loggly/FormattedIdConverter .cs new file mode 100644 index 0000000..9984fa0 --- /dev/null +++ b/src/Logging/Loggly/Loggly/FormattedIdConverter .cs @@ -0,0 +1,30 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; + +public class FormattedIdConverter : JsonConverter +{ + private readonly HashSet IdNumericTypes = new HashSet + { + typeof(byte), typeof(short), typeof(int), typeof(long), + typeof(sbyte), typeof(ushort), typeof(uint), typeof(ulong), + }; + + public override bool CanConvert(Type objectType) + { + return IdNumericTypes.Contains(objectType); + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + if (writer.Path.EndsWith("Id", StringComparison.OrdinalIgnoreCase)) + writer.WriteValue(Convert.ToString(value)); + else + writer.WriteValue(value); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + throw new NotImplementedException(); + } +} \ No newline at end of file diff --git a/src/Logging/Loggly/Loggly/JsonSettings.cs b/src/Logging/Loggly/Loggly/JsonSettings.cs new file mode 100644 index 0000000..f1a50b3 --- /dev/null +++ b/src/Logging/Loggly/Loggly/JsonSettings.cs @@ -0,0 +1,19 @@ +using Newtonsoft.Json; + +namespace EMG.Extensions.Logging.Loggly +{ + public static class JsonSettings + { + public static readonly JsonSerializerSettings SerializerSettings = new JsonSerializerSettings + { + TypeNameHandling = TypeNameHandling.None, + NullValueHandling = NullValueHandling.Ignore, + Formatting = Formatting.None, + DefaultValueHandling = DefaultValueHandling.Include, + DateTimeZoneHandling = DateTimeZoneHandling.Utc, + DateFormatHandling = DateFormatHandling.IsoDateFormat, + ReferenceLoopHandling = ReferenceLoopHandling.Ignore, + Converters = { new FormattedIdConverter() } + }; + } +} \ No newline at end of file diff --git a/src/Logging/Loggly/Loggly/LogglyHttpClient.cs b/src/Logging/Loggly/Loggly/LogglyHttpClient.cs index eaf01d6..e8198b0 100644 --- a/src/Logging/Loggly/Loggly/LogglyHttpClient.cs +++ b/src/Logging/Loggly/Loggly/LogglyHttpClient.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using System.Net.Http; using System.Threading.Tasks; @@ -32,7 +31,7 @@ public async Task PublishAsync(LogglyMessage message) { using (var request = new HttpRequestMessage(HttpMethod.Post, $"/inputs/{_options.ApiKey}/tag/{_options.Environment}")) { - var content = JsonConvert.SerializeObject(FixData(message), SerializerSettings); + var content = JsonConvert.SerializeObject(FixData(message), _options.SerializerSettings); request.Content = new StringContent(content, _options.ContentEncoding, "application/json"); using (var response = await _http.SendAsync(request).ConfigureAwait(false)) @@ -66,7 +65,7 @@ public async Task PublishManyAsync(IEnumerable messages) tags.Add(_options.Environment); request.Headers.Add("X-LOGGLY-TAG", tags); - var fixedMessages = string.Join("\n", messages.Select(FixData).Select(s => JsonConvert.SerializeObject(s, SerializerSettings))); + var fixedMessages = string.Join("\n", messages.Select(FixData).Select(s => JsonConvert.SerializeObject(s, _options.SerializerSettings))); request.Content = new StringContent(fixedMessages, _options.ContentEncoding, "application/json"); using (var response = await _http.SendAsync(request).ConfigureAwait(false)) @@ -109,15 +108,5 @@ private static LogglyMessage CloneLogglyEvent(LogglyMessage logglyMessage, objec Message = logglyMessage.Message }; } - - private static readonly JsonSerializerSettings SerializerSettings = new JsonSerializerSettings - { - TypeNameHandling = TypeNameHandling.None, - NullValueHandling = NullValueHandling.Ignore, - Formatting = Formatting.None, - DefaultValueHandling = DefaultValueHandling.Ignore, - DateTimeZoneHandling = DateTimeZoneHandling.Utc, - DateFormatHandling = DateFormatHandling.IsoDateFormat, - }; } } \ No newline at end of file diff --git a/src/Logging/Loggly/Loggly/LogglyOptions.cs b/src/Logging/Loggly/Loggly/LogglyOptions.cs index cd7846a..b0bb4f5 100644 --- a/src/Logging/Loggly/Loggly/LogglyOptions.cs +++ b/src/Logging/Loggly/Loggly/LogglyOptions.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Text; using Microsoft.Extensions.Logging; +using Newtonsoft.Json; namespace EMG.Extensions.Logging.Loggly { @@ -28,6 +29,8 @@ public class LogglyOptions public Encoding ContentEncoding { get; set; } = Encoding.UTF8; public TimeSpan Buffer { get; set; } = TimeSpan.FromMilliseconds(50); + + public JsonSerializerSettings SerializerSettings { get; set; } = JsonSettings.SerializerSettings; } public delegate bool FilterDelegate(string categoryName, EventId eventId, LogLevel logLevel); diff --git a/tests/Logging/Tests.Loggly/AutoMoqData.cs b/tests/Logging/Tests.Loggly/AutoMoqData.cs index fa9300f..4c44517 100644 --- a/tests/Logging/Tests.Loggly/AutoMoqData.cs +++ b/tests/Logging/Tests.Loggly/AutoMoqData.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Net; using System.Net.Http; using System.Text; using AutoFixture; @@ -31,6 +32,7 @@ public AutoMoqDataAttribute() : base(() => fixture.Customize(o => o .Without(p => p.LogglyHost) .Without(p => p.LogglyScheme) + .Without(p => p.SerializerSettings) .With(p => p.SuppressExceptions, false) .With(p => p.ContentEncoding, Encoding.UTF8)); diff --git a/tests/Logging/Tests.Loggly/FormattedIdConverterTest.cs b/tests/Logging/Tests.Loggly/FormattedIdConverterTest.cs new file mode 100644 index 0000000..d300d01 --- /dev/null +++ b/tests/Logging/Tests.Loggly/FormattedIdConverterTest.cs @@ -0,0 +1,196 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using Newtonsoft.Json; +using Moq; + +namespace Tests +{ + [TestFixture] + public class FormattedIdConverterTest + { + + [Test, AutoMoqData] + public void CanConvert_returns_true_for_integer_types(FormattedIdConverter sut) + { + // Assert + foreach (var intType in integerTypesList) + { + Assert.That(sut.CanConvert(intType), Is.EqualTo(true)); + } + } + + [Test, AutoMoqData] + public void CanConvert_returns_false_for_non_integer_primitive_types(FormattedIdConverter sut) + { + // Arrange + var frameworkTypes = typeof(Type).Assembly.GetTypes() + .Where(x => x.IsPrimitive).ToList(); + + var nonIntegerTypesList = frameworkTypes.Except(integerTypesList); + + // Assert + foreach (var intType in nonIntegerTypesList) + { + Assert.That(sut.CanConvert(intType), Is.EqualTo(false)); + } + } + + readonly List integerTypesList = new List + { + typeof(byte), typeof(short), typeof(int), typeof(long), + typeof(sbyte), typeof(ushort), typeof(uint), typeof(ulong), + }; + + + [Test, AutoMoqData] + public void WriteJson_calls_WriteValue_method_of_JsonWriter_for_single_value_once_only(FormattedIdConverter sut + , JsonWriter writer + , JsonSerializer serializer + , byte value) + { + + // Act + sut.WriteJson(writer, value, serializer); + + // Assert + Mock.Get(writer).Verify(i => i.WriteValue(It.IsAny()), Times.Once); + } + + [Test, AutoMoqData] + public void WriteJson_calls_WriteValue_method_of_JsonWriter_for_single_value_with_the_right_value(FormattedIdConverter sut + , JsonWriter writer + , JsonSerializer serializer + , byte value) + { + + // Act + sut.WriteJson(writer, value, serializer); + + // Assert + Mock.Get(writer).Verify(i => i.WriteValue(value), Times.Once); + } + + [Test, AutoMoqData] + public void WriteJson_calls_WriteValue_method_of_JsonWriter_for_non_Id_property_once_only(FormattedIdConverter sut + , JsonWriter writer + , JsonSerializer serializer + , string propertyName + , byte value) + { + Assume.That(propertyName, Does.Not.EndWith("Id")); + + writer.WriteStartObject(); + writer.WritePropertyName(propertyName); + + // Act + sut.WriteJson(writer, value, serializer); + + // Assert + Mock.Get(writer).Verify(i => i.WriteValue(It.IsAny()), Times.Once); + } + + [Test, AutoMoqData] + public void WriteJson_calls_WriteValue_method_of_JsonWriter_for_non_Id_property_with_the_right_value(FormattedIdConverter sut + , JsonWriter writer + , JsonSerializer serializer + , string propertyName + , byte value) + { + Assume.That(propertyName, Does.Not.EndWith("Id")); + + writer.WriteStartObject(); + writer.WritePropertyName(propertyName); + + // Act + sut.WriteJson(writer, value, serializer); + + // Assert + Mock.Get(writer).Verify(i => i.WriteValue(value), Times.Once); + } + + [Test, AutoMoqData] + public void WriteJson_calls_WriteValue_of_JsonWriter_for_Id_Property_once(FormattedIdConverter sut + , JsonWriter writer + , JsonSerializer serializer + , string propertyName + , byte value) + { + // Arrange + writer.WriteStartObject(); + writer.WritePropertyName($"{propertyName}Id"); + + // Act + sut.WriteJson(writer, value, serializer); + + // Assert + Mock.Get(writer).Verify(i => i.WriteValue(Convert.ToString(value)), Times.Once); + } + + [Test, AutoMoqData] + public void WriteJson_calls_WriteValue_of_JsonWriter_for_Id_Property_with_string_value(FormattedIdConverter sut + , JsonWriter writer + , JsonSerializer serializer + , string propertyName + , byte value) + { + // Arrange + writer.WriteStartObject(); + writer.WritePropertyName($"{propertyName}Id"); + + // Act + sut.WriteJson(writer, value, serializer); + + // Assert + Mock.Get(writer).Verify(i => i.WriteValue(Convert.ToString(value)), Times.Once); + } + + [Test, AutoMoqData] + public void WriteJson_checks_PropertyName_CaseInsensitive_Lower(FormattedIdConverter sut + , JsonWriter writer + , JsonSerializer serializer + , string propertyName + , byte value) + { + // Arrange + writer.WriteStartObject(); + writer.WritePropertyName($"{propertyName}Id".ToLower()); + + // Act + sut.WriteJson(writer, value, serializer); + + // Assert + Mock.Get(writer).Verify(i => i.WriteValue(Convert.ToString(value)), Times.Once); + } + + [Test, AutoMoqData] + public void WriteJson_checks_PropertyName_CaseInsensitive_Upper(FormattedIdConverter sut + , JsonWriter writer + , JsonSerializer serializer + , string propertyName + , byte value) + { + // Arrange + writer.WriteStartObject(); + writer.WritePropertyName($"{propertyName}Id".ToUpper()); + + // Act + sut.WriteJson(writer, value, serializer); + + // Assert + Mock.Get(writer).Verify(i => i.WriteValue(Convert.ToString(value)), Times.Once); + } + + [Test, AutoMoqData] + public void ReadJson_throws_NotImplementedException(FormattedIdConverter sut + , JsonReader reader + , Type objectType + , object existingValue + , JsonSerializer serializer) + { + // Assert + Assert.Throws(() => sut.ReadJson(reader, objectType, existingValue, serializer)); + } + } +} diff --git a/tests/Logging/Tests.Loggly/Loggly/LogglyHttpClientTests.cs b/tests/Logging/Tests.Loggly/Loggly/LogglyHttpClientTests.cs index 9f490db..9ee54b5 100644 --- a/tests/Logging/Tests.Loggly/Loggly/LogglyHttpClientTests.cs +++ b/tests/Logging/Tests.Loggly/Loggly/LogglyHttpClientTests.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Net; using System.Net.Http; using System.Threading.Tasks; @@ -7,7 +6,6 @@ using AutoFixture.Idioms; using AutoFixture.NUnit3; using EMG.Extensions.Logging.Loggly; -using Moq; using NUnit.Framework; using WorldDomination.Net.Http; @@ -16,9 +14,22 @@ namespace Tests.Loggly [TestFixture] public class LogglyHttpClientTests { - [Test, AutoData] - public void Constructor_is_guarded(GuardClauseAssertion assertion) + [Test, AutoMoqData] + public void Constructor_is_guarded(GuardClauseAssertion assertion, IFixture fixture) { + var registration = new HttpMessageOptions + { + HttpResponseMessage = new HttpResponseMessage(HttpStatusCode.OK), + HttpMethod = HttpMethod.Post + }; + + var handler = new FakeHttpMessageHandler(registration); + + fixture.Register((LogglyOptions o) => new HttpClient(handler) + { + BaseAddress = new Uri($"{o.LogglyScheme}://{o.LogglyHost}") + }); + assertion.Verify(typeof(LogglyHttpClient).GetConstructors()); }