Skip to content
Snippets Groups Projects
Select Git revision
  • 79efb169a8d1e431bb78a76f2ad528905f614515
  • main default protected
  • dev protected
  • Issue/3142-kpiGenerator
  • Hotfix/3115-userReportingEmpty2
  • Hotfix/3115-userReportingEmpty
  • Issue/3073-kpi
  • Issue/2492-respOrg
  • Issue/3005-kpiReportingBroken
  • Issue/2982-kpiDataPub
  • gitkeep
  • Issue/2847-reporting
  • Issue/2850-removeGrantId
  • Issue/2432-publicationKpi
  • Hotfix/xxxx-rors
  • Issue/2666-adminCronjobs-theSequal
  • Issue/2666-adminCronjobs
  • Issue/2568-betterLogging
  • Issue/2518-docs
  • Hotfix/2388-sensitive
  • Issue/2330-fixNaNQuotainAdmin
  • v1.2.10
  • v1.2.9
  • v1.2.8
  • v1.2.7
  • v1.2.6
  • v1.2.5
  • v1.2.4
  • v1.2.3
  • v1.2.2
  • v1.2.1
  • v1.2.0
  • v1.1.1
  • v1.1.0
  • v1.0.9
  • v1.0.8
  • v1.0.7
  • v1.0.6
  • v1.0.5
  • v1.0.4
  • v1.0.3
41 results

