diff --git a/src/Fusion.Resources.sln b/src/Fusion.Resources.sln index e99eb061c..416a6f9ef 100644 --- a/src/Fusion.Resources.sln +++ b/src/Fusion.Resources.sln @@ -61,6 +61,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Fusion.Resources.Infrastruc EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Common", "Common", "{3C253096-BCFA-40D7-8C3F-F3F3B3BA4F1C}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Fusion.Summary.Functions.Tests", "tests\Fusion.Summary.Functions.Tests\Fusion.Summary.Functions.Tests.csproj", "{F819CC16-861C-4E8E-97FB-4B0928BD8704}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -163,6 +165,10 @@ Global {53F9FAF1-EFA4-4951-9693-1E350F6428A8}.Debug|Any CPU.Build.0 = Debug|Any CPU {53F9FAF1-EFA4-4951-9693-1E350F6428A8}.Release|Any CPU.ActiveCfg = Release|Any CPU {53F9FAF1-EFA4-4951-9693-1E350F6428A8}.Release|Any CPU.Build.0 = Release|Any CPU + {F819CC16-861C-4E8E-97FB-4B0928BD8704}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F819CC16-861C-4E8E-97FB-4B0928BD8704}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F819CC16-861C-4E8E-97FB-4B0928BD8704}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F819CC16-861C-4E8E-97FB-4B0928BD8704}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -193,6 +199,7 @@ Global {0F9CC682-2DD6-4422-A321-D7100780A739} = {286E3C76-C860-4661-9AEF-817458D1231B} {3C253096-BCFA-40D7-8C3F-F3F3B3BA4F1C} = {996B233A-1FC5-4CF5-97AD-B9C09F3E0A53} {CC1EF38A-2741-4D92-A33A-3970E6F73797} = {3C253096-BCFA-40D7-8C3F-F3F3B3BA4F1C} + {F819CC16-861C-4E8E-97FB-4B0928BD8704} = {D12DC33D-BB83-40E4-9F36-18F59351A21D} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {F47D8FDB-3919-45C8-8C08-486405ECEC01} diff --git a/src/Fusion.Summary.Functions/Functions/DepartmentResourceOwnerSync.cs b/src/Fusion.Summary.Functions/Functions/DepartmentResourceOwnerSync.cs index 35b26e1dc..7f3deff67 100644 --- a/src/Fusion.Summary.Functions/Functions/DepartmentResourceOwnerSync.cs +++ b/src/Fusion.Summary.Functions/Functions/DepartmentResourceOwnerSync.cs @@ -52,7 +52,7 @@ public DepartmentResourceOwnerSync( { _totalBatchTime = TimeSpan.FromHours(4.5); - logger.LogWarning("Configuration variable 'total_batch_time_in_minutes' not found, batching messages over {BatchTime}", _totalBatchTime); + logger.LogInformation("Configuration variable 'total_batch_time_in_minutes' not found, batching messages over {BatchTime}", _totalBatchTime); } } @@ -72,13 +72,17 @@ public async Task RunAsync( var client = new ServiceBusClient(_serviceBusConnectionString); var sender = client.CreateSender(_weeklySummaryQueueName); + logger.LogInformation("weekly-department-recipients-sync fetching departments with department filter {DepartmentFilter}", JsonConvert.SerializeObject(_departmentFilter, Formatting.Indented)); + // Fetch all departments var departments = (await lineOrgApiClient.GetOrgUnitDepartmentsAsync()) .DistinctBy(d => d.SapId) - .Where(d => d.FullDepartment != null && d.SapId != null) - .Where(d => _departmentFilter.Any(df => d.FullDepartment.Contains(df))) - .Where(d => d.Management.Persons.Length > 0); + .Where(d => d.FullDepartment != null && d.SapId != null); + + if (_departmentFilter.Length != 0) + departments = departments.Where(d => _departmentFilter.Any(df => d.FullDepartment!.Contains(df))); + logger.LogInformation("Found departments {Departments}", JsonConvert.SerializeObject(departments, Formatting.Indented)); var apiDepartments = new List(); @@ -95,6 +99,13 @@ public async Task RunAsync( .Distinct() .ToArray(); + var recipients = resourceOwners.Concat(delegatedResponsibles).ToArray(); + if (recipients.Length == 0) + { + logger.LogInformation("Skipping department {Department} as it has no resource owners or delegated responsibles", orgUnit.FullDepartment); + continue; + } + apiDepartments.Add(new ApiResourceOwnerDepartment() { DepartmentSapId = orgUnit.SapId!, @@ -113,12 +124,13 @@ public async Task RunAsync( { try { + //TODO: Do one batch update instead of individual updates // Update the database await summaryApiClient.PutDepartmentAsync(department, cancellationToken); } catch (Exception e) { - logger.LogError(e, "Failed to PUT department {Department}", JsonConvert.SerializeObject(department, Formatting.Indented)); + logger.LogCritical(e, "Failed to PUT department {Department}", JsonConvert.SerializeObject(department, Formatting.Indented)); continue; } @@ -129,9 +141,11 @@ public async Task RunAsync( } catch (Exception e) { - logger.LogError(e, "Failed to send department to queue {Department}", JsonConvert.SerializeObject(department, Formatting.Indented)); + logger.LogCritical(e, "Failed to send department to queue {Department}", JsonConvert.SerializeObject(department, Formatting.Indented)); } } + + logger.LogInformation("weekly-department-recipients-sync completed"); } @@ -156,6 +170,8 @@ private Dictionary CalculateDepartme var currentTime = DateTimeOffset.UtcNow; var minutesPerReportSlice = _totalBatchTime.TotalMinutes / apiDepartments.Count; + logger.LogInformation("Minutes allocated for each worker: {MinutesPerReportSlice}", minutesPerReportSlice); + var departmentDelayMapping = new Dictionary(); foreach (var department in apiDepartments) { diff --git a/src/Fusion.Summary.Functions/Functions/WeeklyDepartmentSummarySender.cs b/src/Fusion.Summary.Functions/Functions/WeeklyDepartmentSummarySender.cs index b97d76f9e..7e78090fb 100644 --- a/src/Fusion.Summary.Functions/Functions/WeeklyDepartmentSummarySender.cs +++ b/src/Fusion.Summary.Functions/Functions/WeeklyDepartmentSummarySender.cs @@ -22,6 +22,7 @@ public class WeeklyDepartmentSummarySender private readonly IConfiguration configuration; private int _maxDegreeOfParallelism; + private readonly string[] _departmentFilter; public WeeklyDepartmentSummarySender(ISummaryApiClient summaryApiClient, INotificationApiClient notificationApiClient, ILogger logger, IConfiguration configuration) @@ -32,14 +33,21 @@ public WeeklyDepartmentSummarySender(ISummaryApiClient summaryApiClient, INotifi this.configuration = configuration; _maxDegreeOfParallelism = int.TryParse(configuration["weekly-department-summary-sender-parallelism"], out var result) ? result : 2; + _departmentFilter = configuration["departmentFilter"]?.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries) ?? ["PRD"]; } [FunctionName("weekly-department-summary-sender")] public async Task RunAsync([TimerTrigger("0 0 5 * * MON", RunOnStartup = false)] TimerInfo timerInfo) { + logger.LogInformation("weekly-department-summary-sender started with department filter {DepartmentFilter}", JsonConvert.SerializeObject(_departmentFilter, Formatting.Indented)); + + // TODO: Use OData query to filter departments var departments = await summaryApiClient.GetDepartmentsAsync(); - if (departments is null || !departments.Any()) + if (_departmentFilter.Length != 0) + departments = departments?.Where(d => _departmentFilter.Any(df => d.FullDepartmentName!.Contains(df))).ToArray(); + + if (departments is null || departments.Count == 0) { logger.LogCritical("No departments found. Exiting"); return; @@ -52,6 +60,8 @@ public async Task RunAsync([TimerTrigger("0 0 5 * * MON", RunOnStartup = false)] // Use Parallel.ForEachAsync to easily limit the number of parallel requests await Parallel.ForEachAsync(departments, options, async (department, _) => await CreateAndSendNotificationsAsync(department)); + + logger.LogInformation("weekly-department-summary-sender completed"); } private async Task CreateAndSendNotificationsAsync(ApiResourceOwnerDepartment department) @@ -64,7 +74,7 @@ private async Task CreateAndSendNotificationsAsync(ApiResourceOwnerDepartment de if (summaryReport is null) { - logger.LogCritical( + logger.LogWarning( "No summary report found for department {Department}. Unable to send report notification", JsonConvert.SerializeObject(department, Formatting.Indented)); return; @@ -72,7 +82,7 @@ private async Task CreateAndSendNotificationsAsync(ApiResourceOwnerDepartment de } catch (Exception e) { - logger.LogError(e, "Failed to get summary report for department {Department}", JsonConvert.SerializeObject(department, Formatting.Indented)); + logger.LogCritical(e, "Failed to get summary report for department {Department}", JsonConvert.SerializeObject(department, Formatting.Indented)); return; } @@ -83,7 +93,7 @@ private async Task CreateAndSendNotificationsAsync(ApiResourceOwnerDepartment de } catch (Exception e) { - logger.LogError(e, "Failed to create notification for department {DepartmentSapId} | Report {Report}", department.DepartmentSapId, JsonConvert.SerializeObject(summaryReport, Formatting.Indented)); + logger.LogCritical(e, "Failed to create notification for department {DepartmentSapId} | Report {Report}", department.DepartmentSapId, JsonConvert.SerializeObject(summaryReport, Formatting.Indented)); return; } @@ -95,11 +105,11 @@ private async Task CreateAndSendNotificationsAsync(ApiResourceOwnerDepartment de { var result = await notificationApiClient.SendNotification(notification, azureId); if (!result) - logger.LogError("Failed to send notification to user with AzureId {AzureId} | Report {Report}", azureId, JsonConvert.SerializeObject(summaryReport, Formatting.Indented)); + logger.LogCritical("Failed to send notification to user with AzureId {AzureId} | Report {Report}", azureId, JsonConvert.SerializeObject(summaryReport, Formatting.Indented)); } catch (Exception e) { - logger.LogError(e, "Failed to send notification to user with AzureId {AzureId} | Report {Report}", azureId, JsonConvert.SerializeObject(summaryReport, Formatting.Indented)); + logger.LogCritical(e, "Failed to send notification to user with AzureId {AzureId} | Report {Report}", azureId, JsonConvert.SerializeObject(summaryReport, Formatting.Indented)); } } } diff --git a/src/Fusion.Summary.Functions/Functions/WeeklyDepartmentSummaryWorker.cs b/src/Fusion.Summary.Functions/Functions/WeeklyDepartmentSummaryWorker.cs index fcc322d92..b9b118d28 100644 --- a/src/Fusion.Summary.Functions/Functions/WeeklyDepartmentSummaryWorker.cs +++ b/src/Fusion.Summary.Functions/Functions/WeeklyDepartmentSummaryWorker.cs @@ -32,6 +32,7 @@ public async Task RunAsync( [ServiceBusTrigger("%department_summary_weekly_queue%", Connection = "AzureWebJobsServiceBus")] ServiceBusReceivedMessage message, ServiceBusMessageActions messageReceiver) { + _logger.LogInformation("weekly-department-summary-worker started with message: {MessageBody}", message.Body); try { var dto = await JsonSerializer.DeserializeAsync(message.Body.ToStream()); @@ -50,6 +51,7 @@ public async Task RunAsync( { // Complete the message regardless of outcome. await messageReceiver.CompleteMessageAsync(message); + _logger.LogInformation("weekly-department-summary-worker completed"); } } @@ -66,7 +68,7 @@ private async Task CreateAndStoreReportAsync(ApiResourceOwnerDepartment message) // Check if the department has personnel, abort if not if (departmentPersonnel.Count == 0) { - _logger.LogInformation("Department contains no personnel, no need to store report"); + _logger.LogInformation("Department {Department} contains no valid personnel, no need to store report", message.FullDepartmentName); return; } diff --git a/src/tests/Fusion.Summary.Functions.Tests/Fusion.Summary.Functions.Tests.csproj b/src/tests/Fusion.Summary.Functions.Tests/Fusion.Summary.Functions.Tests.csproj new file mode 100644 index 000000000..cffc36c25 --- /dev/null +++ b/src/tests/Fusion.Summary.Functions.Tests/Fusion.Summary.Functions.Tests.csproj @@ -0,0 +1,49 @@ + + + + net8.0 + enable + enable + + false + true + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + + + + PreserveNewest + true + PreserveNewest + + + + diff --git a/src/tests/Fusion.Summary.Functions.Tests/Notifications/Mock/NotificationReportApiResponseMock.cs b/src/tests/Fusion.Summary.Functions.Tests/Notifications/Mock/NotificationReportApiResponseMock.cs new file mode 100644 index 000000000..75afae2b0 --- /dev/null +++ b/src/tests/Fusion.Summary.Functions.Tests/Notifications/Mock/NotificationReportApiResponseMock.cs @@ -0,0 +1,110 @@ +using Fusion.Resources.Functions.Common.ApiClients; + +namespace Fusion.Summary.Functions.Tests.Notifications.Mock; + +public abstract class NotificationReportApiResponseMock +{ + public static List GetMockedInternalPersonnel( + double personnelCount, + double workload, + double otherTasks, + double vacationLeave, + double absenceLeave) + { + var personnel = new List(); + for (var i = 0; i < personnelCount; i++) + { + personnel.Add(new IResourcesApiClient.InternalPersonnelPerson() + { + EmploymentStatuses = new List + { + new() + { + Type = IResourcesApiClient.ApiAbsenceType.Vacation, + AppliesFrom = DateTime.UtcNow.AddDays(-1 - i), + AppliesTo = DateTime.UtcNow.AddDays(1 + i * 10), + AbsencePercentage = vacationLeave + }, + new() + { + Type = IResourcesApiClient.ApiAbsenceType.OtherTasks, + AppliesFrom = DateTime.UtcNow.AddDays(-1 - i), + AppliesTo = DateTime.UtcNow.AddDays(1 + i * 10), + AbsencePercentage = otherTasks + }, + new() + { + Type = IResourcesApiClient.ApiAbsenceType.Absence, + AppliesFrom = DateTime.UtcNow.AddDays(-1 - i), + AppliesTo = DateTime.UtcNow.AddDays(1 + i * 10), + AbsencePercentage = absenceLeave + } + }, + PositionInstances = new List + { + new() + { + AppliesFrom = DateTime.UtcNow.AddDays(-1 - i), + AppliesTo = DateTime.UtcNow.AddDays(1 + i * 10), + Workload = workload + } + } + } + ); + ; + } + + return personnel; + } + + public static List GetMockedInternalPersonnelWithInstancesWithAndWithoutChanges(double personnelCount) + { + var personnel = new List(); + for (var i = 0; i < personnelCount; i++) + { + personnel.Add(new IResourcesApiClient.InternalPersonnelPerson() + { + // Should return 4 instances for each person + PositionInstances = new List + { + new() + { + // One active instance without any changes + AppliesFrom = DateTime.UtcNow.AddDays(-1 - i), + AppliesTo = DateTime.UtcNow.AddDays(1 + i * 10), + AllocationState = null, + AllocationUpdated = null + }, + new() + { + // One active instance that contains changes done within the last week + AppliesFrom = DateTime.UtcNow.AddDays(-1 - i), + AppliesTo = DateTime.UtcNow.AddDays(1 + i * 10), + AllocationState = "ChangeByTaskOwner", + AllocationUpdated = DateTime.UtcNow + }, + new() + { + // One active instance that contains changes done more than a week ago + AppliesFrom = DateTime.UtcNow.AddDays(-1 - i), + AppliesTo = DateTime.UtcNow.AddDays(1 + i * 10), + AllocationState = "ChangeByTaskOwner", + AllocationUpdated = DateTime.UtcNow.AddDays(-8) + }, + new() + { + // One instance that will become active in more than 3 months that contains changes + AppliesFrom = DateTime.UtcNow.AddMonths(4), + AppliesTo = DateTime.UtcNow.AddMonths(4 + i), + AllocationState = "ChangeByTaskOwner", + AllocationUpdated = DateTime.UtcNow + } + } + } + ); + ; + } + + return personnel; + } +} \ No newline at end of file diff --git a/src/tests/Fusion.Summary.Functions.Tests/Notifications/ScheduledReportNotificationBuilderTests.cs b/src/tests/Fusion.Summary.Functions.Tests/Notifications/ScheduledReportNotificationBuilderTests.cs new file mode 100644 index 000000000..c4ea51a1c --- /dev/null +++ b/src/tests/Fusion.Summary.Functions.Tests/Notifications/ScheduledReportNotificationBuilderTests.cs @@ -0,0 +1,246 @@ +using FluentAssertions; +using Fusion.ApiClients.Org; +using Fusion.Resources.Functions.Common.ApiClients; +using Fusion.Summary.Functions.ReportCreator; +using Fusion.Summary.Functions.Tests.Notifications.Mock; + +namespace Fusion.Summary.Functions.Tests.Notifications; + +public class ScheduledReportNotificationBuilderTests +{ + [Fact] + public void GetChangesForDepartment_Should_ResultInNumberOfChanges() + { + // Arrange + const int personnelCount = 4; + // Returns 4 different instances pr personnel + var personnel = NotificationReportApiResponseMock.GetMockedInternalPersonnelWithInstancesWithAndWithoutChanges( + personnelCount); + + // Act + var changes = ResourceOwnerReportDataCreator.CalculateDepartmentChangesLastWeek(personnel); + + // Assert + changes.Should().Be(8); + } + + [Fact] + public void GetCapacityInUse_ShouldReturnCorrectCapacityInUse() + { + // Arrange + const int personnelCount = 4; + const int workload = 80; + const int otherTasks = 4; + const int vacationLeave = 2; + const int absenceLeave = 3; + var personnel = NotificationReportApiResponseMock.GetMockedInternalPersonnel( + personnelCount, + workload, + otherTasks, + vacationLeave, + absenceLeave); + + // Act + var capacityInUse = ResourceOwnerReportDataCreator.GetCapacityInUse(personnel); + var capacityInUseCalculated = (int)Math.Round((double)(workload + otherTasks) / + (100 - (vacationLeave + absenceLeave)) * 100); + + // Assert + capacityInUse.Should().Be(capacityInUseCalculated); + } + + [Fact] + public void GetNumberOfRequestsLastWeek_ShouldReturnCorrectNumberOfRequests() + { + // Arrange + var requests = new List + { + // Will pass + new() + { + Type = RequestType.Allocation.ToString(), + IsDraft = false, + Created = DateTimeOffset.UtcNow.AddDays(-1) + }, + new() + { + Type = RequestType.Allocation.ToString(), + IsDraft = false, + Created = DateTimeOffset.UtcNow.AddDays(-8) + }, + new() + { + Type = RequestType.Allocation.ToString(), + IsDraft = true, + Created = DateTimeOffset.UtcNow.AddDays(-1) + }, + new() + { + Type = RequestType.ResourceOwnerChange.ToString(), + IsDraft = false, + Created = DateTimeOffset.UtcNow.AddDays(-1) + } + }; + + // Act + var numberOfRequests = ResourceOwnerReportDataCreator.GetNumberOfRequestsLastWeek(requests); + + // Assert + numberOfRequests.Should().Be(1); + } + + [Fact] + public void GetNumberOfOpenRequests_ShouldReturnCorrectNumberOfOpenRequests() + { + // Arrange + var requests = new List + { + // Will pass + new() + { + Type = RequestType.Allocation.ToString(), + State = RequestState.Created.ToString() + }, + new() + { + Type = RequestType.ResourceOwnerChange.ToString(), + State = RequestState.Created.ToString(), + ProposedPerson = new IResourcesApiClient.ProposedPerson() + { Person = new IResourcesApiClient.InternalPersonnelPerson() { AzureUniquePersonId = new Guid() } } + }, + new() + { + Type = RequestType.ResourceOwnerChange.ToString(), + State = RequestState.Created.ToString() + }, + new() + { + Type = RequestType.Allocation.ToString(), + State = RequestState.Completed.ToString() + } + }; + + + // Act + var numberOfOpenRequests = ResourceOwnerReportDataCreator.GetNumberOfOpenRequests(requests); + + // Assert + numberOfOpenRequests.Should().Be(1); + } + + [Fact] + public void GetNumberOfRequestsStartingInMoreThanThreeMonths_ShouldReturnCorrectNumberOfRequests() + { + // Arrange + var requests = new List + { + // Will pass + new() + { + Type = RequestType.Allocation.ToString(), + State = RequestState.Created.ToString(), + OrgPositionInstance = new ApiPositionInstanceV2 { AppliesFrom = DateTime.UtcNow.AddMonths(4) } + }, + new() + { + Type = RequestType.Allocation.ToString(), + State = RequestState.Created.ToString(), + OrgPositionInstance = new ApiPositionInstanceV2 { AppliesFrom = DateTime.UtcNow.AddMonths(2) } + }, + new() + { + Type = RequestType.ResourceOwnerChange.ToString(), + State = RequestState.Completed.ToString(), + OrgPositionInstance = new ApiPositionInstanceV2 { AppliesFrom = DateTime.UtcNow.AddMonths(4) } + } + }; + + + // Act + var numberOfRequests = + ResourceOwnerReportDataCreator.GetNumberOfRequestsStartingInMoreThanThreeMonths(requests); + + // Assert + numberOfRequests.Should().Be(1); + } + + [Fact] + public void GetTotalNumberOfPersonnel_ShouldReturnCorrectNumberOfPersonnel() + { + // Arrange + var personnel = NotificationReportApiResponseMock.GetMockedInternalPersonnel(5, 100, 0, 0, 0); + + // Act + var totalNumberOfPersonnel = ResourceOwnerReportDataCreator.GetTotalNumberOfPersonnel(personnel); + + // Assert + totalNumberOfPersonnel.Should().Be(5); + } + + [Fact] + public void GetNumberOfRequestsStartingInLessThanThreeMonths_ShouldReturnCorrectNumberOfRequests() + { + // Arrange + var requests = new List + { + // Will pass + new() + { + Type = RequestType.Allocation.ToString(), + State = RequestState.Created.ToString(), + OrgPositionInstance = new ApiPositionInstanceV2 { AppliesFrom = DateTime.UtcNow.AddMonths(2) } + }, + new() + { + Type = RequestType.Allocation.ToString(), + State = RequestState.Created.ToString(), + OrgPositionInstance = new ApiPositionInstanceV2 { AppliesFrom = DateTime.UtcNow.AddMonths(4) } + }, + new() + { + Type = RequestType.ResourceOwnerChange.ToString(), + State = RequestState.Completed.ToString(), + OrgPositionInstance = new ApiPositionInstanceV2 { AppliesFrom = DateTime.UtcNow.AddMonths(2) } + } + }; + + // Act + var numberOfRequests = + ResourceOwnerReportDataCreator.GetNumberOfRequestsStartingInLessThanThreeMonths(requests); + + // Assert + numberOfRequests.Should().Be(1); + } + + [Fact] + public void GetAllocationChangesAwaitingTaskOwnerAction_ShouldReturnCorrectNumberOfRequests() + { + // Arrange + var requests = new List + { + // Will pass + new() + { + Type = RequestType.ResourceOwnerChange.ToString(), + State = RequestState.Created.ToString() + }, + new() + { + Type = RequestType.Allocation.ToString(), + State = RequestState.Completed.ToString() + }, + new() + { + Type = RequestType.Allocation.ToString(), + State = RequestState.Created.ToString() + } + }; + + // Act + var numberOfRequests = + ResourceOwnerReportDataCreator.GetAllocationChangesAwaitingTaskOwnerAction(requests); + + // Assert + numberOfRequests.Should().Be(1); + } +} \ No newline at end of file diff --git a/src/tests/Fusion.Summary.Functions.Tests/nuget.config b/src/tests/Fusion.Summary.Functions.Tests/nuget.config new file mode 100644 index 000000000..84f4b4aa5 --- /dev/null +++ b/src/tests/Fusion.Summary.Functions.Tests/nuget.config @@ -0,0 +1,8 @@ + + + + + + + \ No newline at end of file diff --git a/src/tests/Fusion.Summary.Functions.Tests/xunit.runner.json b/src/tests/Fusion.Summary.Functions.Tests/xunit.runner.json new file mode 100644 index 000000000..f78bc2f0c --- /dev/null +++ b/src/tests/Fusion.Summary.Functions.Tests/xunit.runner.json @@ -0,0 +1,3 @@ +{ + "$schema": "https://xunit.net/schema/current/xunit.runner.schema.json" +} \ No newline at end of file