public async Task SaveAuditResultSavesKubeMetadata()
        {
            // Arrange
            await using var context = JosekiTestsDb.CreateUniqueContext();
            var parser = new ConfigurationParser("config.sample.yaml");
            var db     = new MssqlJosekiDatabase(context, parser);
            var audit  = new Audit
            {
                MetadataKube = new MetadataKube
                {
                    Date = DateTime.UtcNow,
                    JSON = Guid.NewGuid().ToString(),
                },
            };

            // Act & Assert
            context.MetadataKube.Should().HaveCount(0);
            await db.SaveAuditResult(audit);

            context.MetadataKube.Should().HaveCount(1);

            var actual = await context.MetadataKube.FirstAsync();

            actual.JSON.Should().Be(audit.MetadataKube.JSON);
            actual.Date.Should().Be(audit.MetadataKube.Date);
        }
        public async Task GetAuditedComponentsWithHistoryReturnsOnlyOneMonthData()
        {
            // Arrange
            await using var context = JosekiTestsDb.CreateUniqueContext();

            var parser = new ConfigurationParser("config.sample.yaml");
            var db     = new MssqlJosekiDatabase(context, parser);

            var componentId = Guid.NewGuid().ToString();
            var today       = DateTime.UtcNow;

            // create more than 31 entries, which the oldest ones would be filtered-out
            var entities = Enumerable.Range(0, 35)
                           .Select(i => today.AddDays(-i)).Select(i => new AuditEntity
            {
                Date                    = i,
                ComponentId             = componentId,
                InfrastructureComponent = new InfrastructureComponentEntity(),
            });

            context.AddRange(entities);
            await context.SaveChangesAsync();

            // Act & Assert
            var audits = await db.GetAuditedComponentsWithHistory(today);

            audits.Should().HaveCount(31);
            audits.All(i => i.Date >= today.AddDays(-30)).Should().BeTrue("All audits should be not earlier than 30 days ago");
        }
        public async Task GetNotExpiredImageScansReturnsTheLatestScan()
        {
            // Arrange
            await using var context = JosekiTestsDb.CreateUniqueContext();

            var parser = new ConfigurationParser("config.sample.yaml");
            var db     = new MssqlJosekiDatabase(context, parser);

            var tag   = Guid.NewGuid().ToString();
            var scans = new[]
            {
                new ImageScanResultEntity {
                    ImageTag = tag, Date = DateTime.UtcNow.AddHours(-5), Status = joseki.db.entities.ImageScanStatus.Failed
                },
                new ImageScanResultEntity {
                    ImageTag = tag, Date = DateTime.UtcNow, Status = joseki.db.entities.ImageScanStatus.Queued
                },
                new ImageScanResultEntity {
                    ImageTag = tag, Date = DateTime.UtcNow.AddHours(-3), Status = joseki.db.entities.ImageScanStatus.Succeeded
                },
            };

            context.ImageScanResult.AddRange(scans);
            await context.SaveChangesAsync();

            // Act & Assert
            var notExpiredScans = await db.GetNotExpiredImageScans(new[] { tag });

            notExpiredScans.Should().HaveCount(1);
            notExpiredScans.First().Status.Should().Be(ImageScanStatus.Queued);
        }
        public async Task SaveAuditResultSavesCorrectAudit()
        {
            // Arrange
            await using var context = JosekiTestsDb.CreateUniqueContext();
            var parser = new ConfigurationParser("config.sample.yaml");
            var db     = new MssqlJosekiDatabase(context, parser);
            var audit  = new Audit
            {
                ComponentId   = Guid.NewGuid().ToString(),
                ComponentName = Guid.NewGuid().ToString(),
                Date          = DateTime.UtcNow,
                ScannerId     = Guid.NewGuid().ToString(),
                Id            = Guid.NewGuid().ToString(),
            };

            // Act & Assert
            context.Audit.Should().HaveCount(0);
            await db.SaveAuditResult(audit);

            context.Audit.Should().HaveCount(1);

            var actual = await context.Audit.FirstAsync();

            actual.AuditId.Should().Be(audit.Id);
            actual.Date.Should().Be(audit.Date);
            actual.ComponentId.Should().Be(audit.ComponentId);
        }
        public async Task SaveInProgressImageScanSavesCorrectEntity()
        {
            // Arrange
            await using var context = JosekiTestsDb.CreateUniqueContext();
            var parser = new ConfigurationParser("config.sample.yaml");
            var db     = new MssqlJosekiDatabase(context, parser);
            var scan   = new ImageScanResultWithCVEs
            {
                Date        = DateTime.UtcNow,
                Id          = Guid.NewGuid().ToString(),
                Description = Guid.NewGuid().ToString(),
                ImageTag    = Guid.NewGuid().ToString(),
                Status      = ImageScanStatus.Queued,
            };

            // Act & Assert
            context.ImageScanResult.Should().HaveCount(0);
            await db.SaveInProgressImageScan(scan);

            context.ImageScanResult.Should().HaveCount(1);

            var actual = await context.ImageScanResult.FirstAsync();

            actual.Date.Should().Be(scan.Date);
            actual.ExternalId.Should().Be(scan.Id);
            actual.Description.Should().Be(scan.Description);
            actual.ImageTag.Should().Be(scan.ImageTag);
            actual.Status.Should().Be(joseki.db.entities.ImageScanStatus.Queued);
        }
        public async Task SaveImageScanResultCouldSaveNewEntity()
        {
            // Arrange
            await using var context = JosekiTestsDb.CreateUniqueContext();
            var parser = new ConfigurationParser("config.sample.yaml");
            var db     = new MssqlJosekiDatabase(context, parser);
            var cves   = new List <ImageScanToCve>
            {
                new ImageScanToCve {
                    InternalCveId = 1, Target = Guid.NewGuid().ToString(), UsedPackage = Guid.NewGuid().ToString(), UsedPackageVersion = Guid.NewGuid().ToString()
                },
                new ImageScanToCve {
                    InternalCveId = 2, Target = Guid.NewGuid().ToString(), UsedPackage = Guid.NewGuid().ToString(), UsedPackageVersion = Guid.NewGuid().ToString()
                },
                new ImageScanToCve {
                    InternalCveId = 3, Target = Guid.NewGuid().ToString(), UsedPackage = Guid.NewGuid().ToString(), UsedPackageVersion = Guid.NewGuid().ToString()
                },
            };
            var scan = new ImageScanResultWithCVEs
            {
                Date        = DateTime.UtcNow,
                Id          = Guid.NewGuid().ToString(),
                Description = Guid.NewGuid().ToString(),
                ImageTag    = Guid.NewGuid().ToString(),
                Status      = ImageScanStatus.Succeeded,
                FoundCVEs   = cves,
            };

            // Act & Assert
            context.ImageScanResult.Should().HaveCount(0);
            context.ImageScanResultToCve.Should().HaveCount(0);

            await db.SaveImageScanResult(scan);

            context.ImageScanResult.Should().HaveCount(1);
            context.ImageScanResultToCve.Should().HaveCount(cves.Count);

            var actual = await context.ImageScanResult.FirstAsync();

            actual.Date.Should().Be(scan.Date);
            actual.ExternalId.Should().Be(scan.Id);
            actual.Description.Should().Be(scan.Description);
            actual.ImageTag.Should().Be(scan.ImageTag);
            actual.Status.Should().Be(joseki.db.entities.ImageScanStatus.Succeeded);

            foreach (var actualCve in await context.ImageScanResultToCve.ToArrayAsync())
            {
                var expectedCve = cves.First(i => i.InternalCveId == actualCve.CveId);
                actualCve.Target.Should().Be(expectedCve.Target);
                actualCve.UsedPackage.Should().Be(expectedCve.UsedPackage);
                actualCve.UsedPackageVersion.Should().Be(expectedCve.UsedPackageVersion);
            }
        }
        public async Task GetAuditedComponentsWithHistoryReturnsEmptyArrayIfNoAudits()
        {
            // Arrange
            await using var context = JosekiTestsDb.CreateUniqueContext();

            var parser = new ConfigurationParser("config.sample.yaml");
            var db     = new MssqlJosekiDatabase(context, parser);

            // Act & Assert
            var audits = await db.GetAuditedComponentsWithHistory(DateTime.UtcNow);

            audits.Should().BeEmpty();
        }
        public async Task GetNotExpiredImageScansReturnsEmptyArrayIfNoActualScanResult()
        {
            // Arrange
            await using var context = JosekiTestsDb.CreateUniqueContext();

            context.ImageScanResult.Add(new ImageScanResultEntity {
                ImageTag = Guid.NewGuid().ToString(), Date = DateTime.UtcNow.AddHours(-1)
            });
            await context.SaveChangesAsync();

            var parser = new ConfigurationParser("config.sample.yaml");
            var db     = new MssqlJosekiDatabase(context, parser);

            // Act & Assert
            var notExpiredScans = await db.GetNotExpiredImageScans(new[] { Guid.NewGuid().ToString() });

            notExpiredScans.Should().BeEmpty();
        }
        public async Task GetAuditedComponentsWithHistoryReturnsOnlyOneAuditPerDay()
        {
            // Arrange
            await using var context = JosekiTestsDb.CreateUniqueContext();

            var parser = new ConfigurationParser("config.sample.yaml");
            var db     = new MssqlJosekiDatabase(context, parser);

            // Create two audits at each day during the last month: at 3am and 10am
            var componentId = Guid.NewGuid().ToString();
            var today3am    = DateTime.UtcNow.Date.AddHours(3);
            var today10am   = today3am.AddHours(7);
            var entries3am  = Enumerable.Range(0, 31)
                              .Select(i => new AuditEntity
            {
                Date                    = today3am.AddDays(-i),
                ComponentId             = componentId,
                InfrastructureComponent = new InfrastructureComponentEntity(),
            });
            var entries10am = Enumerable.Range(0, 31)
                              .Select(i => new AuditEntity
            {
                Date                    = today10am.AddDays(-i),
                ComponentId             = componentId,
                InfrastructureComponent = new InfrastructureComponentEntity(),
            });

            context.AddRange(entries3am);
            context.AddRange(entries10am);
            context.Add(new InfrastructureComponentEntity {
                ComponentId = componentId
            });
            await context.SaveChangesAsync();

            // Act & Assert
            // DB object should choose only one latest audit at each day (e.g. discard all 3am items, but leave 10am ones)
            var audits = await db.GetAuditedComponentsWithHistory(today10am.AddHours(2));

            audits.Should().HaveCount(31);
            audits.All(i => i.Date.Hour == 10).Should().BeTrue("All audits should be at 10 am");
        }
        public async Task SaveAuditResultSavesCheckResults()
        {
            // Arrange
            await using var context = JosekiTestsDb.CreateUniqueContext();
            var parser = new ConfigurationParser("config.sample.yaml");
            var db     = new MssqlJosekiDatabase(context, parser);
            var audit  = new Audit
            {
                CheckResults = new List <CheckResult>
                {
                    new CheckResult {
                        InternalCheckId = 1, ComponentId = Guid.NewGuid().ToString(), Message = Guid.NewGuid().ToString(), Value = CheckValue.NoData
                    },
                    new CheckResult {
                        InternalCheckId = 2, ComponentId = Guid.NewGuid().ToString(), Message = Guid.NewGuid().ToString(), Value = CheckValue.Failed
                    },
                    new CheckResult {
                        InternalCheckId = 3, ComponentId = Guid.NewGuid().ToString(), Message = Guid.NewGuid().ToString(), Value = CheckValue.InProgress
                    },
                    new CheckResult {
                        InternalCheckId = 4, ComponentId = Guid.NewGuid().ToString(), Message = Guid.NewGuid().ToString(), Value = CheckValue.Succeeded
                    },
                },
            };

            // Act & Assert
            context.CheckResult.Should().HaveCount(0);
            await db.SaveAuditResult(audit);

            context.CheckResult.Should().HaveCount(4);

            var entities = await context.CheckResult.ToArrayAsync();

            foreach (var actual in entities)
            {
                var expected = audit.CheckResults.First(i => i.InternalCheckId == actual.CheckId);
                actual.ComponentId.Should().Be(expected.ComponentId);
                actual.Message.Should().Be(expected.Message);
                actual.Value.Should().Be(expected.Value.ToEntity());
            }
        }
        public async Task SaveImageScanResultUpdatesInProgressCheckResults()
        {
            // Arrange
            await using var context = JosekiTestsDb.CreateUniqueContext();

            var scan = new ImageScanResultWithCVEs
            {
                Date        = DateTime.UtcNow,
                Id          = Guid.NewGuid().ToString(),
                Description = Guid.NewGuid().ToString(),
                ImageTag    = Guid.NewGuid().ToString(),
                Status      = ImageScanStatus.Succeeded,
                FoundCVEs   = new List <ImageScanToCve>
                {
                    new ImageScanToCve {
                        InternalCveId = 1, Target = Guid.NewGuid().ToString(), UsedPackage = Guid.NewGuid().ToString(), UsedPackageVersion = Guid.NewGuid().ToString()
                    },
                    new ImageScanToCve {
                        InternalCveId = 2, Target = Guid.NewGuid().ToString(), UsedPackage = Guid.NewGuid().ToString(), UsedPackageVersion = Guid.NewGuid().ToString()
                    },
                    new ImageScanToCve {
                        InternalCveId = 3, Target = Guid.NewGuid().ToString(), UsedPackage = Guid.NewGuid().ToString(), UsedPackageVersion = Guid.NewGuid().ToString()
                    },
                },
                Counters = new[]
                {
                    new VulnerabilityCounter {
                        Count = 1, Severity = CveSeverity.High
                    },
                    new VulnerabilityCounter {
                        Count = 2, Severity = CveSeverity.Medium
                    },
                },
            };

            // only the first one should be updated
            var checkResults = new[]
            {
                new CheckResultEntity {
                    Id = 100, Value = joseki.db.entities.CheckValue.InProgress, ComponentId = $"/k8s/.../image/{scan.ImageTag}", DateUpdated = DateTime.UtcNow.AddDays(-1)
                },
                new CheckResultEntity {
                    Id = 200, Value = joseki.db.entities.CheckValue.InProgress, ComponentId = $"/k8s/.../image/{Guid.NewGuid()}", DateUpdated = DateTime.UtcNow.AddDays(-1)
                },
                new CheckResultEntity {
                    Id = 300, Value = joseki.db.entities.CheckValue.Succeeded, ComponentId = $"/k8s/.../image/{scan.ImageTag}", DateUpdated = DateTime.UtcNow.AddDays(-1)
                },
            };

            // this is the hack -_-
            // Use sync version, because it does not update DateUpdated & DateCreated
            context.CheckResult.AddRange(checkResults);
            context.SaveChanges();

            var parser = new ConfigurationParser("config.sample.yaml");
            var db     = new MssqlJosekiDatabase(context, parser);

            // Act & Assert
            var saveRequestedAt = DateTime.UtcNow;
            await db.SaveImageScanResult(scan);

            var updatedCheckResults = await context.CheckResult.Where(i => i.DateUpdated >= saveRequestedAt).ToArrayAsync();

            updatedCheckResults.Should().HaveCount(1);
            updatedCheckResults.First().Id.Should().Be(100);
        }
        public async Task SaveImageScanResultCouldUpdateExistingEntity()
        {
            // Arrange
            await using var context = JosekiTestsDb.CreateUniqueContext();

            var queuedScan = new ImageScanResultEntity
            {
                Date        = DateTime.UtcNow.AddMinutes(-10),
                ExternalId  = Guid.NewGuid().ToString(),
                Description = Guid.NewGuid().ToString(),
                ImageTag    = Guid.NewGuid().ToString(),
                Status      = joseki.db.entities.ImageScanStatus.Queued,
            };

            context.Set <ImageScanResultEntity>().Add(queuedScan);
            await context.SaveChangesAsync();

            var scan = new ImageScanResultWithCVEs
            {
                Date        = DateTime.UtcNow,
                Id          = queuedScan.ExternalId,
                Description = Guid.NewGuid().ToString(),
                ImageTag    = queuedScan.ImageTag,
                Status      = ImageScanStatus.Succeeded,
                FoundCVEs   = new List <ImageScanToCve>
                {
                    new ImageScanToCve {
                        InternalCveId = 1, Target = Guid.NewGuid().ToString(), UsedPackage = Guid.NewGuid().ToString(), UsedPackageVersion = Guid.NewGuid().ToString()
                    },
                    new ImageScanToCve {
                        InternalCveId = 2, Target = Guid.NewGuid().ToString(), UsedPackage = Guid.NewGuid().ToString(), UsedPackageVersion = Guid.NewGuid().ToString()
                    },
                    new ImageScanToCve {
                        InternalCveId = 3, Target = Guid.NewGuid().ToString(), UsedPackage = Guid.NewGuid().ToString(), UsedPackageVersion = Guid.NewGuid().ToString()
                    },
                },
            };

            var parser = new ConfigurationParser("config.sample.yaml");
            var db     = new MssqlJosekiDatabase(context, parser);

            // Act & Assert
            context.ImageScanResult.Should().HaveCount(1);
            context.ImageScanResultToCve.Should().HaveCount(0);

            await db.SaveImageScanResult(scan);

            context.ImageScanResult.Should().HaveCount(1);
            context.ImageScanResultToCve.Should().HaveCount(scan.FoundCVEs.Count);

            // Only Date, Description, Status, and CVEs should be updated
            var actual = await context.ImageScanResult.FirstAsync();

            actual.Date.Should().Be(scan.Date);
            actual.Description.Should().Be(scan.Description);
            actual.Status.Should().Be(joseki.db.entities.ImageScanStatus.Succeeded);

            foreach (var actualCve in await context.ImageScanResultToCve.ToArrayAsync())
            {
                var expectedCve = scan.FoundCVEs.First(i => i.InternalCveId == actualCve.CveId);
                actualCve.Target.Should().Be(expectedCve.Target);
                actualCve.UsedPackage.Should().Be(expectedCve.UsedPackage);
                actualCve.UsedPackageVersion.Should().Be(expectedCve.UsedPackageVersion);
            }
        }