ProjectReportingTests.cs

  • Code owners
    Assign users and groups as approvers for specific file changes. Learn more.
    ProjectReportingTests.cs 11.78 KiB
    using AutoMapper;
    using Coscine.ApiClient.Core.Api;
    using Coscine.ApiClient.Core.Model;
    using Coscine.KpiGenerator.MappingProfiles;
    using Coscine.KpiGenerator.Models;
    using Coscine.KpiGenerator.Models.ConfigurationModels;
    using Coscine.KpiGenerator.Reportings.Project;
    using Coscine.KpiGenerator.Utils;
    using Microsoft.Extensions.Logging;
    using Microsoft.Extensions.Options;
    using NSubstitute;
    using NSubstitute.Extensions;
    using static Coscine.KpiGenerator.Models.ConfigurationModels.ReportingConfiguration;
    using static KPIGenerator.Utils.CommandLineOptions;
    
    namespace KpiGenerator.Tests;
    
    [TestFixture]
    public class ProjectReportingTests
    {
        private IMapper _mapper = null!;
        private ILogger<ProjectReporting> _logger = null!;
        private IStorageService _gitlabStorageService = null!;
        private IStorageService _localStorageService = null!;
        private IOptionsMonitor<KpiConfiguration> _kpiConfiguration = null!;
        private IOptionsMonitor<ReportingConfiguration> _reportingConfiguration = null!;
    
        private IAdminApi _adminApi = null!;
    
        private ProjectReporting _projectReporting = null!; // System Under Test
    
        [SetUp]
        public void SetUp()
        {
            // NSubstitute Mocks
            _logger = Substitute.For<ILogger<ProjectReporting>>();
            _gitlabStorageService = Substitute.For<IStorageService>();
            _localStorageService = Substitute.For<IStorageService>();
    
            // Mock IOptionsMonitor
            var kpiConfig = new KpiConfiguration
            {
                ProjectKpi = new("project_reporting.json")
            };
            _kpiConfiguration = Substitute.For<IOptionsMonitor<KpiConfiguration>>();
            _kpiConfiguration.CurrentValue.Returns(kpiConfig);
    
            // Create a real mapper
            _mapper = new MapperConfiguration(cfg =>
            {
                cfg.AddProfile<MappingProfiles>();
            }).CreateMapper();
    
            // Mock ReportingConfiguration
            var reportingConfig = new ReportingConfiguration
            {
                Endpoint = "https://some-endpoint/api",
                ApiKey = "dummy-api-key",
                // Possibly fill Organization as needed
                Organization = new OrganizationConfiguration
                {
                    OtherOrganization = new()
                    {
                        Name = "Other",
                        RorUrl = "https://ror.org/_other",
                    }
                }
            };
            _reportingConfiguration = Substitute.For<IOptionsMonitor<ReportingConfiguration>>();
            _reportingConfiguration.CurrentValue.Returns(reportingConfig);
    
            _adminApi = Substitute.For<IAdminApi>();
        }
    
        #region GenerateReportingAsync Tests
    
        [Test]
        public async Task GenerateReportingAsync_ReturnsGeneralAndPerOrgFiles_WhenProjectsExist()
        {
            // Arrange
            var projects = TestData.ProjectAdminDtos;
    
            _adminApi
                .GetAllProjectsAsync(
                    includeDeleted: Arg.Any<bool>(),
                    includeQuotas: Arg.Any<bool>(),
                    includePublicationRequests: Arg.Any<bool>(),
                    pageNumber: Arg.Any<int>(),
                    pageSize: Arg.Any<int>()
                )
                .Returns(ci =>
                {
                    // Return the test projects data, single page
                    var pagination = new Pagination(currentPage: 1, pageSize: 2, totalCount: 2, totalPages: 1);
                    return Task.FromResult(new ProjectAdminDtoPagedResponse(data: projects, pagination: pagination, statusCode: 200, traceId: "dummy-trace-id"));
                });
            _projectReporting = new ProjectReporting(_mapper, _logger, _gitlabStorageService, _localStorageService, _kpiConfiguration, _reportingConfiguration, _adminApi);
    
            // Act
            var result = await _projectReporting.GenerateReportingAsync();
    
            // Assert
            Assert.That(result, Is.Not.Null);
            Assert.That(result.Count(), Is.EqualTo(3), "Expected 3 reporting files.");
    
            var generalFile = result.FirstOrDefault(r => r.Path.Contains("general", StringComparison.OrdinalIgnoreCase)
                                                         || r.Path.EndsWith("project_reporting.json"));
            Assert.That(generalFile, Is.Not.Null, "General file should exist.");
    
            var orgFile1 = result.FirstOrDefault(r => r.Path.Contains("12345"));
            Assert.That(orgFile1, Is.Not.Null, "Per-organization file for ror.org/12345 should exist.");
    
            var orgFile2 = result.FirstOrDefault(r => r.Path.Contains("54321"));
            Assert.That(orgFile2, Is.Not.Null, "Per-organization file for ror.org/54321 should exist.");
        }
    
        [Test]
        public async Task GenerateReportingAsync_ReturnsOnlyGeneralFile_WhenNoProjects()
        {
            // Arrange
            _adminApi
                .GetAllProjectsAsync()
                .Returns(ci =>
                {
                    // No projects, empty data
                    var pagination = new Pagination(currentPage: 1, pageSize: 0, totalCount: 0, totalPages: 1);
                    return Task.FromResult(new ProjectAdminDtoPagedResponse(data: [], pagination: pagination, statusCode: 200, traceId: "dummy-trace-id"));
                });
            _projectReporting = new ProjectReporting(_mapper, _logger, _gitlabStorageService, _localStorageService, _kpiConfiguration, _reportingConfiguration, _adminApi);
    
            // Act
            var result = await _projectReporting.GenerateReportingAsync();
    
            // Assert
            //   We expect 1 file: the "general" file (with an empty list of projects).
            Assert.That(result.Count(), Is.EqualTo(1));
            var file = result.First();
            Assert.That(file.Path, Does.Contain("project_reporting.json").Or.Contain("general"));
        }
    
        #endregion
    
        #region RunAsync Tests
    
        [Test]
        public async Task RunAsync_WhenGitlabPublishSucceeds_ShouldReturnTrue()
        {
            // Arrange
            var options = new ProjectReportingOptions { DummyMode = false };
            var reportingFiles = new List<ReportingFileObject>
                {
                    new() { Path = "foo", Content = new MemoryStream() }
                };
    
            // We want to ensure that GenerateReportingAsync returns some test objects
            _projectReporting = Substitute.ForPartsOf<ProjectReporting>(_mapper, _logger, _gitlabStorageService, _localStorageService, _kpiConfiguration, _reportingConfiguration, _adminApi);
            _projectReporting
                .Configure()
                .GenerateReportingAsync()
                .Returns(Task.FromResult((IEnumerable<ReportingFileObject>)reportingFiles));
    
            // GitLab publish => success
            _gitlabStorageService.PublishAsync(Arg.Any<string>(), Arg.Any<IEnumerable<ReportingFileObject>>())
                .Returns(Task.FromResult(true));
    
            // Local storage shouldn't be called if GitLab is successful
            _localStorageService.PublishAsync(Arg.Any<string>(), Arg.Any<IEnumerable<ReportingFileObject>>())
                .Returns(Task.FromResult(true)); // default
    
            // Act
            var result = await _projectReporting.RunAsync(options);
    
            // Assert
            Assert.That(result, Is.True, "Expected RunAsync to return true if GitLab publish succeeds.");
    
            // Verify GitLab was called
            await _gitlabStorageService.Received(1)
                .PublishAsync("Project Reporting", reportingFiles);
    
            // Verify Local storage was never called
            await _localStorageService.DidNotReceiveWithAnyArgs()
                .PublishAsync(Arg.Any<string>(), Arg.Any<IEnumerable<ReportingFileObject>>());
        }
    
        [Test]
        public async Task RunAsync_WhenGitlabPublishFails_ShouldFallbackToLocalStorage()
        {
            // Arrange
            var options = new ProjectReportingOptions { DummyMode = false };
            var reportingFiles = new List<ReportingFileObject>
                {
                    new() { Path = "bar", Content = new MemoryStream() }
                };
    
            // Partial mock to override GenerateReportingAsync
            _projectReporting = Substitute.ForPartsOf<ProjectReporting>(_mapper, _logger, _gitlabStorageService, _localStorageService, _kpiConfiguration, _reportingConfiguration, _adminApi);
            _projectReporting
                .Configure()
                .GenerateReportingAsync()
                .Returns(Task.FromResult((IEnumerable<ReportingFileObject>)reportingFiles));
    
            // GitLab publish => fails
            _gitlabStorageService.PublishAsync(Arg.Any<string>(), Arg.Any<IEnumerable<ReportingFileObject>>())
                .Returns(Task.FromResult(false));
    
            // Local publish => success
            _localStorageService.PublishAsync(Arg.Any<string>(), Arg.Any<IEnumerable<ReportingFileObject>>())
                .Returns(Task.FromResult(true));
    
            // Act
            var result = await _projectReporting.RunAsync(options);
    
            // Assert
            Assert.That(result, Is.True, "Expected RunAsync to return true if local storage publish succeeds after GitLab fails.");
    
            // Verify GitLab was called
            await _gitlabStorageService.Received(1)
                .PublishAsync("Project Reporting", reportingFiles);
    
            // Verify fallback to local was called
            await _localStorageService.Received(1)
                .PublishAsync("Project Reporting", reportingFiles);
        }
    
        [Test]
        public async Task RunAsync_WhenInDummyMode_ShouldSkipGitlabAndPublishToLocal()
        {
            // Arrange
            var options = new ProjectReportingOptions { DummyMode = true };
            var reportingFiles = new List<ReportingFileObject>
                {
                    new() { Path = "dummy", Content = new MemoryStream() }
                };
    
            // Partial mock to override GenerateReportingAsync
            _projectReporting = Substitute.ForPartsOf<ProjectReporting>(_mapper, _logger, _gitlabStorageService, _localStorageService, _kpiConfiguration, _reportingConfiguration, _adminApi);
            _projectReporting
                .Configure()
                .GenerateReportingAsync()
                .Returns(Task.FromResult((IEnumerable<ReportingFileObject>)reportingFiles));
    
            // GitLab publish => should not be called
            // Local publish => success
            _localStorageService.PublishAsync(Arg.Any<string>(), Arg.Any<IEnumerable<ReportingFileObject>>())
                .Returns(Task.FromResult(true));
    
            // Act
            var result = await _projectReporting.RunAsync(options);
    
            // Assert
            Assert.That(result, Is.True, "Expected RunAsync to return true if local storage publish succeeds in DummyMode.");
    
            // Verify GitLab wasn't called
            await _gitlabStorageService.DidNotReceiveWithAnyArgs()
                .PublishAsync(Arg.Any<string>(), Arg.Any<IEnumerable<ReportingFileObject>>());
    
            // Verify local storage was called
            await _localStorageService.Received(1)
                .PublishAsync("Project Reporting", reportingFiles);
        }
    
        [Test]
        public async Task RunAsync_WhenBothGitlabAndLocalFail_ShouldReturnFalse()
        {
            // Arrange
            var options = new ProjectReportingOptions { DummyMode = false };
            var reportingFiles = new List<ReportingFileObject>
                {
                    new() { Path = "fail", Content = new MemoryStream() }
                };
    
            // Partial mock
            _projectReporting = Substitute.ForPartsOf<ProjectReporting>(_mapper, _logger, _gitlabStorageService, _localStorageService, _kpiConfiguration, _reportingConfiguration, _adminApi);
            _projectReporting
                .Configure()
                .GenerateReportingAsync()
                .Returns(Task.FromResult((IEnumerable<ReportingFileObject>)reportingFiles));
    
            _gitlabStorageService.PublishAsync(Arg.Any<string>(), Arg.Any<IEnumerable<ReportingFileObject>>())
                .Returns(Task.FromResult(false)); // GitLab fails
            _localStorageService.PublishAsync(Arg.Any<string>(), Arg.Any<IEnumerable<ReportingFileObject>>())
                .Returns(Task.FromResult(false)); // Local also fails
    
            // Act
            var result = await _projectReporting.RunAsync(options);
    
            // Assert
            Assert.That(result, Is.False, "Expected RunAsync to return false if both GitLab and local publish fail.");
        }
    
        #endregion
    }