Adding
This commit is contained in:
+25
@@ -0,0 +1,25 @@
|
||||
using LCU.API.Internal.DocumentIngestorLib.Extensions;
|
||||
|
||||
namespace LCU.API.Internal.DocumentIngestorLibTests.Extensions;
|
||||
|
||||
public class FilterNullsAndZerosOrDefaultTest
|
||||
{
|
||||
[Test]
|
||||
public void FilterNullsAndZerosOrDefault()
|
||||
{
|
||||
var notNullList = new[] { "123", "345" };
|
||||
var partialList = new[] { string.Empty, "123" };
|
||||
var emptyList = Array.Empty<string>();
|
||||
var listEmptyStrings = new[] { string.Empty, string.Empty };
|
||||
var listZero = new[] { string.Empty, "0" };
|
||||
List<string>? nullValue = null;
|
||||
|
||||
Assert.That(new[] { "123", "345" }, Is.EqualTo(notNullList.FilterNullsAndZerosOrDefault()));
|
||||
Assert.That(new[] { "123" }, Is.EqualTo(partialList.FilterNullsAndZerosOrDefault()));
|
||||
|
||||
Assert.IsNull(emptyList.FilterNullsAndZerosOrDefault());
|
||||
Assert.IsNull(listEmptyStrings.FilterNullsAndZerosOrDefault());
|
||||
Assert.IsNull(nullValue.FilterNullsAndZerosOrDefault());
|
||||
Assert.IsNull(listZero.FilterNullsAndZerosOrDefault());
|
||||
}
|
||||
}
|
||||
+131
@@ -0,0 +1,131 @@
|
||||
using FluentAssertions;
|
||||
using LCU.Api.Internal.DocumentIngestorLib.Extensions.Queryables;
|
||||
using LCU.Library.Internal.DocumentIngestorModels.Entities;
|
||||
using LCU.Library.Internal.DocumentIngestorModels.Enums;
|
||||
using LCU.Library.Internal.DocumentIngestorModels.Models.Pagination;
|
||||
|
||||
namespace LCU.API.Internal.DocumentIngestorLibTests.Extensions.Queryables;
|
||||
|
||||
public class DocumentProcessingInfoNotificationStatusQueryableTest
|
||||
{
|
||||
[Test]
|
||||
public void FilterByNotificationStatusType_NotificationStatusFiltersIsNull_ReturnsUnmodifiedQueryable()
|
||||
{
|
||||
var processingInfo = InfoFactory();
|
||||
var filter = new Filter
|
||||
{
|
||||
NotificationStatusFilters = null
|
||||
};
|
||||
var originalProcessingInfo = processingInfo.ToArray();
|
||||
|
||||
var filtered = processingInfo.FilterByNotificationStatusType(filter);
|
||||
|
||||
filtered.Should().BeEquivalentTo(originalProcessingInfo);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void FilterByNotificationStatusType_NotificationStatusFiltersIsEmpty_ReturnsUnmodifiedQueryable()
|
||||
{
|
||||
var processingInfo = InfoFactory();
|
||||
var filter = new Filter
|
||||
{
|
||||
NotificationStatusFilters = new List<NotificationStatusTypes>()
|
||||
};
|
||||
var originalProcessingInfo = processingInfo.ToArray();
|
||||
|
||||
var filtered = processingInfo.FilterByNotificationStatusType(filter);
|
||||
|
||||
filtered.Should().BeEquivalentTo(originalProcessingInfo);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void FilterByNotificationStatusType_ProcessingInfoMemberNotificationInfoIsNull_IsFilteredOut()
|
||||
{
|
||||
var processingInfo = InfoFactory();
|
||||
var filter = new Filter
|
||||
{
|
||||
NotificationStatusFilters = processingInfo
|
||||
.Where(i => i.MemberNotificationInfos != null)
|
||||
.SelectMany(i => i.MemberNotificationInfos.ToList())
|
||||
.Select(i => i.NotificationStatusType)
|
||||
.Distinct()
|
||||
.ToList()
|
||||
};
|
||||
|
||||
var filtered = processingInfo.FilterByNotificationStatusType(filter);
|
||||
|
||||
filtered.Should().NotContain(i => i.MemberNotificationInfos == null);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void FilterByNotificationStatusType_ProcessingInfoMemberNotificationInfoStatusNotInFilter_IsFilteredOut()
|
||||
{
|
||||
var processingInfo = InfoFactory();
|
||||
var notIncludedStatuses = Enum.GetValues<NotificationStatusTypes>()[..(Enum.GetValues<NotificationStatusTypes>().Length / 2)];
|
||||
var filter = new Filter
|
||||
{
|
||||
NotificationStatusFilters = processingInfo
|
||||
.Where(i => i.MemberNotificationInfos != null)
|
||||
.SelectMany(i => i.MemberNotificationInfos.ToList())
|
||||
.Select(i => i.NotificationStatusType)
|
||||
.Distinct()
|
||||
.Except(notIncludedStatuses)
|
||||
.ToList()
|
||||
};
|
||||
|
||||
var filtered = processingInfo.FilterByNotificationStatusType(filter);
|
||||
|
||||
filtered.Should().NotContain(i => notIncludedStatuses.Contains(i.MemberNotificationInfos.FirstOrDefault().NotificationStatusType));
|
||||
}
|
||||
|
||||
|
||||
[Test]
|
||||
public void FilterByNotificationStatusType_ProcessingInfoMemberNotificationInfoStatusInFilter_IsRetained()
|
||||
{
|
||||
var processingInfo = InfoFactory();
|
||||
var includedStatuses = Enum.GetValues<NotificationStatusTypes>()[..(Enum.GetValues<NotificationStatusTypes>().Length / 2)];
|
||||
var filter = new Filter
|
||||
{
|
||||
NotificationStatusFilters = includedStatuses.Distinct().ToList()
|
||||
};
|
||||
|
||||
var filtered = processingInfo.FilterByNotificationStatusType(filter);
|
||||
|
||||
filtered.Should().OnlyContain(i => includedStatuses.Contains(i.MemberNotificationInfos.FirstOrDefault().NotificationStatusType));
|
||||
}
|
||||
|
||||
private static IQueryable<DocumentProcessingInfo> InfoFactory()
|
||||
=> Enum.GetValues<NotificationStatusTypes>()
|
||||
.Distinct()
|
||||
.SelectMany(status => new[]
|
||||
{
|
||||
new DocumentProcessingInfo
|
||||
{
|
||||
MemberNotificationInfos = new List<MemberNotificationInfo>
|
||||
{
|
||||
new()
|
||||
{
|
||||
NotificationStatusType = status
|
||||
}
|
||||
}
|
||||
},
|
||||
new DocumentProcessingInfo//double each status
|
||||
{
|
||||
MemberNotificationInfos = new List<MemberNotificationInfo>
|
||||
{
|
||||
new()
|
||||
{
|
||||
NotificationStatusType = status
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.Concat(new[]
|
||||
{
|
||||
new DocumentProcessingInfo
|
||||
{
|
||||
MemberNotificationInfos = null
|
||||
}
|
||||
})
|
||||
.AsQueryable();
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
|
||||
<IsPackable>false</IsPackable>
|
||||
|
||||
<Configurations>Debug;Release;Test</Configurations>
|
||||
|
||||
<Platforms>AnyCPU</Platforms>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Docker.DotNet" Version="3.125.12" />
|
||||
<PackageReference Include="FluentAssertions" Version="6.8.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="7.0.2" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" />
|
||||
<PackageReference Include="NUnit" Version="3.13.3" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.2.1" />
|
||||
<PackageReference Include="NUnit.Analyzers" Version="3.3.0" />
|
||||
<PackageReference Include="coverlet.collector" Version="3.1.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Extensions\Queryables\" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\LCU.API.Internal.DocumentIngestorLib\LCU.API.Internal.DocumentIngestorLib.csproj" />
|
||||
<ProjectReference Include="..\LCU.Library.Internal.DocumentIngestorModels\LCU.Library.Internal.DocumentIngestorModels.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
+821
@@ -0,0 +1,821 @@
|
||||
using FluentAssertions;
|
||||
using LCU.API.Internal.DocumentIngestorLib.Persistence;
|
||||
using LCU.API.Internal.DocumentIngestorLib.Repositories;
|
||||
using LCU.API.Internal.DocumentIngestorLibTests;
|
||||
using LCU.Library.Internal.DocumentIngestorModels.Constants;
|
||||
using LCU.Library.Internal.DocumentIngestorModels.Entities;
|
||||
using LCU.Library.Internal.DocumentIngestorModels.Enums;
|
||||
using LCU.Library.Internal.DocumentIngestorModels.Models.Pagination;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System;
|
||||
|
||||
namespace LCU.API.Internal.DocumentIngestorLibTests.Repositories;
|
||||
|
||||
public class DocumentProcessingInfoRepositoryTest : IDisposable
|
||||
{
|
||||
private IDocumentProcessingInfoRepository _repository;
|
||||
|
||||
private readonly SqliteAppDbContextFactory _contextFactory = new();
|
||||
private AppDbContext _context;
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
_context = _contextFactory.CreateContext();
|
||||
_repository = new DocumentProcessingInfoRepository(_context);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task FindAsync_AllCases_ReturnsAllDocumentProcessingInfoRecords()
|
||||
{
|
||||
var records = new[]
|
||||
{
|
||||
DocumentProcessingInfoFactory(),
|
||||
DocumentProcessingInfoFactory(),
|
||||
DocumentProcessingInfoFactory()
|
||||
};
|
||||
await _context.DocumentProcessingInfos.AddRangeAsync(records);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
var infos = await _repository.FindAsync();
|
||||
|
||||
infos.Should().BeEquivalentTo(records);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task FindByIdAsync_RecordExists_ReturnsRecord()
|
||||
{
|
||||
var matchedRecord = DocumentProcessingInfoFactory();
|
||||
var records = new[]
|
||||
{
|
||||
DocumentProcessingInfoFactory(),
|
||||
matchedRecord,
|
||||
DocumentProcessingInfoFactory()
|
||||
};
|
||||
await _context.DocumentProcessingInfos.AddRangeAsync(records);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
var info = await _repository.FindByIdAsync(matchedRecord.DocumentProcessingInfoId);
|
||||
|
||||
info.Should().BeEquivalentTo(matchedRecord);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task FindByIdAsync_RecordDoesNotExist_ReturnsNull()
|
||||
{
|
||||
var records = new[]
|
||||
{
|
||||
DocumentProcessingInfoFactory(),
|
||||
DocumentProcessingInfoFactory(),
|
||||
DocumentProcessingInfoFactory()
|
||||
};
|
||||
await _context.DocumentProcessingInfos.AddRangeAsync(records);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
var info = await _repository.FindByIdAsync(_idSeries.Next());
|
||||
|
||||
Assert.That(info, Is.Null);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task FindListByIdAsync_RecordsMatchedById_ReturnsMatched()
|
||||
{
|
||||
var records = new[]
|
||||
{
|
||||
DocumentProcessingInfoFactory(),
|
||||
DocumentProcessingInfoFactory(),
|
||||
DocumentProcessingInfoFactory(),
|
||||
DocumentProcessingInfoFactory(),
|
||||
DocumentProcessingInfoFactory()
|
||||
};
|
||||
await _context.DocumentProcessingInfos.AddRangeAsync(records);
|
||||
await _context.SaveChangesAsync();
|
||||
var matchedRecords = records.Where((_, i) => i % 2 == 0).ToArray();
|
||||
var ids = matchedRecords
|
||||
.Select(r => r.DocumentProcessingInfoId)
|
||||
.Concat(new[] { _idSeries.Next() })
|
||||
.ToList();
|
||||
|
||||
var infos = await _repository.FindListByIdAsync(ids);
|
||||
|
||||
infos.Should().BeEquivalentTo(matchedRecords);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task FindByIngestorRunInfoIdAsync_RecordsMatchedByRunInfo_ReturnsMatched()
|
||||
{
|
||||
var runInfo = IngestorRunInfoFactory();
|
||||
var matchedRecords = new[]
|
||||
{
|
||||
DocumentProcessingInfoFactory(runInfo),
|
||||
DocumentProcessingInfoFactory(runInfo),
|
||||
DocumentProcessingInfoFactory(runInfo),
|
||||
};
|
||||
await _context.DocumentProcessingInfos.AddRangeAsync
|
||||
(
|
||||
DocumentProcessingInfoFactory(IngestorRunInfoFactory()),
|
||||
DocumentProcessingInfoFactory(IngestorRunInfoFactory())
|
||||
);
|
||||
await _context.DocumentProcessingInfos.AddRangeAsync(matchedRecords);
|
||||
await _context.DocumentProcessingInfos.AddRangeAsync
|
||||
(
|
||||
DocumentProcessingInfoFactory(IngestorRunInfoFactory()),
|
||||
DocumentProcessingInfoFactory(IngestorRunInfoFactory())
|
||||
);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
var infos = await _repository.FindByIngestorRunInfoIdAsync(runInfo.IngestorRunInfoId);
|
||||
|
||||
infos.Should().BeEquivalentTo(matchedRecords);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task FindByProcessStatusTypeAsync_RecordsMatchingType_ReturnsMatched()
|
||||
{
|
||||
var records = new[]
|
||||
{
|
||||
DocumentProcessingInfoFactory(),
|
||||
DocumentProcessingInfoFactory(),
|
||||
DocumentProcessingInfoFactory(),
|
||||
DocumentProcessingInfoFactory(),
|
||||
DocumentProcessingInfoFactory()
|
||||
};
|
||||
|
||||
var type = records[1].StatusType;
|
||||
await _context.DocumentProcessingInfos.AddRangeAsync(records);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
var infoByType = await _repository.FindByProcessStatusTypeAsync(type, null);
|
||||
|
||||
infoByType.Where(documentProcessingInfo => documentProcessingInfo.StatusType.Equals(type));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task FindByProcessStatusTypeListAsync_RecordsMatchingType_ReturnsMatched()
|
||||
{
|
||||
var records = new[]
|
||||
{
|
||||
DocumentProcessingInfoFactory(),
|
||||
DocumentProcessingInfoFactory(),
|
||||
DocumentProcessingInfoFactory(),
|
||||
DocumentProcessingInfoFactory(),
|
||||
DocumentProcessingInfoFactory()
|
||||
};
|
||||
|
||||
var types = records.Select(u => u.StatusType).ToList();
|
||||
|
||||
await _context.DocumentProcessingInfos.AddRangeAsync(records);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
var infoByType = await _repository.FindByProcessStatusTypeListAsync(types, null);
|
||||
|
||||
infoByType.Where(documentProcessingInfo => documentProcessingInfo.StatusType.Equals(types));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task FindByProcessStatusTypeAsync_RecordsMatchingType_IsUnknown_ReturnsMatched()
|
||||
{
|
||||
var records = new[]
|
||||
{
|
||||
DocumentProcessingInfoFactory(),
|
||||
DocumentProcessingInfoFactory(),
|
||||
DocumentProcessingInfoFactory(),
|
||||
DocumentProcessingInfoFactory(),
|
||||
DocumentProcessingInfoFactory()
|
||||
};
|
||||
records[0].EntityUnknown = true;
|
||||
records[2].EntityUnknown = true;
|
||||
var type = records[1].StatusType;
|
||||
await _context.DocumentProcessingInfos.AddRangeAsync(records);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
var infoByType = await _repository.FindByProcessStatusTypeAsync(type, true);
|
||||
|
||||
infoByType.Should().BeEquivalentTo(records.Where(r => r.StatusType == type && r.EntityUnknown));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task FindByProcessStatusTypeListAsync_RecordsMatchingType_IsUnknown_ReturnsMatched()
|
||||
{
|
||||
var records = new[]
|
||||
{
|
||||
DocumentProcessingInfoFactory(),
|
||||
DocumentProcessingInfoFactory(),
|
||||
DocumentProcessingInfoFactory(),
|
||||
DocumentProcessingInfoFactory(),
|
||||
DocumentProcessingInfoFactory()
|
||||
};
|
||||
|
||||
records[0].EntityUnknown = true;
|
||||
records[2].EntityUnknown = true;
|
||||
|
||||
var types = new List<ProcessStatusTypes> { records[1].StatusType };
|
||||
await _context.DocumentProcessingInfos.AddRangeAsync(records);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
var infoByType = await _repository.FindByProcessStatusTypeListAsync(types, true);
|
||||
|
||||
infoByType.Should().BeEquivalentTo(records.Where(r => r.StatusType.Equals(types) && r.EntityUnknown));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task FindCompletedFirstPassByIngestorRunInfoIdAsync_MatchedByRunInfoAndProcessedAtLeastOnce_ReturnsMatched()
|
||||
{
|
||||
var runInfo = IngestorRunInfoFactory();
|
||||
var matchedRecords = new[]
|
||||
{
|
||||
DocumentProcessingInfoFactory(runInfo),
|
||||
DocumentProcessingInfoFactory(runInfo),
|
||||
DocumentProcessingInfoFactory(runInfo),
|
||||
DocumentProcessingInfoFactory(runInfo),
|
||||
DocumentProcessingInfoFactory(runInfo),
|
||||
};
|
||||
var matchedProcessedRecords = matchedRecords
|
||||
.Where((_, i) => i % 2 == 0)
|
||||
.ToArray();
|
||||
foreach (var record in matchedProcessedRecords)
|
||||
{
|
||||
record.ProcessCount = Math.Max(1, DateTime.Now.Millisecond);
|
||||
}
|
||||
await _context.DocumentProcessingInfos.AddRangeAsync
|
||||
(
|
||||
DocumentProcessingInfoFactory(IngestorRunInfoFactory()),
|
||||
DocumentProcessingInfoFactory(IngestorRunInfoFactory())
|
||||
);
|
||||
await _context.DocumentProcessingInfos.AddRangeAsync(matchedRecords);
|
||||
await _context.DocumentProcessingInfos.AddRangeAsync
|
||||
(
|
||||
DocumentProcessingInfoFactory(IngestorRunInfoFactory()),
|
||||
DocumentProcessingInfoFactory(IngestorRunInfoFactory())
|
||||
);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
var runCompleted = await _repository.FindCompletedFirstPassByIngestorRunInfoIdAsync(runInfo.IngestorRunInfoId);
|
||||
|
||||
runCompleted.Should().BeEquivalentTo(matchedProcessedRecords);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task AddAsync_AllCases_TracksAndDoesntSave()
|
||||
{
|
||||
var info = DocumentProcessingInfoFactory();
|
||||
|
||||
var added = await _repository.AddAsync(info);
|
||||
|
||||
var tracked = _context.ChangeTracker.Entries<DocumentProcessingInfo>()
|
||||
.Select(e => e.Entity);
|
||||
var id = info.DocumentProcessingInfoId;
|
||||
var committed = _context.Database.SqlQuery<int>
|
||||
(
|
||||
@$"
|
||||
SELECT count(1)
|
||||
FROM DocumentProcessingInfo i
|
||||
WHERE i.DocumentProcessingInfoId = {id}
|
||||
"
|
||||
).ToArray()
|
||||
.Single();
|
||||
added.Should().BeEquivalentTo(info);
|
||||
tracked.Should().Contain(info);
|
||||
committed.Should().Be(0);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Update_InfoAlreadyExists_TracksAndDoesntSave()
|
||||
{
|
||||
var info = DocumentProcessingInfoFactory();
|
||||
await _context.DocumentProcessingInfos.AddAsync(info);
|
||||
await _context.SaveChangesAsync();
|
||||
info.ProcessCount++;
|
||||
|
||||
_repository.Update(info);
|
||||
|
||||
var tracked = _context.ChangeTracker.Entries<DocumentProcessingInfo>()
|
||||
.Where(e => e.State == EntityState.Modified)
|
||||
.Select(e => e.Entity);
|
||||
var id = info.DocumentProcessingInfoId;
|
||||
var count = info.ProcessCount;
|
||||
var updated = _context.Database.SqlQuery<int>
|
||||
(
|
||||
@$"
|
||||
SELECT count(1)
|
||||
FROM DocumentProcessingInfo i
|
||||
WHERE i.DocumentProcessingInfoId = {id}
|
||||
AND i.ProcessCount = {count}
|
||||
"
|
||||
).ToArray()
|
||||
.Single();
|
||||
tracked.Should().Contain(info);
|
||||
updated.Should().Be(0);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Remove_InfoAlreadyExists_TracksRemovalAndDoesntSave()
|
||||
{
|
||||
var info = DocumentProcessingInfoFactory();
|
||||
await _context.DocumentProcessingInfos.AddAsync(info);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
_repository.Remove(info);
|
||||
|
||||
var tracked = _context.ChangeTracker.Entries<DocumentProcessingInfo>()
|
||||
.Where(e => e.State == EntityState.Deleted)
|
||||
.Select(e => e.Entity);
|
||||
var id = info.DocumentProcessingInfoId;
|
||||
var stored = _context.Database.SqlQuery<int>
|
||||
(
|
||||
@$"
|
||||
SELECT count(1)
|
||||
FROM DocumentProcessingInfo i
|
||||
WHERE i.DocumentProcessingInfoId = {id}
|
||||
"
|
||||
).ToArray()
|
||||
.Single();
|
||||
tracked.Should().Contain(info);
|
||||
stored.Should().Be(1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task FilteredListAsync_ByDocType_CountsAndFiltersByDocType()
|
||||
{
|
||||
var records = new[]
|
||||
{
|
||||
DocumentProcessingInfoFactory(),
|
||||
DocumentProcessingInfoFactory(),
|
||||
DocumentProcessingInfoFactory(),
|
||||
DocumentProcessingInfoFactory(),
|
||||
DocumentProcessingInfoFactory(),
|
||||
DocumentProcessingInfoFactory(),
|
||||
DocumentProcessingInfoFactory(),
|
||||
DocumentProcessingInfoFactory(),
|
||||
DocumentProcessingInfoFactory(),
|
||||
DocumentProcessingInfoFactory(),
|
||||
DocumentProcessingInfoFactory(),
|
||||
DocumentProcessingInfoFactory()
|
||||
};
|
||||
var matchedRecords = records.Where((_, i) => i % 2 == 0).ToArray();
|
||||
var docType = matchedRecords.First().DocumentType;
|
||||
foreach (var record in matchedRecords)
|
||||
{
|
||||
record.DocumentType = docType;
|
||||
}
|
||||
await _context.DocumentProcessingInfos.AddRangeAsync(records);
|
||||
await _context.SaveChangesAsync();
|
||||
var filter = new Filter
|
||||
{
|
||||
DocType = docType,
|
||||
Page = 2,
|
||||
Limit = 2
|
||||
};
|
||||
|
||||
var filteredDocuments = await _repository.FilteredListAsync(filter);
|
||||
|
||||
filteredDocuments.Items.Should().BeEquivalentTo(matchedRecords.Skip((filter.Page - 1) * filter.Limit).Take(filter.Limit));
|
||||
filteredDocuments.Total.Should().Be(matchedRecords.Length);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task FilteredListAsync_ByBusinessName_CountsAndFiltersByBusinessName()
|
||||
{
|
||||
var records = new[]
|
||||
{
|
||||
DocumentProcessingInfoFactory(),
|
||||
DocumentProcessingInfoFactory(),
|
||||
DocumentProcessingInfoFactory(),
|
||||
DocumentProcessingInfoFactory(),
|
||||
DocumentProcessingInfoFactory(),
|
||||
DocumentProcessingInfoFactory(),
|
||||
DocumentProcessingInfoFactory(),
|
||||
DocumentProcessingInfoFactory(),
|
||||
DocumentProcessingInfoFactory(),
|
||||
DocumentProcessingInfoFactory(),
|
||||
DocumentProcessingInfoFactory(),
|
||||
DocumentProcessingInfoFactory()
|
||||
};
|
||||
var matchedRecords = records.Where((_, i) => i % 2 == 0).ToArray();
|
||||
var businessName = matchedRecords.First().DocumentBusinessName;
|
||||
foreach (var record in matchedRecords)
|
||||
{
|
||||
record.DocumentBusinessName = businessName;
|
||||
}
|
||||
await _context.DocumentProcessingInfos.AddRangeAsync(records);
|
||||
await _context.SaveChangesAsync();
|
||||
var filter = new Filter
|
||||
{
|
||||
DocBusinessName = businessName,
|
||||
Page = 2,
|
||||
Limit = 2
|
||||
};
|
||||
|
||||
var filteredDocuments = await _repository.FilteredListAsync(filter);
|
||||
|
||||
filteredDocuments.Items.Should().BeEquivalentTo(matchedRecords.Skip((filter.Page - 1) * filter.Limit).Take(filter.Limit));
|
||||
filteredDocuments.Total.Should().Be(matchedRecords.Length);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task FilteredListAsync_ByStatusType_CountsAndFiltersByStatusType()
|
||||
{
|
||||
var records = new[]
|
||||
{
|
||||
DocumentProcessingInfoFactory(),
|
||||
DocumentProcessingInfoFactory(),
|
||||
DocumentProcessingInfoFactory(),
|
||||
DocumentProcessingInfoFactory(),
|
||||
DocumentProcessingInfoFactory(),
|
||||
DocumentProcessingInfoFactory(),
|
||||
DocumentProcessingInfoFactory(),
|
||||
DocumentProcessingInfoFactory(),
|
||||
DocumentProcessingInfoFactory(),
|
||||
DocumentProcessingInfoFactory(),
|
||||
DocumentProcessingInfoFactory(),
|
||||
DocumentProcessingInfoFactory()
|
||||
};
|
||||
var matchedRecords = records.Where((_, i) => i % 2 == 0).ToArray();
|
||||
var statusType = (ProcessStatusTypes)int.MinValue;
|
||||
foreach (var record in matchedRecords)
|
||||
{
|
||||
record.StatusType = statusType;
|
||||
}
|
||||
await _context.DocumentProcessingInfos.AddRangeAsync(records);
|
||||
await _context.SaveChangesAsync();
|
||||
var filter = new Filter
|
||||
{
|
||||
StatusFilters = records
|
||||
.Select(i => i.StatusType)
|
||||
.Distinct()
|
||||
.Select(s => new StatusFilter { IsEnabled = s == statusType, StatusType = s })
|
||||
.ToList(),
|
||||
Page = 2,
|
||||
Limit = 2
|
||||
};
|
||||
|
||||
var filteredDocuments = await _repository.FilteredListAsync(filter);
|
||||
|
||||
filteredDocuments.Items.Should().BeEquivalentTo(matchedRecords.Skip((filter.Page - 1) * filter.Limit).Take(filter.Limit));
|
||||
filteredDocuments.Total.Should().Be(matchedRecords.Length);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task FilteredListAsync_ByCreatedDate_CountsAndFiltersByCreatedDate()
|
||||
{
|
||||
var records = new[]
|
||||
{
|
||||
DocumentProcessingInfoFactory(),
|
||||
DocumentProcessingInfoFactory(),
|
||||
DocumentProcessingInfoFactory(),
|
||||
DocumentProcessingInfoFactory(),
|
||||
DocumentProcessingInfoFactory(),
|
||||
DocumentProcessingInfoFactory(),
|
||||
DocumentProcessingInfoFactory(),
|
||||
DocumentProcessingInfoFactory(),
|
||||
DocumentProcessingInfoFactory(),
|
||||
DocumentProcessingInfoFactory(),
|
||||
DocumentProcessingInfoFactory(),
|
||||
DocumentProcessingInfoFactory()
|
||||
};
|
||||
var matchedRecords = records.Where((_, i) => i % 2 == 0).ToArray();
|
||||
var createdDate = matchedRecords.First().CreatedDate;
|
||||
foreach (var record in matchedRecords)
|
||||
{
|
||||
record.CreatedDate = createdDate;
|
||||
}
|
||||
foreach (var record in records.Where(r => !matchedRecords.Contains(r)))
|
||||
{
|
||||
record.CreatedDate = createdDate.AddDays(1);
|
||||
}
|
||||
await _context.DocumentProcessingInfos.AddRangeAsync(records);
|
||||
await _context.SaveChangesAsync();
|
||||
var filter = new Filter
|
||||
{
|
||||
CreatedDateStart = createdDate.AddMinutes(-1),
|
||||
CreatedDateEnd = createdDate.AddMinutes(1),
|
||||
Page = 2,
|
||||
Limit = 2
|
||||
};
|
||||
|
||||
var filteredDocuments = await _repository.FilteredListAsync(filter);
|
||||
|
||||
filteredDocuments.Items.Should().HaveCount(filter.Limit);
|
||||
filteredDocuments.Items.Should().BeSubsetOf(matchedRecords);
|
||||
filteredDocuments.Total.Should().Be(matchedRecords.Length);
|
||||
}
|
||||
|
||||
|
||||
[Test]
|
||||
public async Task FilteredListAsync_ByIngestorRunInfoId_CountsAndFiltersByCreatedDate()
|
||||
{
|
||||
var runInfo = IngestorRunInfoFactory();
|
||||
var records = new[]
|
||||
{
|
||||
DocumentProcessingInfoFactory(),
|
||||
DocumentProcessingInfoFactory(),
|
||||
DocumentProcessingInfoFactory(),
|
||||
DocumentProcessingInfoFactory(),
|
||||
DocumentProcessingInfoFactory(),
|
||||
DocumentProcessingInfoFactory(),
|
||||
DocumentProcessingInfoFactory(),
|
||||
DocumentProcessingInfoFactory(),
|
||||
DocumentProcessingInfoFactory(),
|
||||
DocumentProcessingInfoFactory(),
|
||||
DocumentProcessingInfoFactory(),
|
||||
DocumentProcessingInfoFactory()
|
||||
};
|
||||
var matchedRecords = records.Where((_, i) => i % 2 == 0).ToArray();
|
||||
foreach (var record in matchedRecords)
|
||||
{
|
||||
record.IngestorRunInfo = runInfo;
|
||||
}
|
||||
await _context.DocumentProcessingInfos.AddRangeAsync(records);
|
||||
await _context.SaveChangesAsync();
|
||||
var filter = new Filter
|
||||
{
|
||||
IngestorRunInfoId = runInfo.IngestorRunInfoId,
|
||||
Page = 2,
|
||||
Limit = 2
|
||||
};
|
||||
|
||||
var filteredDocuments = await _repository.FilteredListAsync(filter);
|
||||
|
||||
filteredDocuments.Items.Should().HaveCount(filter.Limit);
|
||||
filteredDocuments.Items.Should().BeSubsetOf(matchedRecords);
|
||||
filteredDocuments.Total.Should().Be(matchedRecords.Length);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[Ignore("a real SQL server connection in EF is required for the json filter to work\nthe filter is tested on its own in an integration test")]
|
||||
public async Task FilteredListAsync_ByIndexFileJsonKeyValue_CountsAndFiltersByDocumentValuePayloadContainingJson()
|
||||
{
|
||||
var records = new[]
|
||||
{
|
||||
DocumentProcessingInfoFactory(),
|
||||
DocumentProcessingInfoFactory(),
|
||||
DocumentProcessingInfoFactory(),
|
||||
DocumentProcessingInfoFactory(),
|
||||
DocumentProcessingInfoFactory(),
|
||||
DocumentProcessingInfoFactory(),
|
||||
DocumentProcessingInfoFactory(),
|
||||
DocumentProcessingInfoFactory(),
|
||||
DocumentProcessingInfoFactory(),
|
||||
DocumentProcessingInfoFactory(),
|
||||
DocumentProcessingInfoFactory(),
|
||||
DocumentProcessingInfoFactory()
|
||||
};
|
||||
var matchedRecords = records.Where((_, i) => i % 2 == 0).ToArray();
|
||||
var property = JObject.Parse(matchedRecords.First().DocumentValuePayload)
|
||||
.Properties()
|
||||
.First();
|
||||
foreach (var record in matchedRecords)
|
||||
{
|
||||
var payload = JObject.Parse(record.DocumentValuePayload);
|
||||
payload[property.Name] = property.Value;
|
||||
|
||||
record.DocumentValuePayload = payload.ToString().Replace(" ", string.Empty);
|
||||
}
|
||||
await _context.DocumentProcessingInfos.AddRangeAsync(records);
|
||||
await _context.SaveChangesAsync();
|
||||
var filter = new Filter
|
||||
{
|
||||
CustomIndexFileFiltersEquals = new Dictionary<string, string>
|
||||
{
|
||||
[property.Name] = property.Value.ToString()
|
||||
},
|
||||
Page = 2,
|
||||
Limit = 2
|
||||
};
|
||||
|
||||
var filteredDocuments = await _repository.FilteredListAsync(filter);
|
||||
|
||||
filteredDocuments.Items.Should().BeEquivalentTo(matchedRecords.Skip((filter.Page - 1) * filter.Limit).Take(filter.Limit));
|
||||
filteredDocuments.Total.Should().Be(matchedRecords.Length);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task FilteredListAsync_ByNotificationStatus_CountsAndFiltersByMemberNotificationInfoStatusType()
|
||||
{
|
||||
var notificationStatus = NotificationStatusTypesFactory();
|
||||
var records = new[]
|
||||
{
|
||||
DocumentProcessingInfoFactory(),
|
||||
DocumentProcessingInfoFactory(),
|
||||
DocumentProcessingInfoFactory(),
|
||||
DocumentProcessingInfoFactory(),
|
||||
DocumentProcessingInfoFactory(),
|
||||
DocumentProcessingInfoFactory(),
|
||||
DocumentProcessingInfoFactory(),
|
||||
DocumentProcessingInfoFactory(),
|
||||
DocumentProcessingInfoFactory(),
|
||||
DocumentProcessingInfoFactory(),
|
||||
DocumentProcessingInfoFactory(),
|
||||
DocumentProcessingInfoFactory()
|
||||
};
|
||||
var matchedRecords = records.Where((_, i) => i % 2 == 0).ToArray();
|
||||
foreach (var record in matchedRecords)
|
||||
{
|
||||
if (!record.MemberNotificationInfos.Any()) continue;
|
||||
foreach (var notification in record.MemberNotificationInfos)
|
||||
{
|
||||
notification.NotificationStatusType = notificationStatus;
|
||||
}
|
||||
}
|
||||
foreach (var record in records.Except(matchedRecords))
|
||||
{
|
||||
if (!record.MemberNotificationInfos.Any()) continue;
|
||||
foreach (var notification in record.MemberNotificationInfos)
|
||||
{
|
||||
notification.NotificationStatusType = NotificationStatusTypesFactory(not: notificationStatus);
|
||||
}
|
||||
|
||||
}
|
||||
await _context.DocumentProcessingInfos.AddRangeAsync(records);
|
||||
await _context.SaveChangesAsync();
|
||||
var filter = new Filter
|
||||
{
|
||||
NotificationStatusFilters = new List<NotificationStatusTypes> { notificationStatus },
|
||||
Page = 2,
|
||||
Limit = 2
|
||||
};
|
||||
|
||||
var filteredDocuments = await _repository.FilteredListAsync(filter);
|
||||
|
||||
filteredDocuments.Items.Should().BeEquivalentTo(matchedRecords.Skip((filter.Page - 1) * filter.Limit).Take(filter.Limit));
|
||||
filteredDocuments.Total.Should().Be(matchedRecords.Length);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task CountCompletedFirstPassByIngestorRunInfoIdAsync_AllCases_ReturnsCountOfDocumentProcessingInfoWithIngestorRunInfoIdAndProcessCountMoreThanOne()
|
||||
{
|
||||
var ingestorRunInfo = IngestorRunInfoFactory();
|
||||
var runInfos = new List<IngestorRunInfo> { ingestorRunInfo };
|
||||
var documents = Enumerable.Repeat
|
||||
(
|
||||
() => DocumentProcessingInfoFactory(ingestorRunInfo),
|
||||
100
|
||||
).Select(f => f())
|
||||
.ToArray();
|
||||
var expectedCount = 0;
|
||||
foreach (var (document, index) in documents.Select((d, i) => (d, i)))
|
||||
{
|
||||
document.ProcessCount = index % 2 == 0 ? 0 : 1;
|
||||
|
||||
if (index % 3 == 0)
|
||||
{
|
||||
document.IngestorRunInfoId = ingestorRunInfo.IngestorRunInfoId;
|
||||
document.IngestorRunInfo = ingestorRunInfo;
|
||||
}
|
||||
else
|
||||
{
|
||||
var run = IngestorRunInfoFactory();
|
||||
runInfos.Add(run);
|
||||
|
||||
document.IngestorRunInfoId = run.IngestorRunInfoId;
|
||||
document.IngestorRunInfo = run;
|
||||
}
|
||||
|
||||
expectedCount += index % 2 == 0 && index % 3 == 0 ? 1 : 0;
|
||||
}
|
||||
await _context.IngestorRunInfos.AddRangeAsync(runInfos);
|
||||
await _context.DocumentProcessingInfos.AddRangeAsync(documents);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
var count = await _repository.CountCompletedFirstPassByIngestorRunInfoIdAsync(ingestorRunInfo.IngestorRunInfoId);
|
||||
|
||||
Assert.That(count, Is.EqualTo(expectedCount));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task FindByDocumentIdsAsync_NullDocumentId_NotReturned()
|
||||
{
|
||||
var info = DocumentProcessingInfoFactory();
|
||||
info.DocumentId = null;
|
||||
var ids = Enumerable.Repeat(Guid.NewGuid, 5)
|
||||
.Select(f => f())
|
||||
.ToArray();
|
||||
await _context.DocumentProcessingInfos.AddRangeAsync(info);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
var infos = await _repository.FindByDocumentIdsAsync(ids);
|
||||
|
||||
CollectionAssert.DoesNotContain(infos, info);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task FindByDocumentIdsAsync_DocumentIdNotInIds_NotReturned()
|
||||
{
|
||||
var info = DocumentProcessingInfoFactory();
|
||||
info.DocumentId = Guid.NewGuid();
|
||||
var ids = Enumerable.Repeat(Guid.NewGuid, 5)
|
||||
.Select(f => f())
|
||||
.ToArray();
|
||||
await _context.DocumentProcessingInfos.AddRangeAsync(info);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
var infos = await _repository.FindByDocumentIdsAsync(ids);
|
||||
|
||||
CollectionAssert.DoesNotContain(infos, info);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task FindByDocumentIdsAsync_DocumentIdsInIds_ReturnsAllMatched()
|
||||
{
|
||||
var expectedInfos = Enumerable.Repeat(DocumentProcessingInfoFactory, 5)
|
||||
.Select(f =>
|
||||
{
|
||||
var info = f(null);
|
||||
info.DocumentId = Guid.NewGuid();
|
||||
|
||||
return info;
|
||||
})
|
||||
.ToArray();
|
||||
var ids = Enumerable.Repeat(Guid.NewGuid, expectedInfos.Length)
|
||||
.Select(f => f())
|
||||
.Concat(expectedInfos.Select(i => i.DocumentId.Value))
|
||||
.Concat
|
||||
(
|
||||
Enumerable.Repeat(Guid.NewGuid, expectedInfos.Length)
|
||||
.Select(f => f())
|
||||
)
|
||||
.ToArray();
|
||||
await _context.DocumentProcessingInfos.AddRangeAsync(expectedInfos);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
var infos = await _repository.FindByDocumentIdsAsync(ids);
|
||||
|
||||
infos.Should().Contain(expectedInfos);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_contextFactory.Dispose();
|
||||
}
|
||||
|
||||
private readonly Random _idSeries = new(DateTime.Now.Millisecond);
|
||||
|
||||
private readonly IngestorRunInfo _runInfo = IngestorRunInfoFactory();
|
||||
|
||||
private static IngestorRunInfo IngestorRunInfoFactory()
|
||||
{
|
||||
return new()
|
||||
{
|
||||
EndTimeStamp = DateTime.Now,
|
||||
IngestorType = IngestorTypes.FosDnaDocument,
|
||||
IngestorRunInfoId = Guid.NewGuid(),
|
||||
IndexTotal = DateTime.Now.Millisecond,
|
||||
IndexFileName = Guid.NewGuid().ToString(),
|
||||
IndexFilePath = Guid.NewGuid().ToString(),
|
||||
StartTimeStamp = DateTime.Now
|
||||
};
|
||||
}
|
||||
|
||||
private ProcessStatusTypes ProcessStatusTypesFactory()
|
||||
=> EnumValueFactory<ProcessStatusTypes>();
|
||||
|
||||
private NotificationStatusTypes NotificationStatusTypesFactory(NotificationStatusTypes? not = null)
|
||||
=> EnumValueFactory(not);
|
||||
|
||||
private T EnumValueFactory<T>(T? not = null) where T : struct, Enum
|
||||
{
|
||||
var availableValues = Enum.GetValues<T>().Where(t => not == null || !t.Equals(not.Value)).ToArray();
|
||||
|
||||
return availableValues[_idSeries.Next() % availableValues.Length];
|
||||
}
|
||||
|
||||
private DocumentProcessingInfo DocumentProcessingInfoFactory(IngestorRunInfo? runInfo = null)
|
||||
=> new()
|
||||
{
|
||||
CreatedDate = DateTime.Now,
|
||||
DocumentFileName = Guid.NewGuid().ToString(),
|
||||
DocumentId = Guid.NewGuid(),
|
||||
DocumentType = Guid.NewGuid().ToString(),
|
||||
DocumentBusinessName = Guid.NewGuid().ToString(),
|
||||
DocumentValuePayload = @$"
|
||||
{{
|
||||
""{Guid.NewGuid()}"":""{Guid.NewGuid().ToString()}"",
|
||||
""{Guid.NewGuid()}"":""{Guid.NewGuid().ToString()}"",
|
||||
""{Guid.NewGuid()}"":""{Guid.NewGuid().ToString()}""
|
||||
}}
|
||||
",
|
||||
DocumentProcessingInfoId = _idSeries.Next(),
|
||||
IngestorRunInfoId = (runInfo ?? _runInfo).IngestorRunInfoId,
|
||||
IngestorRunInfo = runInfo ?? _runInfo,
|
||||
ProcessCount = 0,
|
||||
StatusType = ProcessStatusTypesFactory(),
|
||||
MemberNotificationInfos = NotificationInfoFactory()
|
||||
};
|
||||
|
||||
private List<MemberNotificationInfo> NotificationInfoFactory()
|
||||
=> new() {
|
||||
new MemberNotificationInfo
|
||||
{
|
||||
NotificationStatusType = NotificationStatusTypesFactory()
|
||||
},
|
||||
new MemberNotificationInfo
|
||||
{
|
||||
NotificationStatusType = NotificationStatusTypesFactory()
|
||||
},
|
||||
new MemberNotificationInfo
|
||||
{
|
||||
NotificationStatusType = NotificationStatusTypesFactory()
|
||||
}
|
||||
};
|
||||
}
|
||||
+133
@@ -0,0 +1,133 @@
|
||||
using FluentAssertions;
|
||||
using LCU.API.Internal.DocumentIngestorLib.Persistence;
|
||||
using LCU.API.Internal.DocumentIngestorLib.Repositories;
|
||||
using LCU.Library.Internal.DocumentIngestorModels.Entities;
|
||||
using LCU.Library.Internal.DocumentIngestorModels.Enums;
|
||||
using LCU.Library.Internal.DocumentIngestorModels.Models.Pagination;
|
||||
|
||||
namespace LCU.API.Internal.DocumentIngestorLibTests.Repositories;
|
||||
|
||||
public class MemberNotificationInfoRepositoryTest : IDisposable
|
||||
{
|
||||
private IMemberNotificationInfoRepository _repository;
|
||||
|
||||
private readonly SqliteAppDbContextFactory _contextFactory = new();
|
||||
private AppDbContext _context;
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
_context = _contextFactory.CreateContext();
|
||||
_repository = new MemberNotificationInfoRepository(_context);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task FindNotificationByDocumentProcessingInfoIdAsync_NoneMatchedByDocumentProcessingInfoId_ReturnsNull()
|
||||
{
|
||||
_context.MemberNotificationInfos.RemoveRange(_context.MemberNotificationInfos);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
var notifications = await _repository.FindByDocumentProcessingInfoIdAsync(DateTime.Now.Millisecond);
|
||||
|
||||
Assert.That(notifications, Is.Empty);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task FindNotificationByDocumentProcessingInfoIdAsync_OneMatchedByDocumentProcessingInfoId_ReturnsRecord()
|
||||
{
|
||||
var documentProcessingInfo = (await _context.DocumentProcessingInfos.AddAsync(DocumentProcessingInfoFactory())).Entity;
|
||||
var notificationRecord = (await _context.MemberNotificationInfos.AddAsync(MemberNotificationInfoFactory(documentProcessingInfo.DocumentProcessingInfoId))).Entity;
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
var notification = await _repository.FindByDocumentProcessingInfoIdAsync(notificationRecord.DocumentProcessingInfoId);
|
||||
|
||||
notification.FirstOrDefault().Should().BeEquivalentTo(notificationRecord);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task FindNotificationByDocumentProcessingInfoIdAsync_MultipleMatchedByDocumentProcessingInfoId_ReturnsFirst()
|
||||
{
|
||||
var documentProcessingInfo = (await _context.DocumentProcessingInfos.AddAsync(DocumentProcessingInfoFactory())).Entity;
|
||||
var notificationRecord = MemberNotificationInfoFactory(documentProcessingInfo.DocumentProcessingInfoId);
|
||||
|
||||
await _context.MemberNotificationInfos.AddRangeAsync(notificationRecord,
|
||||
MemberNotificationInfoFactory(notificationRecord.DocumentProcessingInfoId),
|
||||
MemberNotificationInfoFactory(notificationRecord.DocumentProcessingInfoId));
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
var notification = await _repository.FindByDocumentProcessingInfoIdAsync(documentProcessingInfo.DocumentProcessingInfoId);
|
||||
notification.FirstOrDefault()?.DocumentProcessingInfoId.Equals(documentProcessingInfo.DocumentProcessingInfoId);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task GetMemberNotificationInfosByFilter_ReturnsRecords()
|
||||
{
|
||||
var documentProcessingInfos = new List<DocumentProcessingInfo>
|
||||
{
|
||||
DocumentProcessingInfoFactory(DateTime.Now.Millisecond),
|
||||
DocumentProcessingInfoFactory(DateTime.Now.Millisecond + 1),
|
||||
DocumentProcessingInfoFactory(DateTime.Now.Millisecond + 2)
|
||||
};
|
||||
await _context.DocumentProcessingInfos.AddRangeAsync(documentProcessingInfos);
|
||||
await _context.MemberNotificationInfos.AddRangeAsync(new List<MemberNotificationInfo>()
|
||||
{
|
||||
new() {
|
||||
DocumentProcessingInfoId = documentProcessingInfos[0].DocumentProcessingInfoId,
|
||||
NotificationStatusType = NotificationStatusTypes.CriticalFailure
|
||||
},
|
||||
new() {
|
||||
DocumentProcessingInfoId = documentProcessingInfos[1].DocumentProcessingInfoId,
|
||||
NotificationStatusType = NotificationStatusTypes.CriticalFailure
|
||||
},
|
||||
new() {
|
||||
DocumentProcessingInfoId = documentProcessingInfos[2].DocumentProcessingInfoId,
|
||||
NotificationStatusType = NotificationStatusTypes.Complete
|
||||
}
|
||||
}
|
||||
);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
var filter = new Filter
|
||||
{
|
||||
NotificationStatusFilters = new List<NotificationStatusTypes>
|
||||
{
|
||||
NotificationStatusTypes.CriticalFailure
|
||||
}
|
||||
};
|
||||
|
||||
var notificationInfos = await _repository.GetMemberNotificationInfosByFilter(filter);
|
||||
|
||||
Assert.That(notificationInfos.Count(), Is.EqualTo(2));
|
||||
Assert.That(notificationInfos.FirstOrDefault()?.NotificationStatusType, Is.EqualTo(filter.NotificationStatusFilters.FirstOrDefault()));
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_contextFactory.Dispose();
|
||||
}
|
||||
|
||||
private static MemberNotificationInfo MemberNotificationInfoFactory(int? documentProcessingInfoId = null)
|
||||
=> new()
|
||||
{
|
||||
DocumentProcessingInfoId = documentProcessingInfoId ?? DateTime.Now.Millisecond
|
||||
};
|
||||
|
||||
private static DocumentProcessingInfo DocumentProcessingInfoFactory(int? documentProcessingInfoId = null)
|
||||
=> new()
|
||||
{
|
||||
DocumentProcessingInfoId = documentProcessingInfoId ?? DateTime.Now.Millisecond,
|
||||
CreatedDate = DateTime.Now,
|
||||
DocumentValuePayload = "test",
|
||||
StatusType = ProcessStatusTypes.Completed,
|
||||
ProcessCount = 1,
|
||||
DocumentType = "TaxDocument",
|
||||
DocumentBusinessName = "1099-MISC",
|
||||
EntityUnknown = false,
|
||||
IngestorRunInfo = new IngestorRunInfo()
|
||||
{
|
||||
IngestorRunInfoId = Guid.NewGuid(),
|
||||
IndexTotal = 1,
|
||||
IndexFileName = "test"
|
||||
}
|
||||
};
|
||||
}
|
||||
+120
@@ -0,0 +1,120 @@
|
||||
using LCU.API.Internal.DocumentIngestorLib.Persistence;
|
||||
using LCU.API.Internal.DocumentIngestorLib.Repositories;
|
||||
using LCU.API.Internal.DocumentIngestorLibTests;
|
||||
using LCU.Library.Internal.DocumentIngestorModels.Entities;
|
||||
using LCU.Library.Internal.DocumentIngestorModels.Enums;
|
||||
|
||||
namespace LCU.API.Internal.DocumentIngestorLibTests.Repositories;
|
||||
|
||||
public class ProcessingLogRepositoryTest
|
||||
{
|
||||
private IProcessingLogRepository _repository;
|
||||
|
||||
private readonly SqliteAppDbContextFactory _contextFactory = new();
|
||||
private AppDbContext _context;
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
_context = _contextFactory.CreateContext();
|
||||
_repository = new ProcessingLogRepository(_context);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task FindAsync_ReturnsRecord()
|
||||
{
|
||||
var id = DateTime.Now.Millisecond;
|
||||
await _context.DocumentProcessingInfos.AddAsync(DocumentProcessingInfoFactory(id));
|
||||
var processingLogs = new List<ProcessingLog>
|
||||
{ProcessingLogFactory(id + 1), ProcessingLogFactory(id + 2), ProcessingLogFactory(id + 3)};
|
||||
processingLogs.ForEach(x => x.DocumentProcessingInfoId = id);
|
||||
await _context.ProcessingLogs.AddRangeAsync(processingLogs);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
var logs = await _repository.FindAsync();
|
||||
Assert.That(logs.Count(), Is.EqualTo(3));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task FindByIdAsync_ReturnsRecord()
|
||||
{
|
||||
var id = DateTime.Now.Millisecond;
|
||||
await _context.DocumentProcessingInfos.AddAsync(DocumentProcessingInfoFactory(id + 1));
|
||||
var log = ProcessingLogFactory(id);
|
||||
log.DocumentProcessingInfoId = id + 1;
|
||||
await _context.ProcessingLogs.AddAsync(log);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
var logs = await _repository.FindByIdAsync(id);
|
||||
Assert.That(logs.ProcessingLogId, Is.EqualTo(id));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task FindByIdAsync_ReturnsNull()
|
||||
{
|
||||
var id = DateTime.Now.Millisecond;
|
||||
await _context.DocumentProcessingInfos.AddAsync(DocumentProcessingInfoFactory(id + 1));
|
||||
var log = ProcessingLogFactory(id);
|
||||
log.DocumentProcessingInfoId = id + 1;
|
||||
await _context.ProcessingLogs.AddAsync(log);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
var logs = await _repository.FindByIdAsync(id + 5);
|
||||
Assert.That(logs, Is.Null);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task FindByDocumentProcessingInfoIdAsync_ReturnsRecord()
|
||||
{
|
||||
var id = DateTime.Now.Millisecond;
|
||||
await _context.DocumentProcessingInfos.AddAsync(DocumentProcessingInfoFactory(id));
|
||||
var log = ProcessingLogFactory();
|
||||
log.DocumentProcessingInfoId = id;
|
||||
await _context.ProcessingLogs.AddAsync(log);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
var logs = await _repository.FindByDocumentProcessingInfoIdAsync(id);
|
||||
Assert.That(logs.FirstOrDefault()?.DocumentProcessingInfoId, Is.EqualTo(id));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task FindByDocumentProcessingInfoIdAsync_ReturnsNull()
|
||||
{
|
||||
var id = DateTime.Now.Millisecond;
|
||||
await _context.DocumentProcessingInfos.AddAsync(DocumentProcessingInfoFactory(id));
|
||||
var log = ProcessingLogFactory();
|
||||
log.DocumentProcessingInfoId = id;
|
||||
await _context.ProcessingLogs.AddAsync(log);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
var logs = await _repository.FindByDocumentProcessingInfoIdAsync(id + 5);
|
||||
Assert.That(logs, Is.Empty);
|
||||
}
|
||||
|
||||
private static ProcessingLog ProcessingLogFactory(int? processingLogId = null)
|
||||
=> new()
|
||||
{
|
||||
ProcessingLogId = processingLogId ?? DateTime.Now.Millisecond,
|
||||
DocumentProcessingInfoId = DateTime.Now.Millisecond,
|
||||
LogMessage = Guid.NewGuid().ToString(),
|
||||
};
|
||||
|
||||
private static DocumentProcessingInfo DocumentProcessingInfoFactory(int? documentProcessingInfoId = null)
|
||||
=> new()
|
||||
{
|
||||
DocumentProcessingInfoId = documentProcessingInfoId ?? DateTime.Now.Millisecond,
|
||||
CreatedDate = DateTime.Now,
|
||||
DocumentValuePayload = "test",
|
||||
StatusType = ProcessStatusTypes.Completed,
|
||||
ProcessCount = 1,
|
||||
DocumentType = "TaxDocument",
|
||||
DocumentBusinessName = "1099-MISC",
|
||||
EntityUnknown = false,
|
||||
IngestorRunInfo = new IngestorRunInfo()
|
||||
{
|
||||
IngestorRunInfoId = Guid.NewGuid(),
|
||||
IndexTotal = 1,
|
||||
IndexFileName = "test"
|
||||
}
|
||||
};
|
||||
}
|
||||
+138
@@ -0,0 +1,138 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Data;
|
||||
using Docker.DotNet;
|
||||
using Docker.DotNet.Models;
|
||||
using LCU.API.Internal.DocumentIngestorLib.Persistence;
|
||||
using Microsoft.Data.SqlClient;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace LCU.API.Internal.DocumentIngestorLibTests;
|
||||
|
||||
public class SqlServerDbContextFactory : IAsyncDisposable
|
||||
{
|
||||
private const int port = 1434;
|
||||
private const string password = "yourStrong(!)Password";
|
||||
|
||||
private readonly IList<AppDbContext> _contexts = new List<AppDbContext>();
|
||||
|
||||
private static readonly SemaphoreSlim ContainerUse = new(1);
|
||||
private static CreateContainerResponse? Container;
|
||||
private static readonly IDictionary<AppDbContext, bool> _activeContexts = new ConcurrentDictionary<AppDbContext, bool>();
|
||||
|
||||
public async Task<AppDbContext> CreateContext()
|
||||
{
|
||||
await ContainerUse.WaitAsync();
|
||||
Container ??= await DatabaseContainerFactory();
|
||||
ContainerUse.Release();
|
||||
|
||||
var connectionString =
|
||||
$"Data Source=localhost,{port};User Id=sa;Password={password};Database={Guid.NewGuid()};MultipleActiveResultSets=True;App=EntityFramework;Trust Server Certificate=true";
|
||||
|
||||
var options = new DbContextOptionsBuilder<AppDbContext>()
|
||||
.UseSqlServer(connectionString)
|
||||
.EnableSensitiveDataLogging()
|
||||
.Options;
|
||||
|
||||
var context = new AppDbContext(options);
|
||||
await DatabaseConnectionReady(connectionString);
|
||||
await context.Database.EnsureCreatedAsync();
|
||||
|
||||
_contexts.Add(context);
|
||||
_activeContexts[context] = true;
|
||||
return context;
|
||||
}
|
||||
|
||||
private static async Task<CreateContainerResponse> DatabaseContainerFactory()
|
||||
{
|
||||
const string ImageName = "mcr.microsoft.com/mssql/server:2019-CU18-ubuntu-20.04";
|
||||
const int containerPort = 1433;
|
||||
|
||||
using var docker = new DockerClientConfiguration().CreateClient();
|
||||
|
||||
await docker.Images.CreateImageAsync
|
||||
(
|
||||
new ImagesCreateParameters
|
||||
{
|
||||
FromImage = ImageName
|
||||
},
|
||||
null,
|
||||
new Progress<JSONMessage>()
|
||||
);
|
||||
var container = await docker.Containers.CreateContainerAsync
|
||||
(
|
||||
new CreateContainerParameters
|
||||
{
|
||||
Image = ImageName,
|
||||
Name = "LCU.API.Internal.DocumentIngestor.IntegrationTestDatabase",
|
||||
Env = new List<string>
|
||||
{
|
||||
"ACCEPT_EULA=Y",
|
||||
$"MSSQL_SA_PASSWORD={password}"
|
||||
},
|
||||
ExposedPorts = new Dictionary<string, EmptyStruct>
|
||||
{
|
||||
{$"{containerPort}", default}
|
||||
},
|
||||
HostConfig = new HostConfig
|
||||
{
|
||||
PortBindings = new Dictionary<string, IList<PortBinding>>
|
||||
{
|
||||
{ $"{containerPort}", new List<PortBinding> { new() { HostPort = $"{port}" } } }
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
await docker.Containers.StartContainerAsync(container.ID, new ContainerStartParameters());
|
||||
|
||||
return container;
|
||||
}
|
||||
|
||||
private static async Task DatabaseConnectionReady(string connectionString)
|
||||
{
|
||||
await using var connection = new SqlConnection(connectionString);
|
||||
|
||||
var ready = false;
|
||||
var triesRemaining = 20;
|
||||
while (!ready && triesRemaining > 0)
|
||||
{
|
||||
triesRemaining--;
|
||||
|
||||
try
|
||||
{
|
||||
await connection.OpenAsync();
|
||||
}
|
||||
catch
|
||||
{
|
||||
await Task.Delay(TimeSpan.FromMilliseconds(200));
|
||||
}
|
||||
|
||||
ready = connection.State == ConnectionState.Open;
|
||||
}
|
||||
}
|
||||
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
foreach (var context in _contexts)
|
||||
{
|
||||
await context.DisposeAsync();
|
||||
_activeContexts[context] = false;
|
||||
}
|
||||
|
||||
await ContainerUse.WaitAsync();
|
||||
if (_activeContexts.Values.All(a => a == false))
|
||||
{
|
||||
await DisposeContainer(Container);
|
||||
Container = null;
|
||||
}
|
||||
ContainerUse.Release();
|
||||
}
|
||||
|
||||
private static async Task DisposeContainer(CreateContainerResponse container)
|
||||
{
|
||||
using var docker = new DockerClientConfiguration().CreateClient();
|
||||
|
||||
await docker.Containers.StopContainerAsync(container.ID, new ContainerStopParameters());
|
||||
await docker.Containers.RemoveContainerAsync(container.ID, new ContainerRemoveParameters());
|
||||
}
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
using LCU.API.Internal.DocumentIngestorLib.Persistence;
|
||||
using Microsoft.Data.Sqlite;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace LCU.API.Internal.DocumentIngestorLibTests;
|
||||
|
||||
public class SqliteAppDbContextFactory : IDisposable
|
||||
{
|
||||
private readonly IList<SqliteConnection> _connections = new List<SqliteConnection>();
|
||||
private readonly IList<AppDbContext> _contexts = new List<AppDbContext>();
|
||||
|
||||
public AppDbContext CreateContext()
|
||||
{
|
||||
var connection = new SqliteConnection("Filename=:memory:");
|
||||
_connections.Add(connection);
|
||||
connection.Open();
|
||||
|
||||
var options = new DbContextOptionsBuilder<AppDbContext>()
|
||||
.UseSqlite(connection)
|
||||
.EnableSensitiveDataLogging()
|
||||
.Options;
|
||||
|
||||
var context = new AppDbContext(options);
|
||||
context.Database.EnsureCreated();
|
||||
|
||||
_contexts.Add(context);
|
||||
return context;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
foreach (var connection in _connections) connection.Dispose();
|
||||
foreach (var context in _contexts) context.Dispose();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
global using NUnit.Framework;
|
||||
Reference in New Issue
Block a user