From 5ca72424f3b46824a0fc3782a934970bf13482eb Mon Sep 17 00:00:00 2001 From: Michael Date: Mon, 22 Apr 2024 20:02:09 -0700 Subject: [PATCH] Adds more unit tests (many more to come) --- Binner/Binner.Testing/Binner.Testing.csproj | 24 + Binner/Binner.Testing/GlobalUsings.cs | 1 + .../Binner.Testing/InMemoryStorageProvider.cs | 434 ++++++++++++++++++ Binner/Binner.sln | 7 + .../Extensions/IPAddressExtensions.cs | 128 +++++- .../Binner.Common/IO/CsvDataImporter.cs | 2 +- Binner/Library/Binner.Model/IBinnerDb.cs | 4 +- Binner/Library/Binner.Model/StoredFile.cs | 5 + .../Binner.Common.Tests.csproj | 31 +- .../Binner.Common.Tests/IO/BinnerParts.xlsx | Bin 0 -> 20275 bytes .../IO/CsvDataImporterTests.cs | 218 +++++++++ .../IO/ExcelDataImporterTests.cs | 37 ++ .../IO/IPAddressExtensionsTests.cs | 39 ++ .../Binner.Common.Tests/IO/PartTypes.csv | 205 +++++++++ Binner/Tests/Binner.Common.Tests/IO/Parts.csv | 2 + .../Tests/Binner.Common.Tests/IO/Projects.csv | 2 + .../IO/SqlDataImporterTests.cs | 251 ++++++++++ .../Binner.Web.Tests/Binner.Web.Tests.csproj | 12 +- .../VersionHeaderMiddlewareTests.cs | 38 ++ Binner/Tests/Binner.Web.Tests/UnitTest1.cs | 18 - 20 files changed, 1420 insertions(+), 38 deletions(-) create mode 100644 Binner/Binner.Testing/Binner.Testing.csproj create mode 100644 Binner/Binner.Testing/GlobalUsings.cs create mode 100644 Binner/Binner.Testing/InMemoryStorageProvider.cs create mode 100644 Binner/Tests/Binner.Common.Tests/IO/BinnerParts.xlsx create mode 100644 Binner/Tests/Binner.Common.Tests/IO/CsvDataImporterTests.cs create mode 100644 Binner/Tests/Binner.Common.Tests/IO/ExcelDataImporterTests.cs create mode 100644 Binner/Tests/Binner.Common.Tests/IO/IPAddressExtensionsTests.cs create mode 100644 Binner/Tests/Binner.Common.Tests/IO/PartTypes.csv create mode 100644 Binner/Tests/Binner.Common.Tests/IO/Parts.csv create mode 100644 Binner/Tests/Binner.Common.Tests/IO/Projects.csv create mode 100644 Binner/Tests/Binner.Common.Tests/IO/SqlDataImporterTests.cs create mode 100644 Binner/Tests/Binner.Web.Tests/Middleware/VersionHeaderMiddlewareTests.cs delete mode 100644 Binner/Tests/Binner.Web.Tests/UnitTest1.cs diff --git a/Binner/Binner.Testing/Binner.Testing.csproj b/Binner/Binner.Testing/Binner.Testing.csproj new file mode 100644 index 00000000..a82c5c2c --- /dev/null +++ b/Binner/Binner.Testing/Binner.Testing.csproj @@ -0,0 +1,24 @@ + + + + net8.0 + enable + enable + + false + true + + + + + + + + + + + + + + + diff --git a/Binner/Binner.Testing/GlobalUsings.cs b/Binner/Binner.Testing/GlobalUsings.cs new file mode 100644 index 00000000..cefced49 --- /dev/null +++ b/Binner/Binner.Testing/GlobalUsings.cs @@ -0,0 +1 @@ +global using NUnit.Framework; \ No newline at end of file diff --git a/Binner/Binner.Testing/InMemoryStorageProvider.cs b/Binner/Binner.Testing/InMemoryStorageProvider.cs new file mode 100644 index 00000000..9fbd788a --- /dev/null +++ b/Binner/Binner.Testing/InMemoryStorageProvider.cs @@ -0,0 +1,434 @@ +using Binner.Global.Common; +using Binner.Model; +using Binner.Model.Responses; +using Binner.Model.Swarm; +using System.Linq.Expressions; + +namespace Binner.Testing +{ + public class InMemoryStorageProvider : IStorageProvider + { + private readonly Dictionary _parts = new(); + private readonly Dictionary _projects = new(); + private readonly Dictionary _projectPartAssignments = new(); + private readonly Dictionary _projectPcbAssignments = new(); + private readonly Dictionary _partTypes = new(); + private readonly Dictionary _pcbs = new(); + private readonly Dictionary _storedFiles = new(); + private readonly Dictionary _pcbStoredFileAssignments = new(); + private readonly Dictionary _partSuppliers = new(); + private readonly Dictionary _users = new(); + + public InMemoryStorageProvider(bool createEmpty = false) + { + if (!createEmpty) + { + _parts.Add(1, new Part { PartNumber = "LM358", PartId = 1 }); + _projects.Add(1, new Project { Name = "Test Project", ProjectId = 1 }); + } + _partTypes.Add(1, new PartType { Name = "IC", PartTypeId = 1 }); + _partTypes.Add(2, new PartType { Name = "Resistor", PartTypeId = 2 }); + _partTypes.Add(3, new PartType { Name = "Capacitor", PartTypeId = 3 }); + _partTypes.Add(4, new PartType { Name = "Inductor", PartTypeId = 4 }); + } + + public async Task AddPartAsync(Part part, IUserContext? userContext) + { + part.UserId = userContext?.UserId; + var id = _parts.OrderByDescending(x => x.Key).Select(x => x.Key).FirstOrDefault() + 1; + part.PartId = id; + _parts.Add(id, part); + return part; + } + + public async Task AddPartSupplierAsync(PartSupplier partSupplier, IUserContext? userContext) + { + partSupplier.UserId = userContext?.UserId; + var id = _partSuppliers.OrderByDescending(x => x.Key).Select(x => x.Key).FirstOrDefault() + 1; + partSupplier.PartSupplierId = id; + _partSuppliers.Add(id, partSupplier); + return partSupplier; + } + + public async Task AddPcbAsync(Pcb pcb, IUserContext? userContext) + { + pcb.UserId = userContext?.UserId; + var id = _pcbs.OrderByDescending(x => x.Key).Select(x => x.Key).FirstOrDefault() + 1; + pcb.PcbId = id; + _pcbs.Add(id, pcb); + return pcb; + } + + public async Task AddPcbStoredFileAssignmentAsync(PcbStoredFileAssignment assignment, IUserContext? userContext) + { + assignment.UserId = userContext?.UserId ?? 0; + var id = _pcbStoredFileAssignments.OrderByDescending(x => x.Key).Select(x => x.Key).FirstOrDefault() + 1; + assignment.PcbStoredFileAssignmentId = id; + _pcbStoredFileAssignments.Add(id, assignment); + return assignment; + } + + public async Task AddProjectAsync(Project project, IUserContext? userContext) + { + project.UserId = userContext?.UserId; + var id = _projects.OrderByDescending(x => x.Key).Select(x => x.Key).FirstOrDefault() + 1; + project.ProjectId = id; + _projects.Add(id, project); + return project; + } + + public async Task AddProjectPartAssignmentAsync(ProjectPartAssignment assignment, IUserContext? userContext) + { + assignment.UserId = userContext?.UserId ?? 0; + var id = _projectPartAssignments.OrderByDescending(x => x.Key).Select(x => x.Key).FirstOrDefault() + 1; + assignment.ProjectPartAssignmentId = id; + _projectPartAssignments.Add(id, assignment); + return assignment; + } + + public async Task AddProjectPcbAssignmentAsync(ProjectPcbAssignment assignment, IUserContext? userContext) + { + assignment.UserId = userContext?.UserId ?? 0; + var id = _projectPcbAssignments.OrderByDescending(x => x.Key).Select(x => x.Key).FirstOrDefault() + 1; + assignment.ProjectPcbAssignmentId = id; + _projectPcbAssignments.Add(id, assignment); + return assignment; + } + + public async Task AddStoredFileAsync(StoredFile storedFile, IUserContext? userContext) + { + storedFile.UserId = userContext?.UserId ?? 0; + var id = _storedFiles.OrderByDescending(x => x.Key).Select(x => x.Key).FirstOrDefault() + 1; + storedFile.StoredFileId = id; + _storedFiles.Add(id, storedFile); + return storedFile; + } + + public Task CreateOAuthRequestAsync(OAuthAuthorization authRequest, IUserContext? userContext) + { + throw new NotImplementedException(); + } + + public Task DeletePartAsync(Part part, IUserContext? userContext) + { + throw new NotImplementedException(); + } + + public Task DeletePartSupplierAsync(PartSupplier partSupplier, IUserContext? userContext) + { + throw new NotImplementedException(); + } + + public Task DeletePartTypeAsync(PartType partType, IUserContext? userContext) + { + throw new NotImplementedException(); + } + + public Task DeletePcbAsync(Pcb pcb, IUserContext? userContext) + { + throw new NotImplementedException(); + } + + public Task DeleteProjectAsync(Project project, IUserContext? userContext) + { + throw new NotImplementedException(); + } + + public Task DeleteStoredFileAsync(StoredFile storedFile, IUserContext? userContext) + { + throw new NotImplementedException(); + } + + public Task>> FindPartsAsync(string keywords, IUserContext? userContext) + { + throw new NotImplementedException(); + } + + public async Task GetDatabaseAsync(IUserContext? userContext) + { + return new LegacyBinnerDb + { + Parts = _parts.Values, + Pcbs = _pcbs.Values, + PartTypes = _partTypes.Values, + ProjectPartAssignments = _projectPartAssignments.Values, + PartSuppliers = _partSuppliers.Values, + PcbStoredFileAssignments = _pcbStoredFileAssignments.Values, + ProjectPcbAssignments = _projectPcbAssignments.Values, + Projects = _projects.Values, + StoredFiles = _storedFiles.Values, + Count = _parts.Count, + }; + } + + public Task> GetLowStockAsync(PaginatedRequest request, IUserContext? userContext) + { + throw new NotImplementedException(); + } + + public Task GetOAuthCredentialAsync(string providerName, IUserContext? userContext) + { + throw new NotImplementedException(); + } + + public Task GetOAuthRequestAsync(Guid requestId, IUserContext? userContext) + { + throw new NotImplementedException(); + } + + public async Task GetOrCreatePartTypeAsync(PartType partType, IUserContext? userContext) + { + if (_partTypes.Where(x => x.Value.Name == partType.Name).Any()) + return _partTypes.Where(x => x.Value.Name == partType.Name).Select(x => x.Value).First(); + var id = _partTypes.OrderByDescending(x => x.Key).Select(x => x.Key).FirstOrDefault() + 1; + partType.PartTypeId = id; + _partTypes.Add(id, partType); + return partType; + } + + public Task> GetPartAssignmentsAsync(long partId, IUserContext? userContext) + { + throw new NotImplementedException(); + } + + public async Task GetPartAsync(long partId, IUserContext? userContext) + { + return _parts.Where(x => x.Key == partId).Select(x => x.Value).FirstOrDefault(); + } + + public async Task GetPartAsync(string partNumber, IUserContext? userContext) + { + return _parts.Where(x => x.Value.PartNumber == partNumber).Select(x => x.Value).FirstOrDefault(); + } + + public async Task> GetPartsAsync(PaginatedRequest request, IUserContext? userContext) + { + //return _parts.Select(x => x.Value).ToList(); + throw new NotImplementedException(); + } + + public async Task> GetPartsAsync(Expression> predicate, IUserContext? userContext) + { + return _parts.Select(x => x.Value).ToList(); + } + + public async Task GetPartsCountAsync(IUserContext? userContext) + { + return _parts.Count(); + } + + public Task GetPartSupplierAsync(long partSupplierId, IUserContext? userContext) + { + throw new NotImplementedException(); + } + + public Task> GetPartSuppliersAsync(long partId, IUserContext? userContext) + { + throw new NotImplementedException(); + } + + public Task GetPartsValueAsync(IUserContext? userContext) + { + throw new NotImplementedException(); + } + + public async Task GetPartTypeAsync(long partTypeId, IUserContext? userContext) + { + return _partTypes.Where(x => x.Key == partTypeId).Select(x => x.Value).FirstOrDefault(); + } + + public async Task> GetPartTypesAsync(IUserContext? userContext) + { + return _partTypes.Select(x => x.Value).ToList(); + } + + public async Task GetPcbAsync(long pcbId, IUserContext? userContext) + { + return _pcbs.Where(x => x.Key == pcbId).Select(x => x.Value).FirstOrDefault(); + } + + public async Task> GetPcbsAsync(long projectId, IUserContext? userContext) + { + throw new NotImplementedException(); + } + + public Task GetPcbStoredFileAssignmentAsync(long pcbStoredFileAssignmentId, IUserContext? userContext) + { + throw new NotImplementedException(); + } + + public Task> GetPcbStoredFileAssignmentsAsync(long pcbId, IUserContext? userContext) + { + throw new NotImplementedException(); + } + + public async Task GetProjectAsync(long projectId, IUserContext? userContext) + { + return _projects.Where(x => x.Key == projectId).Select(x => x.Value).FirstOrDefault(); + } + + public async Task GetProjectAsync(string projectName, IUserContext? userContext) + { + return _projects.Where(x => x.Value.Name == projectName).Select(x => x.Value).FirstOrDefault(); + } + + public Task GetProjectPartAssignmentAsync(long projectPartAssignmentId, IUserContext? userContext) + { + throw new NotImplementedException(); + } + + public Task GetProjectPartAssignmentAsync(long projectId, long partId, IUserContext? userContext) + { + throw new NotImplementedException(); + } + + public Task GetProjectPartAssignmentAsync(long projectId, string partName, IUserContext? userContext) + { + throw new NotImplementedException(); + } + + public Task> GetProjectPartAssignmentsAsync(long projectId, IUserContext? userContext) + { + throw new NotImplementedException(); + } + + public Task> GetProjectPartAssignmentsAsync(long projectId, PaginatedRequest request, IUserContext? userContext) + { + throw new NotImplementedException(); + } + + public Task GetProjectPcbAssignmentAsync(long projectPcbAssignmentId, IUserContext? userContext) + { + throw new NotImplementedException(); + } + + public Task> GetProjectPcbAssignmentsAsync(long projectId, IUserContext? userContext) + { + throw new NotImplementedException(); + } + + public Task> GetProjectsAsync(PaginatedRequest request, IUserContext? userContext) + { + throw new NotImplementedException(); + } + + public Task GetStoredFileAsync(long storedFileId, IUserContext? userContext) + { + throw new NotImplementedException(); + } + + public Task GetStoredFileAsync(string filename, IUserContext? userContext) + { + throw new NotImplementedException(); + } + + public Task> GetStoredFilesAsync(long partId, StoredFileType? fileType, IUserContext? userContext) + { + throw new NotImplementedException(); + } + + public Task> GetStoredFilesAsync(PaginatedRequest request, IUserContext? userContext) + { + throw new NotImplementedException(); + } + + public Task GetUniquePartsCountAsync(IUserContext? userContext) + { + throw new NotImplementedException(); + } + + public Task RemoveOAuthCredentialAsync(string providerName, IUserContext? userContext) + { + throw new NotImplementedException(); + } + + public Task RemovePcbStoredFileAssignmentAsync(PcbStoredFileAssignment assignment, IUserContext? userContext) + { + throw new NotImplementedException(); + } + + public Task RemoveProjectPartAssignmentAsync(ProjectPartAssignment assignment, IUserContext? userContext) + { + throw new NotImplementedException(); + } + + public Task RemoveProjectPcbAssignmentAsync(ProjectPcbAssignment assignment, IUserContext? userContext) + { + throw new NotImplementedException(); + } + + public Task SaveOAuthCredentialAsync(OAuthCredential credential, IUserContext? userContext) + { + throw new NotImplementedException(); + } + + public Task TestConnectionAsync() + { + throw new NotImplementedException(); + } + + public Task UpdateOAuthRequestAsync(OAuthAuthorization authRequest, IUserContext? userContext) + { + throw new NotImplementedException(); + } + + public Task UpdatePartAsync(Part part, IUserContext? userContext) + { + throw new NotImplementedException(); + } + + public Task UpdatePartSupplierAsync(PartSupplier partSupplier, IUserContext? userContext) + { + throw new NotImplementedException(); + } + + public Task UpdatePartTypeAsync(PartType partType, IUserContext? userContext) + { + throw new NotImplementedException(); + } + + public Task UpdatePcbAsync(Pcb pcb, IUserContext? userContext) + { + throw new NotImplementedException(); + } + + public Task UpdatePcbStoredFileAssignmentAsync(PcbStoredFileAssignment assignment, IUserContext? userContext) + { + throw new NotImplementedException(); + } + + public Task UpdateProjectAsync(Project project, IUserContext? userContext) + { + throw new NotImplementedException(); + } + + public Task UpdateProjectPartAssignmentAsync(ProjectPartAssignment assignment, IUserContext? userContext) + { + throw new NotImplementedException(); + } + + public Task UpdateProjectPcbAssignmentAsync(ProjectPcbAssignment assignment, IUserContext? userContext) + { + throw new NotImplementedException(); + } + + public Task UpdateStoredFileAsync(StoredFile storedFile, IUserContext? userContext) + { + throw new NotImplementedException(); + } + + public void Dispose() + { + _parts.Clear(); + _projects.Clear(); + _projectPartAssignments.Clear(); + _projectPcbAssignments.Clear(); + _partTypes.Clear(); + _pcbs.Clear(); + _storedFiles.Clear(); + _pcbStoredFileAssignments.Clear(); + _partSuppliers.Clear(); + _users.Clear(); + } + } +} diff --git a/Binner/Binner.sln b/Binner/Binner.sln index 481147d9..6cefc7a5 100644 --- a/Binner/Binner.sln +++ b/Binner/Binner.sln @@ -49,6 +49,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Binner.Global.Common", "Lib EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Barcoder.Renderer.Image", "External\barcoder\Barcoder.Renderer.Image\Barcoder.Renderer.Image.csproj", "{0037D5E6-5FE2-4989-8668-266DB89DBAF2}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Binner.Testing", "Binner.Testing\Binner.Testing.csproj", "{958AE9B3-B6AD-4FA7-A463-0AD41D8B3665}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -127,6 +129,10 @@ Global {0037D5E6-5FE2-4989-8668-266DB89DBAF2}.Debug|Any CPU.Build.0 = Debug|Any CPU {0037D5E6-5FE2-4989-8668-266DB89DBAF2}.Release|Any CPU.ActiveCfg = Release|Any CPU {0037D5E6-5FE2-4989-8668-266DB89DBAF2}.Release|Any CPU.Build.0 = Release|Any CPU + {958AE9B3-B6AD-4FA7-A463-0AD41D8B3665}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {958AE9B3-B6AD-4FA7-A463-0AD41D8B3665}.Debug|Any CPU.Build.0 = Debug|Any CPU + {958AE9B3-B6AD-4FA7-A463-0AD41D8B3665}.Release|Any CPU.ActiveCfg = Release|Any CPU + {958AE9B3-B6AD-4FA7-A463-0AD41D8B3665}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -150,6 +156,7 @@ Global {DEEF8BAC-16E2-4FD3-ACDF-8E464189A3B1} = {2D9C4743-1B04-41B1-A12F-4CCF6F5BAB3E} {12E4F3B4-00DB-4FB8-B326-EE81A2FFE64B} = {2D9C4743-1B04-41B1-A12F-4CCF6F5BAB3E} {0037D5E6-5FE2-4989-8668-266DB89DBAF2} = {5E8D1F9D-68C0-444A-B04F-5AAC822D684E} + {958AE9B3-B6AD-4FA7-A463-0AD41D8B3665} = {3FF195C3-1F60-48E5-9EAB-1C4AAC791ABA} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {413F8D19-37B6-4A57-858F-DD8AFF2DAE96} diff --git a/Binner/Library/Binner.Common/Extensions/IPAddressExtensions.cs b/Binner/Library/Binner.Common/Extensions/IPAddressExtensions.cs index 4e46d5f4..525e3350 100644 --- a/Binner/Library/Binner.Common/Extensions/IPAddressExtensions.cs +++ b/Binner/Library/Binner.Common/Extensions/IPAddressExtensions.cs @@ -5,6 +5,40 @@ namespace Binner.Common.Extensions { public static class IpAddressExtensions { + /// + /// Get an IPAddress as 32 bit integer + /// + /// + /// + public static int ToInt(this IPAddress ipAddress) + { + try + { + return IPAddress.NetworkToHostOrder(BitConverter.ToInt32(ipAddress.GetAddressBytes(), 0)); + } + catch (Exception) + { + return 0; + } + } + + /// + /// Get an IPAddress as 32 bit integer + /// + /// + /// + public static uint ToUInt(this IPAddress ipAddress) + { + try + { + return (uint)ToInt(ipAddress); + } + catch (Exception) + { + return 0; + } + } + /// /// Get an IPAddress as 64 bit integer /// @@ -14,8 +48,24 @@ public static long ToLong(this IPAddress ipAddress) { try { - var ipLong = BitConverter.ToInt32(ipAddress.GetAddressBytes(), 0); - return IPAddress.NetworkToHostOrder(ipLong); + return IPAddress.NetworkToHostOrder(BitConverter.ToInt64(ipAddress.GetAddressBytes(), 0)); + } + catch (Exception) + { + return 0; + } + } + + /// + /// Get an IPAddress as 64 bit integer + /// + /// + /// + public static ulong ToULong(this IPAddress ipAddress) + { + try + { + return (ulong)ToLong(ipAddress); } catch (Exception) { @@ -32,7 +82,58 @@ public static IPAddress FromLong(long ipAddress) => ipAddress.ToIpAddress(); /// - /// Get an IPAddress from a 64 bit integer + /// Get an IPAddress from a 32 bit integer + /// + /// + /// + public static IPAddress FromInt(int ipAddress) + { + try + { + return new IPAddress(IPAddress.NetworkToHostOrder(ipAddress)); + } + catch (Exception) + { + } + return IPAddress.None; + } + + /// + /// Get an IPAddress from a 32 bit integer + /// + /// + /// + public static IPAddress ToIpAddress(this int ipAddress) + { + try + { + return new IPAddress(IPAddress.NetworkToHostOrder(ipAddress)); + } + catch (Exception) + { + } + return IPAddress.None; + } + + /// + /// Get an IPAddress from a 32 bit integer + /// + /// + /// + public static IPAddress ToIpAddress(this uint ipAddress) + { + try + { + return new IPAddress(IPAddress.NetworkToHostOrder((int)ipAddress)); + } + catch (Exception) + { + } + return IPAddress.None; + } + + /// + /// Get an IPAddress from a 32 bit integer /// /// /// @@ -40,9 +141,24 @@ public static IPAddress ToIpAddress(this long ipAddress) { try { - var ipAddressStr = ipAddress.ToString(); - if (IPAddress.TryParse(ipAddressStr, out var ip)) - return ip; + return new IPAddress(IPAddress.NetworkToHostOrder(ipAddress)); + } + catch (Exception) + { + } + return IPAddress.None; + } + + /// + /// Get an IPAddress from a 64 bit integer + /// + /// + /// + public static IPAddress ToIpAddress(this ulong ipAddress) + { + try + { + return new IPAddress(IPAddress.NetworkToHostOrder((long)ipAddress)); } catch (Exception) { diff --git a/Binner/Library/Binner.Common/IO/CsvDataImporter.cs b/Binner/Library/Binner.Common/IO/CsvDataImporter.cs index e9bb5167..bd78d579 100644 --- a/Binner/Library/Binner.Common/IO/CsvDataImporter.cs +++ b/Binner/Library/Binner.Common/IO/CsvDataImporter.cs @@ -62,7 +62,7 @@ public async Task ImportAsync(IEnumerable files, IUser private string? GetValueFromHeader(string[] rowData, Header header, string name) { - var headerIndex = header.GetHeaderIndex("Description"); + var headerIndex = header.GetHeaderIndex(name); if (headerIndex >= 0) return rowData[headerIndex]; return null; diff --git a/Binner/Library/Binner.Model/IBinnerDb.cs b/Binner/Library/Binner.Model/IBinnerDb.cs index 38d17ee7..fd675969 100644 --- a/Binner/Library/Binner.Model/IBinnerDb.cs +++ b/Binner/Library/Binner.Model/IBinnerDb.cs @@ -1,6 +1,4 @@ -using System.Collections.Generic; - -namespace Binner.Model +namespace Binner.Model { public interface IBinnerDb { diff --git a/Binner/Library/Binner.Model/StoredFile.cs b/Binner/Library/Binner.Model/StoredFile.cs index e000cd85..9a95e159 100644 --- a/Binner/Library/Binner.Model/StoredFile.cs +++ b/Binner/Library/Binner.Model/StoredFile.cs @@ -37,6 +37,11 @@ public class StoredFile /// public int Crc32 { get; set; } + /// + /// Optional user id to associate + /// + public int UserId { get; set; } + /// /// Creation date /// diff --git a/Binner/Tests/Binner.Common.Tests/Binner.Common.Tests.csproj b/Binner/Tests/Binner.Common.Tests/Binner.Common.Tests.csproj index 94b281db..7d30424f 100644 --- a/Binner/Tests/Binner.Common.Tests/Binner.Common.Tests.csproj +++ b/Binner/Tests/Binner.Common.Tests/Binner.Common.Tests.csproj @@ -1,4 +1,4 @@ - + net8.0 @@ -9,14 +9,37 @@ false + + + + + + + + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + - - - + + + + diff --git a/Binner/Tests/Binner.Common.Tests/IO/BinnerParts.xlsx b/Binner/Tests/Binner.Common.Tests/IO/BinnerParts.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..2b2c135939abe09d40318835dbe0c8d4bd1d3242 GIT binary patch literal 20275 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 AJOBUy literal 0 HcmV?d00001 diff --git a/Binner/Tests/Binner.Common.Tests/IO/CsvDataImporterTests.cs b/Binner/Tests/Binner.Common.Tests/IO/CsvDataImporterTests.cs new file mode 100644 index 00000000..6827183c --- /dev/null +++ b/Binner/Tests/Binner.Common.Tests/IO/CsvDataImporterTests.cs @@ -0,0 +1,218 @@ +using Binner.Common.IO; +using Binner.Global.Common; +using Binner.Testing; +using NUnit.Framework; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; + +namespace Binner.Common.Tests.IO +{ + + [TestFixture] + public class CsvDataImporterTests + { + [Test] + public async Task ShouldImportCsvAsync() + { + using var storageProvider = new InMemoryStorageProvider(true); + var importer = new CsvDataImporter(storageProvider); + var userContext = new UserContext { UserId = 99 }; + + var files = new List(); + using var stream = new MemoryStream(); + using var writer = new StreamWriter(stream); + writer.WriteLine(@$"#ProjectId, Name, Description, Location, Color, DateCreatedUtc, DateModifiedUtc"); + writer.WriteLine($@"1, 'Test Project 1', 'test description', 'location', 1, '2022-01-01 00:00:00', '2022-01-01 00:00:00'"); + writer.Flush(); + stream.Position = 0; + files.Add(new UploadFile("Projects.csv", stream)); + + var result = await importer.ImportAsync(files, userContext); + + var db = await storageProvider.GetDatabaseAsync(userContext); + Assert.That(result.Success, Is.True); + Assert.That(result.TotalRowsImported, Is.EqualTo(1)); + Assert.That(result.RowsImportedByTable["Projects"], Is.EqualTo(1)); + Assert.That(db.Projects.Count, Is.EqualTo(1)); + Assert.That(db.Projects.First().UserId, Is.EqualTo(99)); + } + + [Test] + public async Task ShouldImportQuotedDelimiterCsvAsync() + { + using var storageProvider = new InMemoryStorageProvider(true); + var importer = new CsvDataImporter(storageProvider); + var userContext = new UserContext { UserId = 99 }; + + var files = new List(); + using var stream = new MemoryStream(); + using var writer = new StreamWriter(stream); + writer.WriteLine(@$"#ProjectId, Name, Description, Location, Color, DateCreatedUtc, DateModifiedUtc"); + writer.WriteLine($@"1, 'Test Project 1', 'test, description', 'location', 1, '2022-01-01 00:00:00', '2022-01-01 00:00:00'"); + writer.Flush(); + stream.Position = 0; + files.Add(new UploadFile("Projects.csv", stream)); + + var result = await importer.ImportAsync(files, userContext); + + var db = await storageProvider.GetDatabaseAsync(userContext); + Assert.That(result.Success, Is.True); + Assert.That(result.TotalRowsImported, Is.EqualTo(1)); + Assert.That(result.RowsImportedByTable["Projects"], Is.EqualTo(1)); + Assert.That(db.Projects.Count, Is.EqualTo(1)); + Assert.That(db.Projects.First().UserId, Is.EqualTo(99)); + } + + [Test] + public async Task ShouldIgnoreUserIdAsync() + { + using var storageProvider = new InMemoryStorageProvider(true); + var importer = new CsvDataImporter(storageProvider); + var userContext = new UserContext { UserId = 99 }; + + using var stream = new MemoryStream(); + using var writer = new StreamWriter(stream); + writer.WriteLine(@$"#ProjectId, Name, Description, Location, Color, DateCreatedUtc, DateModifiedUtc, UserId"); + writer.WriteLine($@"1, 'Test Project 1', 'test description', 'location', 1, '2022-01-01 00:00:00', '2022-01-01 00:00:00',1"); + writer.Flush(); + stream.Position = 0; + + var result = await importer.ImportAsync("Projects.csv", stream, userContext); + + var db = await storageProvider.GetDatabaseAsync(userContext); + Assert.That(result.Success, Is.True); + Assert.That(result.TotalRowsImported, Is.EqualTo(1)); + Assert.That(result.RowsImportedByTable["Projects"], Is.EqualTo(1)); + Assert.That(db.Projects.Count, Is.EqualTo(1)); + Assert.That(db.Projects.First().UserId, Is.EqualTo(99)); + } + + [Test] + public async Task ShouldImportMultipleRowsAsync() + { + using var storageProvider = new InMemoryStorageProvider(true); + var importer = new CsvDataImporter(storageProvider); + var userContext = new UserContext { UserId = 99 }; + + using var stream = new MemoryStream(); + using var writer = new StreamWriter(stream); + writer.WriteLine(@$"#ProjectId, Name, Description, Location, Color, DateCreatedUtc, DateModifiedUtc"); + writer.WriteLine($@"1, 'Test Project 1', 'test description', 'location', 1, '2022-01-01 00:00:00', '2022-01-01 00:00:00'"); + writer.WriteLine($@"2, 'Test Project 2', 'test description', 'location', 1, '2022-01-01 00:00:00', '2022-01-01 00:00:00'"); + writer.WriteLine($@"3, 'Test Project 3', 'test description', 'location', 1, '2022-01-01 00:00:00', '2022-01-01 00:00:00'"); + writer.WriteLine($@"4, 'Test Project 4', 'test description', 'location', 1, '2022-01-01 00:00:00', '2022-01-01 00:00:00'"); + // try insert with quoted line-break content + writer.WriteLine($@"5, 'Test Project 5', 'test description\nsome extra data', 'location', 1, '2022-01-01 00:00:00', '2022-01-01 00:00:00'"); + writer.Flush(); + stream.Position = 0; + + var result = await importer.ImportAsync("Projects.csv", stream, userContext); + + var db = await storageProvider.GetDatabaseAsync(userContext); + Assert.That(result.Success, Is.True); + Assert.That(result.TotalRowsImported, Is.EqualTo(5)); + Assert.That(result.RowsImportedByTable["Projects"], Is.EqualTo(5)); + Assert.That(db.Projects.Count, Is.EqualTo(5)); + } + + [Test] + public async Task ShouldNotImportWithUnsupportedTableAsync() + { + using var storageProvider = new InMemoryStorageProvider(true); + var importer = new CsvDataImporter(storageProvider); + var userContext = new UserContext { UserId = 99 }; + + using var stream = new MemoryStream(); + using var writer = new StreamWriter(stream); + writer.WriteLine(@$"#ProjectId, Name, Description, Location, Color, DateCreatedUtc, DateModifiedUtc"); + writer.WriteLine($@"1, 'Test Project 1', 'test description', 'location', 1, '2022-01-01 00:00:00', '2022-01-01 00:00:00'"); + writer.Flush(); + stream.Position = 0; + + var result = await importer.ImportAsync("SomeTable.csv", stream, userContext); + + var db = await storageProvider.GetDatabaseAsync(userContext); + Assert.That(result.Success, Is.False); + Assert.That(result.TotalRowsImported, Is.EqualTo(0)); + Assert.That(result.Errors.Count, Is.EqualTo(1)); + } + + [Test] + public async Task ShouldSkipInvalidRowAsync() + { + using var storageProvider = new InMemoryStorageProvider(true); + var importer = new CsvDataImporter(storageProvider); + var userContext = new UserContext { UserId = 99 }; + + using var stream = new MemoryStream(); + using var writer = new StreamWriter(stream); + writer.WriteLine(@$"#ProjectId, Name, Description, Location, Color, DateCreatedUtc, DateModifiedUtc"); + writer.WriteLine($@"1, 'Test Project 1', 'test description', 'location', 1, '2022-01-01 00:00:00', '2022-01-01 00:00:00'"); + writer.WriteLine($@"2, 'Invalid Test', 'test description', 'location', 1"); + writer.WriteLine($@"3, 'Test Project 2', 'test description', 'location', 1, '2022-01-01 00:00:00', '2022-01-01 00:00:00'"); + writer.Flush(); + stream.Position = 0; + + var result = await importer.ImportAsync("Projects.csv", stream, userContext); + + var db = await storageProvider.GetDatabaseAsync(userContext); + Assert.That(result.Success, Is.True); + Assert.That(result.TotalRowsImported, Is.EqualTo(2)); + Assert.That(result.RowsImportedByTable["Projects"], Is.EqualTo(2)); + Assert.That(result.Warnings.Count, Is.EqualTo(1)); + Assert.That(db.Projects.Count, Is.EqualTo(2)); + } + + [Test] + public async Task ShouldImportUnquotedAsync() + { + using var storageProvider = new InMemoryStorageProvider(true); + var importer = new CsvDataImporter(storageProvider); + var userContext = new UserContext { UserId = 99 }; + + using var stream = new MemoryStream(); + using var writer = new StreamWriter(stream); + writer.WriteLine(@$"#ProjectId, Name, Description, Location, Color, DateCreatedUtc, DateModifiedUtc"); + writer.WriteLine($@"1, 'Test Project 1',test description, location, 1, '2022-01-01 00:00:00', '2022-01-01 00:00:00'"); + writer.WriteLine($@"2, 'Test Project 2', 'test description', 'location', 1, '2022-01-01 00:00:00', '2022-01-01 00:00:00'"); + writer.Flush(); + stream.Position = 0; + + var result = await importer.ImportAsync("Projects.csv", stream, userContext); + + var db = await storageProvider.GetDatabaseAsync(userContext); + Assert.That(result.Success, Is.True); + Assert.That(result.TotalRowsImported, Is.EqualTo(2)); + Assert.That(result.RowsImportedByTable["Projects"], Is.EqualTo(2)); + Assert.That(db.Projects.Count, Is.EqualTo(2)); + } + + [Test] + public async Task ShouldImportEncodedLineBreaksAsync() + { + using var storageProvider = new InMemoryStorageProvider(true); + var importer = new CsvDataImporter(storageProvider); + var userContext = new UserContext { UserId = 99 }; + + using var stream = new MemoryStream(); + using var writer = new StreamWriter(stream); + writer.WriteLine(@$"#ProjectId, Name, Description, Location, Color, DateCreatedUtc, DateModifiedUtc"); + writer.WriteLine($@"1, 'Test Project 1', 'test description +This is a test +another test', location, 1, '2022-01-01 00:00:00', '2022-01-01 00:00:00'"); + writer.WriteLine($@"2, 'Test Project 2', test description\r\nunquoted strings result in decoded line breaks, 'location', 1, '2022-01-01 00:00:00', '2022-01-01 00:00:00'"); + writer.Flush(); + stream.Position = 0; + + var result = await importer.ImportAsync("Projects.csv", stream, userContext); + + var db = await storageProvider.GetDatabaseAsync(userContext); + Assert.That(result.Success, Is.True); + Assert.That(result.TotalRowsImported, Is.EqualTo(2)); + Assert.That(result.RowsImportedByTable["Projects"], Is.EqualTo(2)); + Assert.That(db.Projects.Count, Is.EqualTo(2)); + } + } +} diff --git a/Binner/Tests/Binner.Common.Tests/IO/ExcelDataImporterTests.cs b/Binner/Tests/Binner.Common.Tests/IO/ExcelDataImporterTests.cs new file mode 100644 index 00000000..994007c2 --- /dev/null +++ b/Binner/Tests/Binner.Common.Tests/IO/ExcelDataImporterTests.cs @@ -0,0 +1,37 @@ +using Binner.Common.IO; +using Binner.Global.Common; +using Binner.Testing; +using NUnit.Framework; +using System.IO; +using System.Linq; +using System.Threading.Tasks; + +namespace Binner.Common.Tests.IO +{ + [TestFixture] + public class ExcelDataImporterTests + { + [Test] + public async Task ShouldImportExcelAsync() + { + using var storageProvider = new InMemoryStorageProvider(true); + var importer = new ExcelDataImporter(storageProvider); + var userContext = new UserContext { UserId = 99 }; + + using var stream = new FileStream(".\\IO\\BinnerParts.xlsx", FileMode.Open); + var result = await importer.ImportAsync("BinnerParts.xlsx", stream, userContext); + + var db = await storageProvider.GetDatabaseAsync(userContext); + Assert.That(result.Success, Is.True); + // data based on test set in BinnerParts.xlsx + Assert.That(result.TotalRowsImported, Is.EqualTo(204)); + Assert.That(result.RowsImportedByTable["Projects"], Is.EqualTo(2)); + Assert.That(result.RowsImportedByTable["Parts"], Is.EqualTo(1)); + Assert.That(result.RowsImportedByTable["PartTypes"], Is.EqualTo(201)); + Assert.That(db.Projects.Count, Is.EqualTo(2)); + Assert.That(db.Parts.Count, Is.EqualTo(1)); + Assert.That(db.PartTypes.Count, Is.EqualTo(205)); + Assert.That(db.Projects.First().UserId, Is.EqualTo(99)); + } + } +} diff --git a/Binner/Tests/Binner.Common.Tests/IO/IPAddressExtensionsTests.cs b/Binner/Tests/Binner.Common.Tests/IO/IPAddressExtensionsTests.cs new file mode 100644 index 00000000..cfb171c2 --- /dev/null +++ b/Binner/Tests/Binner.Common.Tests/IO/IPAddressExtensionsTests.cs @@ -0,0 +1,39 @@ +using Binner.Common.Extensions; +using NUnit.Framework; +using System.Net; + +namespace Binner.Common.Tests.IO +{ + [TestFixture] + public class IPAddressExtensionsTests + { + [TestCase("0.0.0.0", ExpectedResult = 0U, Description = "0.0.0.0")] + [TestCase("127.0.0.1", ExpectedResult = 2130706433U, Description = "127.0.0.1")] + [TestCase("192.168.1.55", ExpectedResult = 3232235831U, Description = "192.168.1.55")] + [TestCase("54.22.161.99", ExpectedResult = 907452771U, Description = "54.22.161.99")] + [TestCase("12.24.36.48", ExpectedResult = 202908720U, Description = "12.24.36.48")] + [TestCase("1.1.1.1", ExpectedResult = 16843009U, Description = "1.1.1.1")] + [TestCase("255.255.255.255", ExpectedResult = 4294967295U, Description = "255.255.255.255")] + [Test] + public uint ShouldIpToInt(string ipAddressStr) + { + var ipAddress = IPAddress.Parse(ipAddressStr); + var ip = ipAddress.ToUInt(); + return ip; + } + + [TestCase(0U, ExpectedResult = "0.0.0.0")] + [TestCase(2130706433U, ExpectedResult = "127.0.0.1")] + [TestCase(3232235831U, ExpectedResult = "192.168.1.55")] + [TestCase(907452771U, ExpectedResult = "54.22.161.99")] + [TestCase(202908720U, ExpectedResult = "12.24.36.48")] + [TestCase(16843009U, ExpectedResult = "1.1.1.1")] + [TestCase(4294967295U, ExpectedResult = "255.255.255.255")] + [Test] + public string ShouldIntToIp(uint ip) + { + var ipAddress = ip.ToIpAddress(); + return ipAddress.ToString(); + } + } +} diff --git a/Binner/Tests/Binner.Common.Tests/IO/PartTypes.csv b/Binner/Tests/Binner.Common.Tests/IO/PartTypes.csv new file mode 100644 index 00000000..c2b2edef --- /dev/null +++ b/Binner/Tests/Binner.Common.Tests/IO/PartTypes.csv @@ -0,0 +1,205 @@ +#PartTypeId,ParentPartTypeId,Name,DateCreatedUtc,UserId +12,0,"Cable",2022-04-10 02:26:28,0 +2,0,"Capacitor",2022-04-10 02:26:28,0 +13,0,"Connector",2022-04-10 02:26:28,0 +9,0,"Crystal",2022-04-10 02:26:28,0 +4,0,"Diode",2022-04-10 02:26:28,0 +16,0,"Evaluation",2022-04-10 02:26:28,0 +17,0,"Hardware",2022-04-10 02:26:28,0 +14,0,"IC",2022-04-10 02:26:28,0 +3,0,"Inductor",2022-04-10 02:26:28,0 +201,0,"Kit",2022-04-10 02:26:28,0 +5,0,"LED",2022-04-10 02:26:28,0 +15,0,"Module",2022-04-10 02:26:28,0 +18,0,"Other",2022-04-10 02:26:28,0 +7,0,"Relay",2022-04-10 02:26:28,0 +1,0,"Resistor",2022-04-10 02:26:28,0 +10,0,"Sensor",2022-04-10 02:26:28,0 +11,0,"Switch",2022-04-10 02:26:28,0 +8,0,"Transformer",2022-04-10 02:26:28,0 +6,0,"Transistor",2022-04-10 02:26:28,0 +202,2,"CapacitorKit",2022-04-10 02:26:28,0 +39,2,"CeramicCapacitor",2022-04-10 02:26:28,0 +40,2,"ElectrolyticCapacitor",2022-04-10 02:26:28,0 +41,2,"FilmCapacitor",2022-04-10 02:26:28,0 +42,2,"MicaCapacitor",2022-04-10 02:26:28,0 +43,2,"NonPolarizedCapacitor",2022-04-10 02:26:28,0 +45,2,"PaperCapacitor",2022-04-10 02:26:28,0 +199,2,"SafetyCapacitor",2022-04-10 02:26:28,0 +44,2,"SupercapacitorCapacitor",2022-04-10 02:26:28,0 +200,2,"TantalumCapacitor",2022-04-10 02:26:28,0 +46,2,"VariableCapacitor",2022-04-10 02:26:28,0 +74,4,"CrystalDiode",2022-04-10 02:26:28,0 +203,4,"DiodeKit",2022-04-10 02:26:28,0 +69,4,"GunnDiode",2022-04-10 02:26:28,0 +66,4,"LargeSignalDiode",2022-04-10 02:26:28,0 +68,4,"PeltierDiode",2022-04-10 02:26:28,0 +64,4,"Schottky",2022-04-10 02:26:28,0 +67,4,"Shockley",2022-04-10 02:26:28,0 +65,4,"SmallSignalDiode",2022-04-10 02:26:28,0 +71,4,"StepRecoveryDiode",2022-04-10 02:26:28,0 +73,4,"TransientVoltageSuppressionDiode",2022-04-10 02:26:28,0 +70,4,"TunnelDiode",2022-04-10 02:26:28,0 +72,4,"VaractorDiode",2022-04-10 02:26:28,0 +63,4,"Zener",2022-04-10 02:26:28,0 +124,16,"Alchitry",2022-04-10 02:26:28,0 +125,16,"Amica",2022-04-10 02:26:28,0 +119,16,"Arduino",2022-04-10 02:26:28,0 +141,16,"AsusTinker",2022-04-10 02:26:28,0 +132,16,"BasicEvaluation",2022-04-10 02:26:28,0 +120,16,"BeagleBoard",2022-04-10 02:26:28,0 +135,16,"LattePanda",2022-04-10 02:26:28,0 +123,16,"Launchpad",2022-04-10 02:26:28,0 +131,16,"MikroElektronika",2022-04-10 02:26:28,0 +121,16,"NVidiaJetson",2022-04-10 02:26:28,0 +134,16,"Odriod",2022-04-10 02:26:28,0 +142,16,"OtherEvaluation",2022-04-10 02:26:28,0 +126,16,"Particle",2022-04-10 02:26:28,0 +129,16,"Pic",2022-04-10 02:26:28,0 +133,16,"Pine",2022-04-10 02:26:28,0 +140,16,"PocketBeagle",2022-04-10 02:26:28,0 +128,16,"Qwiic",2022-04-10 02:26:28,0 +118,16,"RaspberryPi",2022-04-10 02:26:28,0 +138,16,"RockPi",2022-04-10 02:26:28,0 +136,16,"Seeeduino",2022-04-10 02:26:28,0 +137,16,"SiliconLabs",2022-04-10 02:26:28,0 +127,16,"Sparkfun",2022-04-10 02:26:28,0 +130,16,"STM32",2022-04-10 02:26:28,0 +122,16,"Teensy",2022-04-10 02:26:28,0 +139,16,"Udoo",2022-04-10 02:26:28,0 +143,17,"Adapter",2022-04-10 02:26:28,0 +150,17,"BallBearing",2022-04-10 02:26:28,0 +158,17,"Belt",2022-04-10 02:26:28,0 +151,17,"Bracket",2022-04-10 02:26:28,0 +149,17,"Coupler",2022-04-10 02:26:28,0 +192,17,"Enclosure",2022-04-10 02:26:28,0 +160,17,"Fan",2022-04-10 02:26:28,0 +148,17,"Gear",2022-04-10 02:26:28,0 +177,17,"Grommet",2022-04-10 02:26:28,0 +159,17,"Hub",2022-04-10 02:26:28,0 +157,17,"Mount",2022-04-10 02:26:28,0 +146,17,"Nut",2022-04-10 02:26:28,0 +155,17,"Plate",2022-04-10 02:26:28,0 +156,17,"RawMaterial",2022-04-10 02:26:28,0 +162,17,"Robotics",2022-04-10 02:26:28,0 +144,17,"Screw",2022-04-10 02:26:28,0 +152,17,"Shaft",2022-04-10 02:26:28,0 +153,17,"Spacer",2022-04-10 02:26:28,0 +176,17,"Spring",2022-04-10 02:26:28,0 +147,17,"Standoff",2022-04-10 02:26:28,0 +154,17,"Tube",2022-04-10 02:26:28,0 +145,17,"Washer",2022-04-10 02:26:28,0 +161,17,"Wheel",2022-04-10 02:26:28,0 +26,14,"ADC",2022-04-10 02:26:28,0 +20,14,"Amplifier",2022-04-10 02:26:28,0 +30,14,"AudioIc",2022-04-10 02:26:28,0 +25,14,"ClockIc",2022-04-10 02:26:28,0 +31,14,"ComparatorIc",2022-04-10 02:26:28,0 +32,14,"CounterIc",2022-04-10 02:26:28,0 +36,14,"DataAcquisitionIc",2022-04-10 02:26:28,0 +33,14,"DividerIc",2022-04-10 02:26:28,0 +37,14,"EmbeddedIc",2022-04-10 02:26:28,0 +28,14,"EnergyMeteringIc",2022-04-10 02:26:28,0 +163,14,"FlipFlopIc",2022-04-10 02:26:28,0 +35,14,"FPGA",2022-04-10 02:26:28,0 +23,14,"InterfaceIc",2022-04-10 02:26:28,0 +29,14,"LedDriverIc",2022-04-10 02:26:28,0 +22,14,"LogicIc",2022-04-10 02:26:28,0 +21,14,"MemoryIc",2022-04-10 02:26:28,0 +24,14,"Microcontroller",2022-04-10 02:26:28,0 +19,14,"OpAmp",2022-04-10 02:26:28,0 +34,14,"PMIC",2022-04-10 02:26:28,0 +38,14,"SpecializedIc",2022-04-10 02:26:28,0 +27,14,"VoltageRegulatorIc",2022-04-10 02:26:28,0 +164,3,"AdjustableInductor",2022-04-10 02:26:28,0 +55,3,"AirCoreInductor",2022-04-10 02:26:28,0 +60,3,"BobbinInductor",2022-04-10 02:26:28,0 +57,3,"FerriteCoreInductor",2022-04-10 02:26:28,0 +204,3,"InductorKit",2022-04-10 02:26:28,0 +56,3,"IronCoreInductor",2022-04-10 02:26:28,0 +58,3,"IronPowderInductor",2022-04-10 02:26:28,0 +59,3,"LaminatedCoreInductor",2022-04-10 02:26:28,0 +62,3,"MultiLayerCeramicInductor",2022-04-10 02:26:28,0 +61,3,"ToroidalInductor",2022-04-10 02:26:28,0 +115,15,"ArduinoShield",2022-04-10 02:26:28,0 +112,15,"CurrentVoltageModule",2022-04-10 02:26:28,0 +116,15,"EvaluationModule",2022-04-10 02:26:28,0 +113,15,"ExperimentModule",2022-04-10 02:26:28,0 +117,15,"OtherModule",2022-04-10 02:26:28,0 +114,15,"RaspberryPiShield",2022-04-10 02:26:28,0 +111,15,"WirelessModule",2022-04-10 02:26:28,0 +81,7,"ElectromagneticRelay",2022-04-10 02:26:28,0 +89,7,"HighVoltageRelay",2022-04-10 02:26:28,0 +82,7,"LatchingRelay",2022-04-10 02:26:28,0 +84,7,"ReedRelay",2022-04-10 02:26:28,0 +88,7,"RotaryRelay",2022-04-10 02:26:28,0 +87,7,"SequenceRelay",2022-04-10 02:26:28,0 +83,7,"SolidStateRelay",2022-04-10 02:26:28,0 +86,7,"ThermalRelay",2022-04-10 02:26:28,0 +85,7,"TimeRelay",2022-04-10 02:26:28,0 +47,1,"CarbonFilmResistor",2022-04-10 02:26:28,0 +193,1,"CeramicResistor",2022-04-10 02:26:28,0 +194,1,"CurrentSenseResistor",2022-04-10 02:26:28,0 +195,1,"HighFrequencyResistor",2022-04-10 02:26:28,0 +48,1,"MetalFilmResistor",2022-04-10 02:26:28,0 +196,1,"MetalFoilResistor",2022-04-10 02:26:28,0 +50,1,"MetalOxideResistor",2022-04-10 02:26:28,0 +51,1,"MetalStripResistor",2022-04-10 02:26:28,0 +198,1,"Potentiometer",2022-04-10 02:26:28,0 +52,1,"PowerResistor",2022-04-10 02:26:28,0 +53,1,"ResistorArray",2022-04-10 02:26:28,0 +197,1,"ResistorKit",2022-04-10 02:26:28,0 +54,1,"VariableResistor",2022-04-10 02:26:28,0 +49,1,"WirewoundResistor",2022-04-10 02:26:28,0 +185,10,"AccelerationSensor",2022-04-10 02:26:28,0 +184,10,"AirQualitySensor",2022-04-10 02:26:28,0 +179,10,"AudioSensor",2022-04-10 02:26:28,0 +107,10,"BiometricSensor",2022-04-10 02:26:28,0 +106,10,"CapacitiveSensor",2022-04-10 02:26:28,0 +167,10,"ColorSensor",2022-04-10 02:26:28,0 +99,10,"CurrentSensor",2022-04-10 02:26:28,0 +102,10,"DistanceSensor",2022-04-10 02:26:28,0 +108,10,"EnvironmentSensor",2022-04-10 02:26:28,0 +170,10,"FlowSensor",2022-04-10 02:26:28,0 +103,10,"ForceSensor",2022-04-10 02:26:28,0 +169,10,"GasSensor",2022-04-10 02:26:28,0 +187,10,"GyroscopeSensor",2022-04-10 02:26:28,0 +182,10,"HallEffectSensor",2022-04-10 02:26:28,0 +168,10,"HumiditySensor",2022-04-10 02:26:28,0 +98,10,"ImagingSensor",2022-04-10 02:26:28,0 +188,10,"InclineSensor",2022-04-10 02:26:28,0 +191,10,"InfraredSensor",2022-04-10 02:26:28,0 +97,10,"LightSensor",2022-04-10 02:26:28,0 +180,10,"LiquidSensor",2022-04-10 02:26:28,0 +101,10,"LoadSensor",2022-04-10 02:26:28,0 +181,10,"MagneticSensor",2022-04-10 02:26:28,0 +105,10,"MotionSensor",2022-04-10 02:26:28,0 +110,10,"OtherSensor",2022-04-10 02:26:28,0 +166,10,"Photodiodes",2022-04-10 02:26:28,0 +186,10,"PositionSensor",2022-04-10 02:26:28,0 +165,10,"PressureSensor",2022-04-10 02:26:28,0 +172,10,"ProximitySensor",2022-04-10 02:26:28,0 +109,10,"RadiationSensor",2022-04-10 02:26:28,0 +104,10,"RfSensor",2022-04-10 02:26:28,0 +96,10,"SensorAssembly",2022-04-10 02:26:28,0 +183,10,"SmokeSensor",2022-04-10 02:26:28,0 +189,10,"SpeedSensor",2022-04-10 02:26:28,0 +173,10,"TemperatureSensor",2022-04-10 02:26:28,0 +171,10,"TiltSensor",2022-04-10 02:26:28,0 +174,10,"TouchSensor",2022-04-10 02:26:28,0 +175,10,"UltrasonicSensor",2022-04-10 02:26:28,0 +190,10,"VibrationSensor",2022-04-10 02:26:28,0 +100,10,"VoltageSensor",2022-04-10 02:26:28,0 +95,8,"AudioTransformer",2022-04-10 02:26:28,0 +92,8,"IsolationTransformer",2022-04-10 02:26:28,0 +94,8,"RfTransformer",2022-04-10 02:26:28,0 +93,8,"SolidStateTransformer",2022-04-10 02:26:28,0 +90,8,"StepDownTransformer",2022-04-10 02:26:28,0 +91,8,"StepUpTransformer",2022-04-10 02:26:28,0 +178,6,"BJT",2022-04-10 02:26:28,0 +79,6,"DIAC",2022-04-10 02:26:28,0 +76,6,"IGBT",2022-04-10 02:26:28,0 +77,6,"JFET",2022-04-10 02:26:28,0 +75,6,"MOSFET",2022-04-10 02:26:28,0 +78,6,"SCR",2022-04-10 02:26:28,0 +80,6,"TRIAC",2022-04-10 02:26:28,0 diff --git a/Binner/Tests/Binner.Common.Tests/IO/Parts.csv b/Binner/Tests/Binner.Common.Tests/IO/Parts.csv new file mode 100644 index 00000000..a613dbe0 --- /dev/null +++ b/Binner/Tests/Binner.Common.Tests/IO/Parts.csv @@ -0,0 +1,2 @@ +#PartId,Quantity,LowStockThreshold,Cost,PartNumber,DigiKeyPartNumber,MouserPartNumber,Description,PartTypeId,MountingTypeId,PackageType,ProductUrl,ImageUrl,LowestCostSupplier,LowestCostSupplierUrl,ProjectId,Keywords,DatasheetUrl,Location,BinNumber,BinNumber2,Manufacturer,ManufacturerPartNumber,SwarmPartNumberManufacturerId,UserId,DateCreatedUtc +1,6,10,0.0875,"LM358","2156-LM358SNG-ON-ND","","General Purpose Amplifier 2 Circuit Differential 8-PDIP",14,1,"8-DIP","https://www.digikey.ca/en/products/detail/onsemi/LM358SNG/5404322","https://d2e86la87jxppk.cloudfront.net/50/b8/09/c8/e6/dd/40/21/ac89b5a73720_1.png","DigiKey","https://www.digikey.ca/en/products/detail/onsemi/LM358DG/1476852",0,"amplifier,lm358sng,general,purpose,2,circuit,0","https://d2e86la87jxppk.cloudfront.net/f3/da/be/59/e1/d0/42/87/0aaadaff6084.pdf","HOME","1","2","Onsemi","LM358SNG",0,1,2022-04-14 03:12:22 diff --git a/Binner/Tests/Binner.Common.Tests/IO/Projects.csv b/Binner/Tests/Binner.Common.Tests/IO/Projects.csv new file mode 100644 index 00000000..2a583f87 --- /dev/null +++ b/Binner/Tests/Binner.Common.Tests/IO/Projects.csv @@ -0,0 +1,2 @@ +#ProjectId,Name,Description,Location,Color,DateCreatedUtc,DateModifiedUtc,UserId +3,"Test Project 1","This is a test project","Vancouver",1,2022-06-02 00:59:50,2022-06-02 00:59:57,1 diff --git a/Binner/Tests/Binner.Common.Tests/IO/SqlDataImporterTests.cs b/Binner/Tests/Binner.Common.Tests/IO/SqlDataImporterTests.cs new file mode 100644 index 00000000..a3df1ff5 --- /dev/null +++ b/Binner/Tests/Binner.Common.Tests/IO/SqlDataImporterTests.cs @@ -0,0 +1,251 @@ +using Binner.Common.IO; +using Binner.Global.Common; +using Binner.Testing; +using NUnit.Framework; +using System.IO; +using System.Linq; +using System.Threading.Tasks; + +namespace Binner.Common.Tests.IO +{ + [TestFixture] + public class SqlDataImporterTests + { + [Test] + public async Task ShouldImportSqlAsync() + { + using var storageProvider = new InMemoryStorageProvider(true); + var importer = new SqlDataImporter(storageProvider); + var userContext = new UserContext { UserId = 99 }; + + using var stream = new MemoryStream(); + using var writer = new StreamWriter(stream); + writer.Write(@$"INSERT INTO Projects (ProjectId, Name, Description, Location, Color, DateCreatedUtc, DateModifiedUtc) VALUES (1, 'Test Project 1', 'test description', 'location', 1, '2022-01-01 00:00:00', '2022-01-01 00:00:00');"); + writer.Flush(); + stream.Position = 0; + + var result = await importer.ImportAsync("testfile.sql", stream, userContext); + + var db = await storageProvider.GetDatabaseAsync(userContext); + Assert.That(result.Success, Is.True); + Assert.That(result.TotalRowsImported, Is.EqualTo(1)); + Assert.That(result.RowsImportedByTable["Projects"], Is.EqualTo(1)); + Assert.That(db.Projects.Count, Is.EqualTo(1)); + Assert.That(db.Projects.First().UserId, Is.EqualTo(99)); + } + + [Test] + public async Task ShouldImportQuotedDelimiterSqlAsync() + { + using var storageProvider = new InMemoryStorageProvider(true); + var importer = new SqlDataImporter(storageProvider); + var userContext = new UserContext { UserId = 99 }; + + using var stream = new MemoryStream(); + using var writer = new StreamWriter(stream); + writer.Write(@$"INSERT INTO Projects (ProjectId, Name, Description, Location, Color, DateCreatedUtc, DateModifiedUtc) VALUES (1, 'Test Project 1', 'test, description', 'location', 1, '2022-01-01 00:00:00', '2022-01-01 00:00:00');"); + writer.Flush(); + stream.Position = 0; + + var result = await importer.ImportAsync("testfile.sql", stream, userContext); + + var db = await storageProvider.GetDatabaseAsync(userContext); + Assert.That(result.Success, Is.True); + Assert.That(result.TotalRowsImported, Is.EqualTo(1)); + Assert.That(result.RowsImportedByTable["Projects"], Is.EqualTo(1)); + Assert.That(db.Projects.Count, Is.EqualTo(1)); + Assert.That(db.Projects.First().UserId, Is.EqualTo(99)); + } + + [Test] + public async Task ShouldIgnoreUserIdAsync() + { + using var storageProvider = new InMemoryStorageProvider(true); + var importer = new SqlDataImporter(storageProvider); + var userContext = new UserContext { UserId = 99 }; + + using var stream = new MemoryStream(); + using var writer = new StreamWriter(stream); + writer.Write(@$"INSERT INTO Projects (ProjectId, UserId, Name, Description, Location, Color, DateCreatedUtc, DateModifiedUtc) VALUES (1, 1, 'Test Project 1', 'test description', 'location', 1, '2022-01-01 00:00:00', '2022-01-01 00:00:00');"); + writer.Flush(); + stream.Position = 0; + + var result = await importer.ImportAsync("testfile.sql", stream, userContext); + + var db = await storageProvider.GetDatabaseAsync(userContext); + Assert.That(result.Success, Is.True); + Assert.That(result.TotalRowsImported, Is.EqualTo(1)); + Assert.That(result.RowsImportedByTable["Projects"], Is.EqualTo(1)); + Assert.That(db.Projects.Count, Is.EqualTo(1)); + Assert.That(db.Projects.First().UserId, Is.EqualTo(99)); + } + + [Test] + public async Task ShouldImportSqlQuotedAsync() + { + using var storageProvider = new InMemoryStorageProvider(true); + var importer = new SqlDataImporter(storageProvider); + var userContext = new UserContext { UserId = 99 }; + + using var stream = new MemoryStream(); + using var writer = new StreamWriter(stream); + writer.Write(@$"INSERT INTO ""Projects"" (""ProjectId"", Name, Description, Location, Color, DateCreatedUtc, DateModifiedUtc) VALUES (1, 'Test Project 1', 'test description', 'location', 1, '2022-01-01 00:00:00', '2022-01-01 00:00:00');"); + writer.Flush(); + stream.Position = 0; + + var result = await importer.ImportAsync("testfile.sql", stream, userContext); + + var db = await storageProvider.GetDatabaseAsync(userContext); + Assert.That(result.Success, Is.True); + Assert.That(result.TotalRowsImported, Is.EqualTo(1)); + Assert.That(result.RowsImportedByTable["Projects"], Is.EqualTo(1)); + Assert.That(db.Projects.Count, Is.EqualTo(1)); + Assert.That(db.Projects.First().UserId, Is.EqualTo(99)); + } + + [Test] + public async Task ShouldImportMultipleRowsAsync() + { + using var storageProvider = new InMemoryStorageProvider(true); + var importer = new SqlDataImporter(storageProvider); + var userContext = new UserContext { UserId = 99 }; + + using var stream = new MemoryStream(); + using var writer = new StreamWriter(stream); + // try insert's with no line-break + writer.Write(@$"INSERT INTO PartTypes (ParentPartTypeId, Name, DateCreatedUtc) VALUES (1, 'Custom Type 1', '2022-01-01 00:00:00');"); + writer.Write(@$"INSERT INTO PartTypes (ParentPartTypeId, Name, DateCreatedUtc) VALUES (null, 'Custom Type 2', '2022-01-01 00:00:00');"); + // insert with line-breaks + writer.WriteLine(@$"INSERT INTO Projects (Name, Description, Location, Color, DateCreatedUtc, DateModifiedUtc) VALUES ('Test Project 1', 'test description', 'location', 1, '2022-01-01 00:00:00', '2022-01-01 00:00:00');"); + writer.WriteLine(@$"INSERT INTO Projects (Name, Description, Location, Color, DateCreatedUtc, DateModifiedUtc) VALUES ('Test Project 2', 'test description', 'location', 1, '2022-01-01 00:00:00', '2022-01-01 00:00:00');"); + writer.WriteLine(@$"INSERT INTO Projects (Name, Description, Location, Color, DateCreatedUtc, DateModifiedUtc) VALUES ('Test Project 3', 'test description', 'location', 1, '2022-01-01 00:00:00', '2022-01-01 00:00:00');"); + writer.WriteLine(@$"INSERT INTO Projects (Name, Description, Location, Color, DateCreatedUtc, DateModifiedUtc) VALUES ('Test Project 4', 'test description', 'location', 1, '2022-01-01 00:00:00', '2022-01-01 00:00:00');"); + // try insert with quoted line-break content + writer.WriteLine(@$"INSERT INTO Projects (Name, Description, Location, Color, DateCreatedUtc, DateModifiedUtc) VALUES ('Test Project 5', 'test description +more description +more text', 'location', 1, '2022-01-01 00:00:00', '2022-01-01 00:00:00');"); + writer.Flush(); + stream.Position = 0; + + var result = await importer.ImportAsync("testfile.sql", stream, userContext); + + var db = await storageProvider.GetDatabaseAsync(userContext); + Assert.That(result.Success, Is.True); + Assert.That(result.TotalRowsImported, Is.EqualTo(7)); + Assert.That(result.RowsImportedByTable["Projects"], Is.EqualTo(5)); + Assert.That(result.RowsImportedByTable["PartTypes"], Is.EqualTo(2)); + Assert.That(db.Projects.Count, Is.EqualTo(5)); + Assert.That(db.PartTypes.Count, Is.EqualTo(6)); + } + + [Test] + public async Task ShouldImportSqlWithSchemaAsync() + { + using var storageProvider = new InMemoryStorageProvider(true); + var importer = new SqlDataImporter(storageProvider); + var userContext = new UserContext { UserId = 99 }; + + using var stream = new MemoryStream(); + using var writer = new StreamWriter(stream); + writer.Write(@$"INSERT INTO dbo.Projects (ProjectId, Name, Description, Location, Color, DateCreatedUtc, DateModifiedUtc) VALUES (1, 'Test Project 1', 'test description', 'location', 1, '2022-01-01 00:00:00', '2022-01-01 00:00:00');"); + writer.Flush(); + stream.Position = 0; + + var result = await importer.ImportAsync("testfile.sql", stream, userContext); + + var db = await storageProvider.GetDatabaseAsync(userContext); + Assert.That(result.Success, Is.True); + Assert.That(result.TotalRowsImported, Is.EqualTo(1)); + Assert.That(result.RowsImportedByTable["Projects"], Is.EqualTo(1)); + Assert.That(db.Projects.Count, Is.EqualTo(1)); + } + + [Test] + public async Task ShouldNotImportSqlWithUnsupportedTableAsync() + { + using var storageProvider = new InMemoryStorageProvider(true); + var importer = new SqlDataImporter(storageProvider); + var userContext = new UserContext { UserId = 99 }; + + using var stream = new MemoryStream(); + using var writer = new StreamWriter(stream); + writer.Write(@$"INSERT INTO SomeOtherTable (ProjectId, Name, Description, Location, Color, DateCreatedUtc, DateModifiedUtc) VALUES (1, 'Test Project 1', 'test description', 'location', 1, '2022-01-01 00:00:00', '2022-01-01 00:00:00');"); + writer.Flush(); + stream.Position = 0; + + var result = await importer.ImportAsync("testfile.sql", stream, userContext); + + var db = await storageProvider.GetDatabaseAsync(userContext); + Assert.That(result.Success, Is.False); + Assert.That(result.TotalRowsImported, Is.EqualTo(0)); + } + + [Test] + public async Task ShouldNotImportSqlWithUnsupportedSchemaAsync() + { + using var storageProvider = new InMemoryStorageProvider(true); + var importer = new SqlDataImporter(storageProvider); + var userContext = new UserContext { UserId = 99 }; + + using var stream = new MemoryStream(); + using var writer = new StreamWriter(stream); + writer.Write(@$"INSERT INTO test.Projects (ProjectId, Name, Description, Location, Color, DateCreatedUtc, DateModifiedUtc) VALUES (1, 'Test Project 1', 'test description', 'location', 1, '2022-01-01 00:00:00', '2022-01-01 00:00:00');"); + writer.Flush(); + stream.Position = 0; + + var result = await importer.ImportAsync("testfile.sql", stream, userContext); + + var db = await storageProvider.GetDatabaseAsync(userContext); + Assert.That(result.Success, Is.False); + Assert.That(result.TotalRowsImported, Is.EqualTo(0)); + Assert.That(result.Errors.Count, Is.EqualTo(1)); + } + + [Test] + public async Task ShouldNotImportSqlWithInvalidContentAsync() + { + using var storageProvider = new InMemoryStorageProvider(true); + var importer = new SqlDataImporter(storageProvider); + var userContext = new UserContext { UserId = 99 }; + + using var stream = new MemoryStream(); + using var writer = new StreamWriter(stream); + writer.WriteLine(@$"#Name, Description, Location, Color, DateCreatedUtc, DateModifiedUtc"); + writer.WriteLine($@"'Test Project 1', 'test description', 'location', 1, '2022-01-01 00:00:00', '2022-01-01 00:00:00'"); + writer.Flush(); + stream.Position = 0; + + var result = await importer.ImportAsync("SomeTable.sql", stream, userContext); + + var db = await storageProvider.GetDatabaseAsync(userContext); + Assert.That(result.Success, Is.False); + Assert.That(result.TotalRowsImported, Is.EqualTo(0)); + Assert.That(result.Errors.Count, Is.EqualTo(1)); + } + + [Test] + public async Task ShouldImportSqlWithUnescapedLineBreaks() + { + using var storageProvider = new InMemoryStorageProvider(true); + var importer = new SqlDataImporter(storageProvider); + var userContext = new UserContext { UserId = 99 }; + + using var stream = new MemoryStream(); + using var writer = new StreamWriter(stream); + writer.Write(@$"INSERT INTO Projects (ProjectId, Name, Description, Location, Color, DateCreatedUtc, DateModifiedUtc) VALUES (1, 'Test Project 1', 'test description + +more text +something else', 'location', 1, '2022-01-01 00:00:00', '2022-01-01 00:00:00');"); + writer.Flush(); + stream.Position = 0; + + var result = await importer.ImportAsync("testfile.sql", stream, userContext); + + var db = await storageProvider.GetDatabaseAsync(userContext); + Assert.That(result.Success, Is.True); + Assert.That(result.TotalRowsImported, Is.EqualTo(1)); + Assert.That(result.RowsImportedByTable["Projects"], Is.EqualTo(1)); + Assert.That(db.Projects.Count, Is.EqualTo(1)); + } + } +} diff --git a/Binner/Tests/Binner.Web.Tests/Binner.Web.Tests.csproj b/Binner/Tests/Binner.Web.Tests/Binner.Web.Tests.csproj index efec562b..4cddef71 100644 --- a/Binner/Tests/Binner.Web.Tests/Binner.Web.Tests.csproj +++ b/Binner/Tests/Binner.Web.Tests/Binner.Web.Tests.csproj @@ -10,16 +10,16 @@ + - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - + + + + + diff --git a/Binner/Tests/Binner.Web.Tests/Middleware/VersionHeaderMiddlewareTests.cs b/Binner/Tests/Binner.Web.Tests/Middleware/VersionHeaderMiddlewareTests.cs new file mode 100644 index 00000000..78dd35d7 --- /dev/null +++ b/Binner/Tests/Binner.Web.Tests/Middleware/VersionHeaderMiddlewareTests.cs @@ -0,0 +1,38 @@ +using Binner.Web.Middleware; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.TestHost; +using Microsoft.Extensions.Hosting; +using NUnit.Framework; +using System.Threading.Tasks; + +namespace Binner.Web.Tests.Middleware +{ + [TestFixture] + public class VersionHeaderMiddlewareTests + { + [Test] + public async Task VersionMiddleware_ShouldMatch() + { + using var host = await new HostBuilder() + .ConfigureWebHost(webBuilder => + { + webBuilder.Configure(app => + { + app.UseMiddleware(); + }); + webBuilder.UseTestServer(); + }).StartAsync(); + + var server = host.GetTestServer(); + var context = await server.SendAsync(context => + { + context.Request.Method = HttpMethods.Get; + context.Request.Path = "/fake"; + }); + + Assert.That(context.Response.Headers.ContainsKey("X-Version"), Is.True); + } + } +} diff --git a/Binner/Tests/Binner.Web.Tests/UnitTest1.cs b/Binner/Tests/Binner.Web.Tests/UnitTest1.cs deleted file mode 100644 index 453c4e13..00000000 --- a/Binner/Tests/Binner.Web.Tests/UnitTest1.cs +++ /dev/null @@ -1,18 +0,0 @@ -using NUnit.Framework; - -namespace Binner.Web.Tests -{ - public class Tests - { - [SetUp] - public void Setup() - { - } - - [Test] - public void Test1() - { - Assert.Pass(); - } - } -} \ No newline at end of file