From a43941ece3b5bb01ccbe7e0b87b6a6871d1e5ffe Mon Sep 17 00:00:00 2001 From: Ross Oliver Date: Tue, 29 Sep 2020 14:28:44 +0100 Subject: [PATCH 1/2] Add filter to log request/response paylaods We want to be able to see request/response payloads to help us when debugging requests in production. Add `LogRequests` attribute that will log request/response information per-controller and enable for every controller. Add `LoggableAttribute` to opt-in models that should be logged. Add `SensitiveDataAttribute` to opt-out any sensitive attributes from being logged. --- .../Attributes/CrmETagAttribute.cs | 2 +- .../Attributes/LogRequestsAttribute.cs | 97 ++++++++++ .../Attributes/LoggableAttribute.cs | 9 + .../Attributes/SensitiveDataAttribute.cs | 9 + .../CallbackBookingQuotasController.cs | 2 + .../Controllers/CandidatesController.cs | 2 + .../Controllers/MailingListController.cs | 2 + .../Controllers/OperationsController.cs | 3 +- .../Controllers/PrivacyPoliciesController.cs | 3 +- .../CandidatesController.cs | 2 + .../Controllers/TeachingEventsController.cs | 3 +- .../Controllers/TypesController.cs | 3 +- .../Attributes/CrmETagAttributeTests.cs | 2 +- .../Attributes/LogRequestsAttributeTests.cs | 175 ++++++++++++++++++ .../CallbackBookingQuotasControllerTests.cs | 7 + .../Controllers/CandidatesControllerTests.cs | 7 + .../Controllers/MailingListControllerTests.cs | 7 + .../Controllers/OperationsControllerTests.cs | 7 + .../PrivacyPoliciesControllerTests.cs | 8 +- .../CandidatesControllerTests.cs | 7 + .../TeachingEventsControllerTests.cs | 8 +- .../Controllers/TypesControllerTests.cs | 8 +- .../Helpers/LoggerTestHelpers.cs | 25 ++- 23 files changed, 386 insertions(+), 12 deletions(-) create mode 100644 GetIntoTeachingApi/Attributes/LogRequestsAttribute.cs create mode 100644 GetIntoTeachingApi/Attributes/LoggableAttribute.cs create mode 100644 GetIntoTeachingApi/Attributes/SensitiveDataAttribute.cs create mode 100644 GetIntoTeachingApiTests/Attributes/LogRequestsAttributeTests.cs diff --git a/GetIntoTeachingApi/Attributes/CrmETagAttribute.cs b/GetIntoTeachingApi/Attributes/CrmETagAttribute.cs index 1492b5a93..1d12e00d3 100644 --- a/GetIntoTeachingApi/Attributes/CrmETagAttribute.cs +++ b/GetIntoTeachingApi/Attributes/CrmETagAttribute.cs @@ -9,7 +9,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; -namespace GetIntoTeachingApi.Filters +namespace GetIntoTeachingApi.Attributes { public class CrmETagAttribute : Attribute, IActionFilter { diff --git a/GetIntoTeachingApi/Attributes/LogRequestsAttribute.cs b/GetIntoTeachingApi/Attributes/LogRequestsAttribute.cs new file mode 100644 index 000000000..7c53cab3e --- /dev/null +++ b/GetIntoTeachingApi/Attributes/LogRequestsAttribute.cs @@ -0,0 +1,97 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Controllers; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; + +namespace GetIntoTeachingApi.Attributes +{ + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = false)] + public class LogRequestsAttribute : Attribute, IActionFilter + { + private readonly ILogger _logger; + + public LogRequestsAttribute() + { + var factory = LoggerFactory.Create(builder => builder.AddConsole()); + _logger = factory.CreateLogger(); + } + + public LogRequestsAttribute(ILogger logger) + { + _logger = logger; + } + + public void OnActionExecuting(ActionExecutingContext context) + { + var descriptor = (ControllerActionDescriptor)context.ActionDescriptor; + + var messages = context.ActionArguments.Select(a => LogMessageForObject(a.Value)); + var message = LogMessage("Request", descriptor, messages); + _logger.LogInformation(message); + } + + public void OnActionExecuted(ActionExecutedContext context) + { + if (context.Result is ObjectResult result) + { + var descriptor = (ControllerActionDescriptor)context.ActionDescriptor; + var messages = new List { LogMessageForObject(result.Value) }; + var message = LogMessage("Response", descriptor, messages); + _logger.LogInformation(message); + } + } + + private static string LogMessage(string type, ControllerActionDescriptor descriptor, IEnumerable messages) + { + var actionName = descriptor.ActionName; + var controllerName = descriptor.ControllerName; + var payload = string.Join("\n", messages.Where(m => !string.IsNullOrEmpty(m))); + + return $"{type} {controllerName}:{actionName} - {payload}"; + } + + private static string LogMessageForObject(object obj) + { + var type = obj.GetType().GetGenericArguments().FirstOrDefault(); + + if (type == null) + { + type = obj.GetType(); + } + + if (type.GetCustomAttribute() == null) + { + return null; + } + + return JsonConvert.SerializeObject( + obj, + Formatting.None, + new JsonSerializerSettings + { + ContractResolver = new FilterSensitiveDataContractResolver(), + }); + } + + private class FilterSensitiveDataContractResolver : DefaultContractResolver + { + protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization) + { + var property = base.CreateProperty(member, memberSerialization); + + property.ShouldSerialize = instance => + instance.GetType().GetCustomAttribute() != null && + member.GetCustomAttribute() == null && + member.GetCustomAttribute() == null; + + return property; + } + } + } +} diff --git a/GetIntoTeachingApi/Attributes/LoggableAttribute.cs b/GetIntoTeachingApi/Attributes/LoggableAttribute.cs new file mode 100644 index 000000000..e6051e423 --- /dev/null +++ b/GetIntoTeachingApi/Attributes/LoggableAttribute.cs @@ -0,0 +1,9 @@ +using System; + +namespace GetIntoTeachingApi.Attributes +{ + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] + public class LoggableAttribute : Attribute + { + } +} diff --git a/GetIntoTeachingApi/Attributes/SensitiveDataAttribute.cs b/GetIntoTeachingApi/Attributes/SensitiveDataAttribute.cs new file mode 100644 index 000000000..2475487e0 --- /dev/null +++ b/GetIntoTeachingApi/Attributes/SensitiveDataAttribute.cs @@ -0,0 +1,9 @@ +using System; + +namespace GetIntoTeachingApi.Attributes +{ + [AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)] + public class SensitiveDataAttribute : Attribute + { + } +} diff --git a/GetIntoTeachingApi/Controllers/CallbackBookingQuotasController.cs b/GetIntoTeachingApi/Controllers/CallbackBookingQuotasController.cs index 659225c99..a13378afc 100644 --- a/GetIntoTeachingApi/Controllers/CallbackBookingQuotasController.cs +++ b/GetIntoTeachingApi/Controllers/CallbackBookingQuotasController.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using GetIntoTeachingApi.Attributes; using GetIntoTeachingApi.Models; using GetIntoTeachingApi.Services; using Microsoft.AspNetCore.Authorization; @@ -9,6 +10,7 @@ namespace GetIntoTeachingApi.Controllers { [Route("api/callback_booking_quotas")] [ApiController] + [LogRequests] [Authorize] public class CallbackBookingQuotasController : ControllerBase { diff --git a/GetIntoTeachingApi/Controllers/CandidatesController.cs b/GetIntoTeachingApi/Controllers/CandidatesController.cs index 668171f2a..5502f5d30 100644 --- a/GetIntoTeachingApi/Controllers/CandidatesController.cs +++ b/GetIntoTeachingApi/Controllers/CandidatesController.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using GetIntoTeachingApi.Attributes; using GetIntoTeachingApi.Models; using GetIntoTeachingApi.Services; using Microsoft.AspNetCore.Authorization; @@ -9,6 +10,7 @@ namespace GetIntoTeachingApi.Controllers { [Route("api/candidates")] [ApiController] + [LogRequests] [Authorize] public class CandidatesController : ControllerBase { diff --git a/GetIntoTeachingApi/Controllers/MailingListController.cs b/GetIntoTeachingApi/Controllers/MailingListController.cs index 14e95a2b6..dcaba1fe1 100644 --- a/GetIntoTeachingApi/Controllers/MailingListController.cs +++ b/GetIntoTeachingApi/Controllers/MailingListController.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using GetIntoTeachingApi.Attributes; using GetIntoTeachingApi.Jobs; using GetIntoTeachingApi.Models; using GetIntoTeachingApi.Services; @@ -11,6 +12,7 @@ namespace GetIntoTeachingApi.Controllers { [Route("api/mailing_list")] [ApiController] + [LogRequests] [Authorize] public class MailingListController : ControllerBase { diff --git a/GetIntoTeachingApi/Controllers/OperationsController.cs b/GetIntoTeachingApi/Controllers/OperationsController.cs index dbff40c9a..802f35c27 100644 --- a/GetIntoTeachingApi/Controllers/OperationsController.cs +++ b/GetIntoTeachingApi/Controllers/OperationsController.cs @@ -1,7 +1,7 @@ -using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using GetIntoTeachingApi.Attributes; using GetIntoTeachingApi.Models; using GetIntoTeachingApi.Services; using GetIntoTeachingApi.Utils; @@ -12,6 +12,7 @@ namespace GetIntoTeachingApi.Controllers { [Route("api/operations")] [ApiController] + [LogRequests] public class OperationsController : ControllerBase { private readonly IStore _store; diff --git a/GetIntoTeachingApi/Controllers/PrivacyPoliciesController.cs b/GetIntoTeachingApi/Controllers/PrivacyPoliciesController.cs index 4ac4cd8ef..a3a6dbea6 100644 --- a/GetIntoTeachingApi/Controllers/PrivacyPoliciesController.cs +++ b/GetIntoTeachingApi/Controllers/PrivacyPoliciesController.cs @@ -1,6 +1,6 @@ using System; using System.Threading.Tasks; -using GetIntoTeachingApi.Filters; +using GetIntoTeachingApi.Attributes; using GetIntoTeachingApi.Models; using GetIntoTeachingApi.Services; using Microsoft.AspNetCore.Authorization; @@ -11,6 +11,7 @@ namespace GetIntoTeachingApi.Controllers { [Route("api/privacy_policies")] [ApiController] + [LogRequests] [Authorize] public class PrivacyPoliciesController : ControllerBase { diff --git a/GetIntoTeachingApi/Controllers/TeacherTrainingAdviser/CandidatesController.cs b/GetIntoTeachingApi/Controllers/TeacherTrainingAdviser/CandidatesController.cs index 3018db53e..8540cd616 100644 --- a/GetIntoTeachingApi/Controllers/TeacherTrainingAdviser/CandidatesController.cs +++ b/GetIntoTeachingApi/Controllers/TeacherTrainingAdviser/CandidatesController.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using GetIntoTeachingApi.Attributes; using GetIntoTeachingApi.Jobs; using GetIntoTeachingApi.Models; using GetIntoTeachingApi.Services; @@ -11,6 +12,7 @@ namespace GetIntoTeachingApi.Controllers.TeacherTrainingAdviser { [Route("api/teacher_training_adviser/candidates")] [ApiController] + [LogRequests] [Authorize] public class CandidatesController : ControllerBase { diff --git a/GetIntoTeachingApi/Controllers/TeachingEventsController.cs b/GetIntoTeachingApi/Controllers/TeachingEventsController.cs index 018341ee4..2dcdd15af 100644 --- a/GetIntoTeachingApi/Controllers/TeachingEventsController.cs +++ b/GetIntoTeachingApi/Controllers/TeachingEventsController.cs @@ -1,6 +1,6 @@ using System.Collections.Generic; using System.Threading.Tasks; -using GetIntoTeachingApi.Filters; +using GetIntoTeachingApi.Attributes; using GetIntoTeachingApi.Jobs; using GetIntoTeachingApi.Models; using GetIntoTeachingApi.Services; @@ -13,6 +13,7 @@ namespace GetIntoTeachingApi.Controllers { [Route("api/teaching_events")] [ApiController] + [LogRequests] [Authorize] public class TeachingEventsController : ControllerBase { diff --git a/GetIntoTeachingApi/Controllers/TypesController.cs b/GetIntoTeachingApi/Controllers/TypesController.cs index 58948d534..808b78931 100644 --- a/GetIntoTeachingApi/Controllers/TypesController.cs +++ b/GetIntoTeachingApi/Controllers/TypesController.cs @@ -1,6 +1,6 @@ using System.Collections.Generic; using System.Threading.Tasks; -using GetIntoTeachingApi.Filters; +using GetIntoTeachingApi.Attributes; using GetIntoTeachingApi.Models; using GetIntoTeachingApi.Services; using Microsoft.AspNetCore.Authorization; @@ -12,6 +12,7 @@ namespace GetIntoTeachingApi.Controllers { [Route("api/types")] [ApiController] + [LogRequests] [Authorize] public class TypesController : ControllerBase { diff --git a/GetIntoTeachingApiTests/Attributes/CrmETagAttributeTests.cs b/GetIntoTeachingApiTests/Attributes/CrmETagAttributeTests.cs index 867341a8b..e6e7aed71 100644 --- a/GetIntoTeachingApiTests/Attributes/CrmETagAttributeTests.cs +++ b/GetIntoTeachingApiTests/Attributes/CrmETagAttributeTests.cs @@ -2,7 +2,7 @@ using System.Linq; using System.Net; using FluentAssertions; -using GetIntoTeachingApi.Filters; +using GetIntoTeachingApi.Attributes; using GetIntoTeachingApi.Jobs; using GetIntoTeachingApi.Services; using GetIntoTeachingApi.Utils; diff --git a/GetIntoTeachingApiTests/Attributes/LogRequestsAttributeTests.cs b/GetIntoTeachingApiTests/Attributes/LogRequestsAttributeTests.cs new file mode 100644 index 000000000..78b6cf3bd --- /dev/null +++ b/GetIntoTeachingApiTests/Attributes/LogRequestsAttributeTests.cs @@ -0,0 +1,175 @@ +using System.Collections.Generic; +using System.Text.Json.Serialization; +using GetIntoTeachingApi.Attributes; +using GetIntoTeachingApiTests.Helpers; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Abstractions; +using Microsoft.AspNetCore.Mvc.Controllers; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.AspNetCore.Routing; +using Microsoft.Extensions.Logging; +using Moq; +using Xunit; + +namespace GetIntoTeachingApiTests.Filters +{ + public class LogRequestsAttributeTests + { + private readonly LogRequestsAttribute _filter; + private readonly ActionExecutingContext _actionExecutingContext; + private readonly ActionExecutedContext _actionExecutedContext; + private readonly Mock> _mockLogger; + + public LogRequestsAttributeTests() + { + var actionContext = new ActionContext( + Mock.Of(), + Mock.Of(), + Mock.Of(), + new ModelStateDictionary() + ) + { + ActionDescriptor = new ControllerActionDescriptor() + { + ActionName = "Action", + ControllerName = "Controller" + } + }; + + _actionExecutingContext = new ActionExecutingContext( + actionContext, + new List(), + new Dictionary(), + Mock.Of() + ); + + _actionExecutedContext = new ActionExecutedContext( + actionContext, + new List(), + Mock.Of() + ); + + _mockLogger = new Mock>(); + + _filter = new LogRequestsAttribute(_mockLogger.Object); + } + + [Fact] + public void OnActionExecuting_WithLoggable_LogsTheRequestWithPayload() + { + _actionExecutingContext.ActionArguments.Add("person", new StubLoggable()); + + _filter.OnActionExecuting(_actionExecutingContext); + + _mockLogger.VerifyInformationWasCalledExactly("Request Controller:Action - {\"Name\":\"Ross\"}"); + } + + [Fact] + public void OnActionExecuting_WithMultipleArgumentsOfLoggable_LogsTheRequestWithPayloads() + { + _actionExecutingContext.ActionArguments.Add("person1", new StubLoggable("Ross")); + _actionExecutingContext.ActionArguments.Add("person2", new StubLoggable("James")); + _actionExecutingContext.ActionArguments.Add("person3", new StubUnloggable()); + + _filter.OnActionExecuting(_actionExecutingContext); + + _mockLogger.VerifyInformationWasCalledExactly("Request Controller:Action - {\"Name\":\"Ross\"}\n{\"Name\":\"James\"}"); + } + + [Fact] + public void OnActionExecuting_WithArrayOfLoggable_LogsTheRequestWithPayload() + { + var people = new List() { new StubLoggable("Ross"), new StubLoggable("James") }; + _actionExecutingContext.ActionArguments.Add("people", people); + + _filter.OnActionExecuting(_actionExecutingContext); + + _mockLogger.VerifyInformationWasCalledExactly("Request Controller:Action - [{\"Name\":\"Ross\"},{\"Name\":\"James\"}]"); + } + + [Fact] + public void OnActionExecuting_WithUnloggable_LogsTheRequestWithoutPayload() + { + _actionExecutingContext.ActionArguments.Add("person", new StubUnloggable()); + + _filter.OnActionExecuting(_actionExecutingContext); + + _mockLogger.VerifyInformationWasCalledExactly("Request Controller:Action - "); + } + + [Fact] + public void OnActionExecuting_WithPrimitive_LogsTheRequestWithoutPayload() + { + _actionExecutingContext.ActionArguments.Add("password", 12345); + + _filter.OnActionExecuting(_actionExecutingContext); + + _mockLogger.VerifyInformationWasCalledExactly("Request Controller:Action - "); + } + + [Fact] + public void OnActionExecuted_WithLoggable_LogsTheResponseWithPayload() + { + _actionExecutedContext.Result = new OkObjectResult(new StubLoggable()); + + _filter.OnActionExecuted(_actionExecutedContext); + + _mockLogger.VerifyInformationWasCalledExactly("Response Controller:Action - {\"Name\":\"Ross\"}"); + } + + [Fact] + public void OnActionExecuted_WithArrayOfLoggable_LogsTheResponseWithPayload() + { + var people = new List() { new StubLoggable("Ross"), new StubLoggable("James") }; + _actionExecutedContext.Result = new OkObjectResult(people); + + _filter.OnActionExecuted(_actionExecutedContext); + + _mockLogger.VerifyInformationWasCalledExactly("Response Controller:Action - [{\"Name\":\"Ross\"},{\"Name\":\"James\"}]"); + } + + [Fact] + public void OnActionExecuted_WithUnloggable_LogsTheRequestWithoutPayload() + { + _actionExecutedContext.Result = new OkObjectResult(new StubUnloggable()); + + _filter.OnActionExecuted(_actionExecutedContext); + + _mockLogger.VerifyInformationWasCalledExactly("Response Controller:Action - "); + } + + [Fact] + public void OnActionExecuted_WithPrimitive_LogsTheRequestWithoutPayload() + { + _actionExecutedContext.Result = new OkObjectResult(12345); + + _filter.OnActionExecuted(_actionExecutedContext); + + _mockLogger.VerifyInformationWasCalledExactly("Response Controller:Action - "); + } + + [Loggable] + private class StubLoggable + { + public string Name { get; set; } = "Ross"; + [SensitiveData] + public string Password { get; set; } = "sensitive"; + [JsonIgnore] + public string Ignored { get; set; } = "ignored"; + + public StubLoggable() { } + + public StubLoggable(string name) + { + Name = name; + } + } + + private class StubUnloggable + { + public string Name { get; set; } = "Ross"; + } + } +} diff --git a/GetIntoTeachingApiTests/Controllers/CallbackBookingQuotasControllerTests.cs b/GetIntoTeachingApiTests/Controllers/CallbackBookingQuotasControllerTests.cs index 85370792d..30a3d39a1 100644 --- a/GetIntoTeachingApiTests/Controllers/CallbackBookingQuotasControllerTests.cs +++ b/GetIntoTeachingApiTests/Controllers/CallbackBookingQuotasControllerTests.cs @@ -7,6 +7,7 @@ using System; using Microsoft.AspNetCore.Authorization; using Xunit; +using GetIntoTeachingApi.Attributes; namespace GetIntoTeachingApiTests.Controllers { @@ -27,6 +28,12 @@ public void Authorize_IsPresent() typeof(CallbackBookingQuotasController).Should().BeDecoratedWith(); } + [Fact] + public void LogRequests_IsPresent() + { + typeof(CallbackBookingQuotasController).Should().BeDecoratedWith(); + } + [Fact] public void GetAll_ReturnsAllQuotas() { diff --git a/GetIntoTeachingApiTests/Controllers/CandidatesControllerTests.cs b/GetIntoTeachingApiTests/Controllers/CandidatesControllerTests.cs index b9e5fb920..ee5df3827 100644 --- a/GetIntoTeachingApiTests/Controllers/CandidatesControllerTests.cs +++ b/GetIntoTeachingApiTests/Controllers/CandidatesControllerTests.cs @@ -7,6 +7,7 @@ using GetIntoTeachingApi.Services; using System.Collections.Generic; using Microsoft.AspNetCore.Authorization; +using GetIntoTeachingApi.Attributes; namespace GetIntoTeachingApiTests.Controllers { @@ -31,6 +32,12 @@ public void Authorize_IsPresent() typeof(CandidatesController).Should().BeDecoratedWith(); } + [Fact] + public void LogRequests_IsPresent() + { + typeof(CandidatesController).Should().BeDecoratedWith(); + } + [Fact] public void CreateAccessToken_InvalidRequest_RespondsWithValidationErrors() { diff --git a/GetIntoTeachingApiTests/Controllers/MailingListControllerTests.cs b/GetIntoTeachingApiTests/Controllers/MailingListControllerTests.cs index c66e615dc..9e285b6a9 100644 --- a/GetIntoTeachingApiTests/Controllers/MailingListControllerTests.cs +++ b/GetIntoTeachingApiTests/Controllers/MailingListControllerTests.cs @@ -11,6 +11,7 @@ using Microsoft.AspNetCore.Authorization; using Moq; using GetIntoTeachingApi.Services; +using GetIntoTeachingApi.Attributes; namespace GetIntoTeachingApiTests.Controllers { @@ -37,6 +38,12 @@ public void Authorize_IsPresent() typeof(MailingListController).Should().BeDecoratedWith(); } + [Fact] + public void LogRequests_IsPresent() + { + typeof(MailingListController).Should().BeDecoratedWith(); + } + [Fact] public void GetMember_InvalidAccessToken_RespondsWithUnauthorized() { diff --git a/GetIntoTeachingApiTests/Controllers/OperationsControllerTests.cs b/GetIntoTeachingApiTests/Controllers/OperationsControllerTests.cs index e4c1f5467..ac56ba9cf 100644 --- a/GetIntoTeachingApiTests/Controllers/OperationsControllerTests.cs +++ b/GetIntoTeachingApiTests/Controllers/OperationsControllerTests.cs @@ -8,6 +8,7 @@ using GetIntoTeachingApi.Utils; using Moq; using Xunit; +using GetIntoTeachingApi.Attributes; namespace GetIntoTeachingApiTests.Controllers { @@ -89,5 +90,11 @@ public async void HealthCheck_ReturnsCorrectly(bool database, bool hangfire, boo health.Hangfire.Should().Be(hangfireStatus); health.Status.Should().Be(expectedStatus); } + + [Fact] + public void LogRequests_IsPresent() + { + typeof(OperationsController).Should().BeDecoratedWith(); + } } } \ No newline at end of file diff --git a/GetIntoTeachingApiTests/Controllers/PrivacyPoliciesControllerTests.cs b/GetIntoTeachingApiTests/Controllers/PrivacyPoliciesControllerTests.cs index 696797ebd..feea94b90 100644 --- a/GetIntoTeachingApiTests/Controllers/PrivacyPoliciesControllerTests.cs +++ b/GetIntoTeachingApiTests/Controllers/PrivacyPoliciesControllerTests.cs @@ -5,10 +5,10 @@ using Microsoft.AspNetCore.Mvc; using Moq; using System; -using GetIntoTeachingApi.Filters; using Hangfire; using Microsoft.AspNetCore.Authorization; using Xunit; +using GetIntoTeachingApi.Attributes; namespace GetIntoTeachingApiTests.Controllers { @@ -29,6 +29,12 @@ public void Authorize_IsPresent() typeof(PrivacyPoliciesController).Should().BeDecoratedWith(); } + [Fact] + public void LogRequests_IsPresent() + { + typeof(PrivacyPoliciesController).Should().BeDecoratedWith(); + } + [Fact] public void CrmETag_IsPresent() { diff --git a/GetIntoTeachingApiTests/Controllers/TeacherTrainingAdviser/CandidatesControllerTests.cs b/GetIntoTeachingApiTests/Controllers/TeacherTrainingAdviser/CandidatesControllerTests.cs index 0478f0cd9..a8998a8ab 100644 --- a/GetIntoTeachingApiTests/Controllers/TeacherTrainingAdviser/CandidatesControllerTests.cs +++ b/GetIntoTeachingApiTests/Controllers/TeacherTrainingAdviser/CandidatesControllerTests.cs @@ -11,6 +11,7 @@ using Hangfire.Common; using Hangfire.States; using Microsoft.AspNetCore.Authorization; +using GetIntoTeachingApi.Attributes; namespace GetIntoTeachingApiTests.Controllers.TeacherTrainingAdviser { @@ -37,6 +38,12 @@ public void Authorize_IsPresent() typeof(CandidatesController).Should().BeDecoratedWith(); } + [Fact] + public void LogRequests_IsPresent() + { + typeof(CandidatesController).Should().BeDecoratedWith(); + } + [Fact] public void Get_InvalidAccessToken_RespondsWithUnauthorized() { diff --git a/GetIntoTeachingApiTests/Controllers/TeachingEventsControllerTests.cs b/GetIntoTeachingApiTests/Controllers/TeachingEventsControllerTests.cs index 4db62032c..f2465b39e 100644 --- a/GetIntoTeachingApiTests/Controllers/TeachingEventsControllerTests.cs +++ b/GetIntoTeachingApiTests/Controllers/TeachingEventsControllerTests.cs @@ -3,7 +3,6 @@ using FluentAssertions; using Xunit; using GetIntoTeachingApi.Controllers; -using GetIntoTeachingApi.Filters; using GetIntoTeachingApi.Jobs; using GetIntoTeachingApi.Models; using Moq; @@ -14,6 +13,7 @@ using Hangfire.States; using Microsoft.AspNetCore.Authorization; using MoreLinq; +using GetIntoTeachingApi.Attributes; namespace GetIntoTeachingApiTests.Controllers { @@ -42,6 +42,12 @@ public void Authorize_IsPresent() typeof(TeachingEventsController).Should().BeDecoratedWith(); } + [Fact] + public void LogRequests_IsPresent() + { + typeof(TeachingEventsController).Should().BeDecoratedWith(); + } + [Fact] public void CrmETag_IsPresent() { diff --git a/GetIntoTeachingApiTests/Controllers/TypesControllerTests.cs b/GetIntoTeachingApiTests/Controllers/TypesControllerTests.cs index c2b0933be..ecf993266 100644 --- a/GetIntoTeachingApiTests/Controllers/TypesControllerTests.cs +++ b/GetIntoTeachingApiTests/Controllers/TypesControllerTests.cs @@ -7,11 +7,11 @@ using Moq; using System; using System.Reflection; -using GetIntoTeachingApi.Filters; using Hangfire; using Microsoft.AspNetCore.Authorization; using MoreLinq; using Xunit; +using GetIntoTeachingApi.Attributes; namespace GetIntoTeachingApiTests.Controllers { @@ -32,6 +32,12 @@ public void Authorize_IsPresent() typeof(TypesController).Should().BeDecoratedWith(); } + [Fact] + public void LogRequests_IsPresent() + { + typeof(TypesController).Should().BeDecoratedWith(); + } + [Fact] public void CrmETag_IsPresent() { diff --git a/GetIntoTeachingApiTests/Helpers/LoggerTestHelpers.cs b/GetIntoTeachingApiTests/Helpers/LoggerTestHelpers.cs index ae64e7d70..e0f3ee6d0 100644 --- a/GetIntoTeachingApiTests/Helpers/LoggerTestHelpers.cs +++ b/GetIntoTeachingApiTests/Helpers/LoggerTestHelpers.cs @@ -11,15 +11,36 @@ public static Mock> VerifyInformationWasCalled(this Mock> VerifyInformationWasCalledExactly(this Mock> logger, string expectedMessage) + { + return VerifyCalledExactly(logger, LogLevel.Information, expectedMessage); + } + public static Mock> VerifyWarningWasCalled(this Mock> logger, string expectedMessage) { return VerifyCalled(logger, LogLevel.Warning, expectedMessage); } + private static Mock> VerifyCalledExactly(this Mock> logger, LogLevel expectedLogLevel, string expectedMessage) + { + bool state(object v, Type t) => v.ToString() == expectedMessage; + + VerifyCalled(logger, expectedLogLevel, state); + + return logger; + } + private static Mock> VerifyCalled(this Mock> logger, LogLevel expectedLogLevel, string expectedMessage) { - Func state = (v, t) => v.ToString().Contains(expectedMessage); + bool state(object v, Type t) => v.ToString().Contains(expectedMessage); + + VerifyCalled(logger, expectedLogLevel, state); + + return logger; + } + private static void VerifyCalled(this Mock> logger, LogLevel expectedLogLevel, Func state) + { logger.Verify( mock => mock.Log( It.Is(logLevel => logLevel == expectedLogLevel), @@ -29,8 +50,6 @@ private static Mock> VerifyCalled(this Mock> logger, Lo It.Is>((v, t) => true) ) ); - - return logger; } } } From 6a8950b82bf95c2312b04bd450191746fec92adb Mon Sep 17 00:00:00 2001 From: Ross Oliver Date: Tue, 29 Sep 2020 14:44:15 +0100 Subject: [PATCH 2/2] Make request/response models loggable Add the `Loggable` attribute to request/response models. Add the `SensitiveData` attribute to any attributes within the models that we want to omit from the logs. --- .../Models/CallbackBookingQuota.cs | 1 + .../Models/HealthCheckResponse.cs | 2 ++ .../Models/MailingListAddMember.cs | 7 +++++++ GetIntoTeachingApi/Models/MappingInfo.cs | 2 ++ .../Models/TeacherTrainingAdviserSignUp.cs | 11 +++++++++++ GetIntoTeachingApi/Models/TeachingEvent.cs | 1 + .../Models/TeachingEventAddAttendee.cs | 7 +++++++ .../Models/TeachingEventBuilding.cs | 1 + .../Models/TeachingEventSearchRequest.cs | 2 ++ GetIntoTeachingApi/Models/TypeEntity.cs | 2 ++ .../Models/CallbackBookingQuotaTests.cs | 6 ++++++ .../Models/ExistingCandidateRequestTests.cs | 1 + .../Models/HealthCheckResponseTests.cs | 7 +++++++ .../Models/MailingListAddMemberTests.cs | 15 +++++++++++++++ .../Models/MappingInfoTests.cs | 7 +++++++ .../TeacherTrainingAdviserSignUpTests.cs | 19 +++++++++++++++++++ .../Models/TeachingEventAddAttendeeTests.cs | 15 +++++++++++++++ .../Models/TeachingEventBuildingTests.cs | 6 ++++++ .../Models/TeachingEventSearchRequestTests.cs | 7 +++++++ .../Models/TeachingEventTests.cs | 6 ++++++ .../Models/TypeEntityTests.cs | 13 +++++++++++-- 21 files changed, 136 insertions(+), 2 deletions(-) diff --git a/GetIntoTeachingApi/Models/CallbackBookingQuota.cs b/GetIntoTeachingApi/Models/CallbackBookingQuota.cs index bf8223faa..ee1893349 100644 --- a/GetIntoTeachingApi/Models/CallbackBookingQuota.cs +++ b/GetIntoTeachingApi/Models/CallbackBookingQuota.cs @@ -5,6 +5,7 @@ namespace GetIntoTeachingApi.Models { + [Loggable] [Entity("dfe_callbackbookingquota")] public class CallbackBookingQuota : BaseModel { diff --git a/GetIntoTeachingApi/Models/HealthCheckResponse.cs b/GetIntoTeachingApi/Models/HealthCheckResponse.cs index b86054e24..4880b5436 100644 --- a/GetIntoTeachingApi/Models/HealthCheckResponse.cs +++ b/GetIntoTeachingApi/Models/HealthCheckResponse.cs @@ -1,9 +1,11 @@ using System.Collections.Generic; using System.Linq; using System.Text.Json.Serialization; +using GetIntoTeachingApi.Attributes; namespace GetIntoTeachingApi.Models { + [Loggable] public class HealthCheckResponse { public const string StatusOk = "ok"; diff --git a/GetIntoTeachingApi/Models/MailingListAddMember.cs b/GetIntoTeachingApi/Models/MailingListAddMember.cs index acb6912e0..9c5656f40 100644 --- a/GetIntoTeachingApi/Models/MailingListAddMember.cs +++ b/GetIntoTeachingApi/Models/MailingListAddMember.cs @@ -1,10 +1,12 @@ using System; using System.Linq; using System.Text.Json.Serialization; +using GetIntoTeachingApi.Attributes; using Swashbuckle.AspNetCore.Annotations; namespace GetIntoTeachingApi.Models { + [Loggable] public class MailingListAddMember { public Guid? CandidateId { get; set; } @@ -18,10 +20,15 @@ public class MailingListAddMember [SwaggerSchema(WriteOnly = true)] public int? ChannelId { get; set; } + [SensitiveData] public string Email { get; set; } + [SensitiveData] public string FirstName { get; set; } + [SensitiveData] public string LastName { get; set; } + [SensitiveData] public string AddressPostcode { get; set; } + [SensitiveData] public string Telephone { get; set; } [SwaggerSchema(ReadOnly = true)] public bool AlreadySubscribedToEvents { get; set; } diff --git a/GetIntoTeachingApi/Models/MappingInfo.cs b/GetIntoTeachingApi/Models/MappingInfo.cs index ca956fa2a..1e4273df2 100644 --- a/GetIntoTeachingApi/Models/MappingInfo.cs +++ b/GetIntoTeachingApi/Models/MappingInfo.cs @@ -1,8 +1,10 @@ using System; using System.Collections.Generic; +using GetIntoTeachingApi.Attributes; namespace GetIntoTeachingApi.Models { + [Loggable] public class MappingInfo { private readonly Type _type; diff --git a/GetIntoTeachingApi/Models/TeacherTrainingAdviserSignUp.cs b/GetIntoTeachingApi/Models/TeacherTrainingAdviserSignUp.cs index 4895dedfa..65e8e0222 100644 --- a/GetIntoTeachingApi/Models/TeacherTrainingAdviserSignUp.cs +++ b/GetIntoTeachingApi/Models/TeacherTrainingAdviserSignUp.cs @@ -1,10 +1,12 @@ using System; using System.Linq; using System.Text.Json.Serialization; +using GetIntoTeachingApi.Attributes; using Swashbuckle.AspNetCore.Annotations; namespace GetIntoTeachingApi.Models { + [Loggable] public class TeacherTrainingAdviserSignUp { public Guid? CandidateId { get; set; } @@ -25,17 +27,26 @@ public class TeacherTrainingAdviserSignUp public int? PlanningToRetakeGcseMathsAndEnglishId { get; set; } public int? PlanningToRetakeGcseScienceId { get; set; } + [SensitiveData] public string Email { get; set; } + [SensitiveData] public string FirstName { get; set; } + [SensitiveData] public string LastName { get; set; } [SwaggerSchema(Format = "date")] + [SensitiveData] public DateTime? DateOfBirth { get; set; } + [SensitiveData] public string TeacherId { get; set; } public string DegreeSubject { get; set; } + [SensitiveData] public string Telephone { get; set; } + [SensitiveData] public string AddressLine1 { get; set; } + [SensitiveData] public string AddressLine2 { get; set; } public string AddressCity { get; set; } + [SensitiveData] public string AddressPostcode { get; set; } [SwaggerSchema(WriteOnly = true)] public DateTime? PhoneCallScheduledAt { get; set; } diff --git a/GetIntoTeachingApi/Models/TeachingEvent.cs b/GetIntoTeachingApi/Models/TeachingEvent.cs index 39e53e439..e364f6a2f 100644 --- a/GetIntoTeachingApi/Models/TeachingEvent.cs +++ b/GetIntoTeachingApi/Models/TeachingEvent.cs @@ -7,6 +7,7 @@ namespace GetIntoTeachingApi.Models { + [Loggable] [Entity("msevtmgt_event")] public class TeachingEvent : BaseModel { diff --git a/GetIntoTeachingApi/Models/TeachingEventAddAttendee.cs b/GetIntoTeachingApi/Models/TeachingEventAddAttendee.cs index 09af56791..4a9f48be6 100644 --- a/GetIntoTeachingApi/Models/TeachingEventAddAttendee.cs +++ b/GetIntoTeachingApi/Models/TeachingEventAddAttendee.cs @@ -1,10 +1,12 @@ using System; using System.Linq; using System.Text.Json.Serialization; +using GetIntoTeachingApi.Attributes; using Swashbuckle.AspNetCore.Annotations; namespace GetIntoTeachingApi.Models { + [Loggable] public class TeachingEventAddAttendee { public Guid? CandidateId { get; set; } @@ -19,10 +21,15 @@ public class TeachingEventAddAttendee public int? ConsiderationJourneyStageId { get; set; } public int? DegreeStatusId { get; set; } + [SensitiveData] public string Email { get; set; } + [SensitiveData] public string FirstName { get; set; } + [SensitiveData] public string LastName { get; set; } + [SensitiveData] public string AddressPostcode { get; set; } + [SensitiveData] public string Telephone { get; set; } [SwaggerSchema(ReadOnly = true)] public bool SubscribeToEvents => AddressPostcode != null && SubscribeToMailingList; diff --git a/GetIntoTeachingApi/Models/TeachingEventBuilding.cs b/GetIntoTeachingApi/Models/TeachingEventBuilding.cs index 3536dbefd..0fb3049b6 100644 --- a/GetIntoTeachingApi/Models/TeachingEventBuilding.cs +++ b/GetIntoTeachingApi/Models/TeachingEventBuilding.cs @@ -8,6 +8,7 @@ namespace GetIntoTeachingApi.Models { + [Loggable] [Entity("msevtmgt_building")] public class TeachingEventBuilding : BaseModel { diff --git a/GetIntoTeachingApi/Models/TeachingEventSearchRequest.cs b/GetIntoTeachingApi/Models/TeachingEventSearchRequest.cs index 66aed7dbf..ca7b9b332 100644 --- a/GetIntoTeachingApi/Models/TeachingEventSearchRequest.cs +++ b/GetIntoTeachingApi/Models/TeachingEventSearchRequest.cs @@ -1,8 +1,10 @@ using System; +using GetIntoTeachingApi.Attributes; using Swashbuckle.AspNetCore.Annotations; namespace GetIntoTeachingApi.Models { + [Loggable] public class TeachingEventSearchRequest : ICloneable { [SwaggerSchema("Postcode to center search around.")] diff --git a/GetIntoTeachingApi/Models/TypeEntity.cs b/GetIntoTeachingApi/Models/TypeEntity.cs index 8808ec7b7..4e4ee8900 100644 --- a/GetIntoTeachingApi/Models/TypeEntity.cs +++ b/GetIntoTeachingApi/Models/TypeEntity.cs @@ -1,11 +1,13 @@ using System; using System.ComponentModel.DataAnnotations.Schema; using System.Text.Json.Serialization; +using GetIntoTeachingApi.Attributes; using Microsoft.PowerPlatform.Cds.Client; using Microsoft.Xrm.Sdk; namespace GetIntoTeachingApi.Models { + [Loggable] public class TypeEntity { public static readonly Guid UnitedKingdomCountryId = new Guid("72f5c2e6-74f9-e811-a97a-000d3a2760f2"); diff --git a/GetIntoTeachingApiTests/Models/CallbackBookingQuotaTests.cs b/GetIntoTeachingApiTests/Models/CallbackBookingQuotaTests.cs index ff0854872..f5bbace03 100644 --- a/GetIntoTeachingApiTests/Models/CallbackBookingQuotaTests.cs +++ b/GetIntoTeachingApiTests/Models/CallbackBookingQuotaTests.cs @@ -7,6 +7,12 @@ namespace GetIntoTeachingApiTests.Models { public class CallbackBookingQuotaTests { + [Fact] + public void Loggable_IsPresent() + { + typeof(CallbackBookingQuota).Should().BeDecoratedWith(); + } + [Fact] public void EntityAttributes() { diff --git a/GetIntoTeachingApiTests/Models/ExistingCandidateRequestTests.cs b/GetIntoTeachingApiTests/Models/ExistingCandidateRequestTests.cs index 6135ac71a..bec17adbf 100644 --- a/GetIntoTeachingApiTests/Models/ExistingCandidateRequestTests.cs +++ b/GetIntoTeachingApiTests/Models/ExistingCandidateRequestTests.cs @@ -3,6 +3,7 @@ using System; using Microsoft.Xrm.Sdk; using Xunit; +using GetIntoTeachingApi.Attributes; namespace GetIntoTeachingApiTests.Models { diff --git a/GetIntoTeachingApiTests/Models/HealthCheckResponseTests.cs b/GetIntoTeachingApiTests/Models/HealthCheckResponseTests.cs index 69aac0098..ffdf4ea3b 100644 --- a/GetIntoTeachingApiTests/Models/HealthCheckResponseTests.cs +++ b/GetIntoTeachingApiTests/Models/HealthCheckResponseTests.cs @@ -1,4 +1,5 @@ using FluentAssertions; +using GetIntoTeachingApi.Attributes; using GetIntoTeachingApi.Models; using Xunit; @@ -6,6 +7,12 @@ namespace GetIntoTeachingApiTests.Models { public class HealthCheckResponseTests { + [Fact] + public void Loggable_IsPresent() + { + typeof(HealthCheckResponse).Should().BeDecoratedWith(); + } + [Theory] [InlineData(true, true, true, true, "healthy")] [InlineData(true, true, false, true, "degraded")] diff --git a/GetIntoTeachingApiTests/Models/MailingListAddMemberTests.cs b/GetIntoTeachingApiTests/Models/MailingListAddMemberTests.cs index 3cab2736d..ec935d36d 100644 --- a/GetIntoTeachingApiTests/Models/MailingListAddMemberTests.cs +++ b/GetIntoTeachingApiTests/Models/MailingListAddMemberTests.cs @@ -1,4 +1,5 @@ using FluentAssertions; +using GetIntoTeachingApi.Attributes; using GetIntoTeachingApi.Models; using System; using System.Collections.Generic; @@ -9,6 +10,20 @@ namespace GetIntoTeachingApiTests.Models { public class MailingListAddMemberTests { + [Fact] + public void Loggable_IsPresent() + { + var type = typeof(MailingListAddMember); + + type.Should().BeDecoratedWith(); + + type.GetProperty("Email").Should().BeDecoratedWith(); + type.GetProperty("FirstName").Should().BeDecoratedWith(); + type.GetProperty("LastName").Should().BeDecoratedWith(); + type.GetProperty("Telephone").Should().BeDecoratedWith(); + type.GetProperty("AddressPostcode").Should().BeDecoratedWith(); + } + [Fact] public void Constructor_WithCandidate_MapsCorrectly() { diff --git a/GetIntoTeachingApiTests/Models/MappingInfoTests.cs b/GetIntoTeachingApiTests/Models/MappingInfoTests.cs index da0b2da8d..1bdcca77a 100644 --- a/GetIntoTeachingApiTests/Models/MappingInfoTests.cs +++ b/GetIntoTeachingApiTests/Models/MappingInfoTests.cs @@ -1,6 +1,7 @@ using System.Collections; using System.Collections.Generic; using FluentAssertions; +using GetIntoTeachingApi.Attributes; using GetIntoTeachingApi.Models; using GetIntoTeachingApiTests.Mocks; using Xunit; @@ -16,6 +17,12 @@ public MappingInfoTests() _mappingInfo = new MappingInfo(typeof(MockModel)); } + [Fact] + public void Loggable_IsPresent() + { + typeof(MappingInfo).Should().BeDecoratedWith(); + } + [Fact] public void Constructor_MapsClass() { diff --git a/GetIntoTeachingApiTests/Models/TeacherTrainingAdviserSignUpTests.cs b/GetIntoTeachingApiTests/Models/TeacherTrainingAdviserSignUpTests.cs index 8e0fd1ac3..d4e664ca5 100644 --- a/GetIntoTeachingApiTests/Models/TeacherTrainingAdviserSignUpTests.cs +++ b/GetIntoTeachingApiTests/Models/TeacherTrainingAdviserSignUpTests.cs @@ -1,4 +1,5 @@ using FluentAssertions; +using GetIntoTeachingApi.Attributes; using GetIntoTeachingApi.Models; using System; using System.Collections.Generic; @@ -9,6 +10,24 @@ namespace GetIntoTeachingApiTests.Models { public class TeacherTrainingAdviserSignUpTests { + [Fact] + public void Loggable_IsPresent() + { + var type = typeof(TeacherTrainingAdviserSignUp); + + type.Should().BeDecoratedWith(); + + type.GetProperty("Email").Should().BeDecoratedWith(); + type.GetProperty("FirstName").Should().BeDecoratedWith(); + type.GetProperty("LastName").Should().BeDecoratedWith(); + type.GetProperty("DateOfBirth").Should().BeDecoratedWith(); + type.GetProperty("TeacherId").Should().BeDecoratedWith(); + type.GetProperty("Telephone").Should().BeDecoratedWith(); + type.GetProperty("AddressLine1").Should().BeDecoratedWith(); + type.GetProperty("AddressLine2").Should().BeDecoratedWith(); + type.GetProperty("AddressPostcode").Should().BeDecoratedWith(); + } + [Fact] public void Constructor_WithCandidate_MapsCorrectly() { diff --git a/GetIntoTeachingApiTests/Models/TeachingEventAddAttendeeTests.cs b/GetIntoTeachingApiTests/Models/TeachingEventAddAttendeeTests.cs index 76cafc3a2..5c67bdb5a 100644 --- a/GetIntoTeachingApiTests/Models/TeachingEventAddAttendeeTests.cs +++ b/GetIntoTeachingApiTests/Models/TeachingEventAddAttendeeTests.cs @@ -1,4 +1,5 @@ using FluentAssertions; +using GetIntoTeachingApi.Attributes; using GetIntoTeachingApi.Models; using System; using System.Collections.Generic; @@ -9,6 +10,20 @@ namespace GetIntoTeachingApiTests.Models { public class TeachingEventAddAttendeeTests { + [Fact] + public void Loggable_IsPresent() + { + var type = typeof(TeachingEventAddAttendee); + + type.Should().BeDecoratedWith(); + + type.GetProperty("Email").Should().BeDecoratedWith(); + type.GetProperty("FirstName").Should().BeDecoratedWith(); + type.GetProperty("LastName").Should().BeDecoratedWith(); + type.GetProperty("Telephone").Should().BeDecoratedWith(); + type.GetProperty("AddressPostcode").Should().BeDecoratedWith(); + } + [Fact] public void Constructor_WithCandidate_MapsCorrectly() { diff --git a/GetIntoTeachingApiTests/Models/TeachingEventBuildingTests.cs b/GetIntoTeachingApiTests/Models/TeachingEventBuildingTests.cs index 7172f8ce0..98f86bcd1 100644 --- a/GetIntoTeachingApiTests/Models/TeachingEventBuildingTests.cs +++ b/GetIntoTeachingApiTests/Models/TeachingEventBuildingTests.cs @@ -7,6 +7,12 @@ namespace GetIntoTeachingApiTests.Models { public class TeachingEventBuildingTests { + [Fact] + public void Loggable_IsPresent() + { + typeof(TeachingEventBuilding).Should().BeDecoratedWith(); + } + [Fact] public void EntityAttributes() { diff --git a/GetIntoTeachingApiTests/Models/TeachingEventSearchRequestTests.cs b/GetIntoTeachingApiTests/Models/TeachingEventSearchRequestTests.cs index 97009433e..d3f6c0788 100644 --- a/GetIntoTeachingApiTests/Models/TeachingEventSearchRequestTests.cs +++ b/GetIntoTeachingApiTests/Models/TeachingEventSearchRequestTests.cs @@ -1,4 +1,5 @@ using FluentAssertions; +using GetIntoTeachingApi.Attributes; using GetIntoTeachingApi.Models; using Xunit; @@ -6,6 +7,12 @@ namespace GetIntoTeachingApiTests.Models { public class TeachingEventSearchRequestTests { + [Fact] + public void Loggable_IsPresent() + { + typeof(TeachingEventSearchRequest).Should().BeDecoratedWith(); + } + [Theory] [InlineData(1, 1.6093)] [InlineData(-1, -1.6093)] diff --git a/GetIntoTeachingApiTests/Models/TeachingEventTests.cs b/GetIntoTeachingApiTests/Models/TeachingEventTests.cs index cda494f2a..d8257daeb 100644 --- a/GetIntoTeachingApiTests/Models/TeachingEventTests.cs +++ b/GetIntoTeachingApiTests/Models/TeachingEventTests.cs @@ -8,6 +8,12 @@ namespace GetIntoTeachingApiTests.Models { public class TeachingEventTests { + [Fact] + public void Loggable_IsPresent() + { + typeof(TeachingEvent).Should().BeDecoratedWith(); + } + [Fact] public void EntityAttributes() { diff --git a/GetIntoTeachingApiTests/Models/TypeEntityTests.cs b/GetIntoTeachingApiTests/Models/TypeEntityTests.cs index a32419a82..e585041b2 100644 --- a/GetIntoTeachingApiTests/Models/TypeEntityTests.cs +++ b/GetIntoTeachingApiTests/Models/TypeEntityTests.cs @@ -1,6 +1,7 @@ using System; using System.ComponentModel.DataAnnotations.Schema; using FluentAssertions; +using GetIntoTeachingApi.Attributes; using GetIntoTeachingApi.Models; using Microsoft.PowerPlatform.Cds.Client; using Microsoft.Xrm.Sdk; @@ -10,6 +11,12 @@ namespace GetIntoTeachingApiTests.Models { public class TypeEntityTests { + [Fact] + public void Loggable_IsPresent() + { + typeof(TypeEntity).Should().BeDecoratedWith(); + } + [Fact] public void EntityAttributes() { @@ -22,8 +29,10 @@ public void EntityAttributes() [Fact] public void Constructor_WithEntity() { - var entity = new Entity(); - entity.Id = Guid.NewGuid(); + var entity = new Entity + { + Id = Guid.NewGuid() + }; entity["dfe_name"] = "name"; var typeEntity = new TypeEntity(entity, "entityName");