Squashed commit of the following:
commit 688c5426e8793b808b9c75c9a19733af0a402fcb Author: Serghei Cebotari <serghei@cebotari.ru> Date: Sun Jul 21 16:25:14 2024 +0300 Switch to port 8080 commit c39249f6528ec76686a9382d1dc375c07d1d5044 Author: Serghei Cebotari <serghei@cebotari.ru> Date: Sun Jul 21 16:24:59 2024 +0300 Switch to alpine image commit 5318d7ec3f4f3d205549cf6732fa5b066a1d0a36 Author: Serghei Cebotari <serghei@cebotari.ru> Date: Sun Jul 21 15:40:14 2024 +0300 Add docker commit b6cd60a973da26bc92cf1fb45b4d2396b7ce56ea Author: Serghei Cebotari <serghei@cebotari.ru> Date: Sun Jul 21 15:00:12 2024 +0300 Delete asynchrony commit 44a194e6d27312f3b8dd0b9c9c02d873e06e0b22 Author: Serghei Cebotari <serghei@cebotari.ru> Date: Sun Jul 21 14:59:29 2024 +0300 Add Equals and GetHasCode methods overrides to ProductQuantity class commit a274eadd313e12f11cc84d32e5030bbc5b187f8c Author: Serghei Cebotari <serghei@cebotari.ru> Date: Sun Jul 21 14:58:37 2024 +0300 Add parsers tests commit 4f969e70d9716d8ddb4f4efedd466846289d7e2b Author: Serghei Cebotari <serghei@cebotari.ru> Date: Sun Jul 21 14:57:55 2024 +0300 Update product tests commit 2485e20d0e93bed562f929055b6867dc2574a95b Author: Serghei Cebotari <serghei@cebotari.ru> Date: Sat Jul 20 19:34:19 2024 +0300 Implement Excel parser commit30f2e28c87
Author: Serghei Cebotari <serghei@cebotari.ru> Date: Sat Jul 20 16:58:35 2024 +0300 Implement csv parser commit08e86b43c0
Author: Serghei Cebotari <serghei@cebotari.ru> Date: Thu Jul 18 21:01:28 2024 +0300 Edit port number
This commit is contained in:
parent
e0313b83a0
commit
6fe0a5e92b
12
.dockerignore
Normal file
12
.dockerignore
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
# directories
|
||||||
|
**/bin/
|
||||||
|
**/obj/
|
||||||
|
**/out/
|
||||||
|
|
||||||
|
# files
|
||||||
|
Dockerfile*
|
||||||
|
**/*.trx
|
||||||
|
**/*.md
|
||||||
|
**/*.ps1
|
||||||
|
**/*.cmd
|
||||||
|
**/*.sh
|
25
Dockerfile
Normal file
25
Dockerfile
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:8.0-alpine AS build
|
||||||
|
ARG TARGETARCH
|
||||||
|
WORKDIR /source
|
||||||
|
|
||||||
|
COPY RhSolutions.SkuParser.Api/*.csproj .
|
||||||
|
RUN dotnet restore -a $TARGETARCH
|
||||||
|
|
||||||
|
COPY RhSolutions.SkuParser.Api/. .
|
||||||
|
RUN dotnet publish -a $TARGETARCH --no-restore -o /app
|
||||||
|
|
||||||
|
FROM mcr.microsoft.com/dotnet/aspnet:8.0-alpine
|
||||||
|
EXPOSE 8080
|
||||||
|
|
||||||
|
ENV \
|
||||||
|
DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=false \
|
||||||
|
LC_ALL=ru_RU.UTF-8 \
|
||||||
|
LANG=ru_RU.UTF-8
|
||||||
|
RUN apk add --no-cache \
|
||||||
|
icu-data-full \
|
||||||
|
icu-libs
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
COPY --from=build /app .
|
||||||
|
USER $APP_UID
|
||||||
|
ENTRYPOINT ["./RhSolutions.SkuParser.Api"]
|
48
RhSolutions.SkuParser.Api/Controllers/ProductsController.cs
Normal file
48
RhSolutions.SkuParser.Api/Controllers/ProductsController.cs
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using RhSolutions.SkuParser.Models;
|
||||||
|
using RhSolutions.SkuParser.Services;
|
||||||
|
|
||||||
|
namespace RhSolutions.SkuParser.Controllers;
|
||||||
|
|
||||||
|
[ApiController]
|
||||||
|
[Route("/api/[controller]")]
|
||||||
|
public class ProductsController : ControllerBase
|
||||||
|
{
|
||||||
|
private IServiceProvider _provider;
|
||||||
|
private Dictionary<Product, double> _result;
|
||||||
|
public ProductsController(IServiceProvider provider)
|
||||||
|
{
|
||||||
|
_provider = provider;
|
||||||
|
_result = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
public IActionResult PostFiles()
|
||||||
|
{
|
||||||
|
IFormFileCollection files = Request.Form.Files;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
foreach (var file in files)
|
||||||
|
{
|
||||||
|
ISkuParser parser = _provider.GetRequiredKeyedService<ISkuParser>(file.ContentType);
|
||||||
|
IEnumerable<ProductQuantity> productQuantities = parser.ParseProducts(file);
|
||||||
|
foreach (ProductQuantity pq in productQuantities)
|
||||||
|
{
|
||||||
|
if (_result.ContainsKey(pq.Product))
|
||||||
|
{
|
||||||
|
_result[pq.Product] += pq.Quantity;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_result.Add(pq.Product, pq.Quantity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return BadRequest(error: $"{ex.Message}\n\n{ex.Source}\n{ex.StackTrace}");
|
||||||
|
}
|
||||||
|
return new JsonResult(_result.Select(x => new { Sku = x.Key.ToString(), Quantity = x.Value }));
|
||||||
|
}
|
||||||
|
}
|
65
RhSolutions.SkuParser.Api/Models/Product.cs
Normal file
65
RhSolutions.SkuParser.Api/Models/Product.cs
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
|
namespace RhSolutions.SkuParser.Models;
|
||||||
|
|
||||||
|
public record Product
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Артикул РЕХАУ в заданном формате
|
||||||
|
/// </summary>
|
||||||
|
public required string Sku
|
||||||
|
{
|
||||||
|
get => _sku;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_sku = IsValudSku(value)
|
||||||
|
? value
|
||||||
|
: throw new ArgumentException("$Неверный артикул: {value}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private string _sku = string.Empty;
|
||||||
|
private const string _parsePattern = @"(?<Lead>[1\s]|^|\b)(?<Article>\d{6})(?<Delimiter>[\s13-])(?<Variant>\d{3})(\b|$)";
|
||||||
|
private const string _validnessPattern = @"^1\d{6}[1|3]\d{3}$";
|
||||||
|
|
||||||
|
private static bool IsValudSku(string value)
|
||||||
|
{
|
||||||
|
return Regex.IsMatch(value.Trim(), _validnessPattern);
|
||||||
|
}
|
||||||
|
private static string GetSku(Match match)
|
||||||
|
{
|
||||||
|
string lead = match.Groups["Lead"].Value;
|
||||||
|
string article = match.Groups["Article"].Value;
|
||||||
|
string delimiter = match.Groups["Delimiter"].Value;
|
||||||
|
string variant = match.Groups["Variant"].Value;
|
||||||
|
|
||||||
|
if (lead != "1" && delimiter == "-")
|
||||||
|
{
|
||||||
|
return $"1{article}1{variant}";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return $"{lead}{article}{delimiter}{variant}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Проверка строки на наличие в ней артикула РЕХАУ
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">Входная строка для проверки</param>
|
||||||
|
/// <param name="product">Артикул, если найден. null - если нет</param>
|
||||||
|
/// <returns>Если артикул в строке есть возвращает true, Если нет - false</returns>
|
||||||
|
public static bool TryParse(string value, out Product? product)
|
||||||
|
{
|
||||||
|
product = null;
|
||||||
|
MatchCollection matches = Regex.Matches(value, _parsePattern);
|
||||||
|
if (matches.Count == 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
string sku = GetSku(matches.First());
|
||||||
|
product = new Product() { Sku = sku };
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
public override int GetHashCode() => Sku.GetHashCode();
|
||||||
|
public override string ToString() => Sku;
|
||||||
|
}
|
30
RhSolutions.SkuParser.Api/Models/ProductQuantity.cs
Normal file
30
RhSolutions.SkuParser.Api/Models/ProductQuantity.cs
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
using CsvHelper.Configuration.Attributes;
|
||||||
|
|
||||||
|
namespace RhSolutions.SkuParser.Models;
|
||||||
|
|
||||||
|
public class ProductQuantity
|
||||||
|
{
|
||||||
|
[Index(0)]
|
||||||
|
public required Product Product { get; set; }
|
||||||
|
[Index(1)]
|
||||||
|
public required double Quantity { get; set; }
|
||||||
|
|
||||||
|
public override bool Equals(object? obj)
|
||||||
|
{
|
||||||
|
if (obj == null || GetType() != obj.GetType())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
ProductQuantity other = (ProductQuantity)obj;
|
||||||
|
return Product == other.Product &&
|
||||||
|
Quantity == other.Quantity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
HashCode hash = new();
|
||||||
|
hash.Add(Product);
|
||||||
|
hash.Add(Quantity);
|
||||||
|
return hash.ToHashCode();
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,12 @@
|
|||||||
|
using RhSolutions.SkuParser.Services;
|
||||||
|
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
builder.Services
|
||||||
|
.AddKeyedScoped<ISkuParser, CsvParser>("text/csv")
|
||||||
|
.AddKeyedScoped<ISkuParser, ExcelParser>("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
|
||||||
|
.AddKeyedScoped<ISkuParser, ExcelParser>("application/vnd.ms-excel.sheet.macroenabled.12");
|
||||||
|
builder.Services.AddControllers();
|
||||||
|
|
||||||
var app = builder.Build();
|
var app = builder.Build();
|
||||||
|
app.MapControllers();
|
||||||
app.MapGet("/", () => "Hello World!");
|
app.Run();
|
||||||
|
|
||||||
app.Run();
|
|
@ -4,7 +4,7 @@
|
|||||||
"windowsAuthentication": false,
|
"windowsAuthentication": false,
|
||||||
"anonymousAuthentication": true,
|
"anonymousAuthentication": true,
|
||||||
"iisExpress": {
|
"iisExpress": {
|
||||||
"applicationUrl": "http://localhost:35100",
|
"applicationUrl": "http://localhost:8080",
|
||||||
"sslPort": 44355
|
"sslPort": 44355
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -12,17 +12,8 @@
|
|||||||
"http": {
|
"http": {
|
||||||
"commandName": "Project",
|
"commandName": "Project",
|
||||||
"dotnetRunMessages": true,
|
"dotnetRunMessages": true,
|
||||||
"launchBrowser": true,
|
"launchBrowser": false,
|
||||||
"applicationUrl": "http://localhost:5087",
|
"applicationUrl": "http://localhost:8080",
|
||||||
"environmentVariables": {
|
|
||||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"https": {
|
|
||||||
"commandName": "Project",
|
|
||||||
"dotnetRunMessages": true,
|
|
||||||
"launchBrowser": true,
|
|
||||||
"applicationUrl": "https://localhost:7266;http://localhost:5087",
|
|
||||||
"environmentVariables": {
|
"environmentVariables": {
|
||||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
}
|
}
|
||||||
|
@ -6,4 +6,9 @@
|
|||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="ClosedXML" Version="0.102.3" />
|
||||||
|
<PackageReference Include="CsvHelper" Version="33.0.1" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
24
RhSolutions.SkuParser.Api/Services/CsvParser.cs
Normal file
24
RhSolutions.SkuParser.Api/Services/CsvParser.cs
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
using System.Globalization;
|
||||||
|
using CsvHelper;
|
||||||
|
using CsvHelper.Configuration;
|
||||||
|
using RhSolutions.SkuParser.Models;
|
||||||
|
|
||||||
|
namespace RhSolutions.SkuParser.Services;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Парсер артикулов и их количества из файлов *.csv
|
||||||
|
/// </summary>
|
||||||
|
public class CsvParser : ISkuParser
|
||||||
|
{
|
||||||
|
public IEnumerable<ProductQuantity> ParseProducts(IFormFile file)
|
||||||
|
{
|
||||||
|
using StreamReader reader = new(file.OpenReadStream());
|
||||||
|
var config = new CsvConfiguration(CultureInfo.GetCultureInfo("ru-RU"))
|
||||||
|
{
|
||||||
|
HasHeaderRecord = false,
|
||||||
|
};
|
||||||
|
using CsvReader csvReader = new(reader, config);
|
||||||
|
|
||||||
|
return csvReader.GetRecords<ProductQuantity>().ToList();
|
||||||
|
}
|
||||||
|
}
|
76
RhSolutions.SkuParser.Api/Services/ExcelParser.cs
Normal file
76
RhSolutions.SkuParser.Api/Services/ExcelParser.cs
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
using ClosedXML.Excel;
|
||||||
|
using RhSolutions.SkuParser.Models;
|
||||||
|
|
||||||
|
namespace RhSolutions.SkuParser.Services;
|
||||||
|
|
||||||
|
public class ExcelParser : ISkuParser
|
||||||
|
{
|
||||||
|
public IEnumerable<ProductQuantity> ParseProducts(IFormFile file)
|
||||||
|
{
|
||||||
|
using XLWorkbook workbook = new(file.OpenReadStream());
|
||||||
|
IXLWorksheet ws = workbook.Worksheet(1);
|
||||||
|
|
||||||
|
var leftTop = ws.FirstCellUsed()?.Address;
|
||||||
|
var rightBottom = ws.LastCellUsed()?.Address;
|
||||||
|
if (new object?[] { leftTop, rightBottom }.Any(x => x == null))
|
||||||
|
{
|
||||||
|
throw new ArgumentException($"Таблица пуста: {file.FileName}");
|
||||||
|
}
|
||||||
|
|
||||||
|
var lookupRange = ws.Range(leftTop, rightBottom).RangeUsed();
|
||||||
|
var columns = lookupRange.Columns();
|
||||||
|
|
||||||
|
var skuColumnQuantity = columns
|
||||||
|
.Select(column => new
|
||||||
|
{
|
||||||
|
Column = column,
|
||||||
|
Products = column.CellsUsed()
|
||||||
|
.Select(cell => !cell.HasFormula && Product.TryParse(cell.Value.ToString(), out Product? p) ? p : null)
|
||||||
|
})
|
||||||
|
.Select(c => new { c.Column, SkuCount = c.Products.Count(p => p != null) })
|
||||||
|
.Aggregate((l, r) => l.SkuCount > r.SkuCount ? l : r);
|
||||||
|
var skuColumn = skuColumnQuantity.SkuCount > 0 ? skuColumnQuantity.Column : null;
|
||||||
|
|
||||||
|
if (skuColumn == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentException($"Столбец с артикулом не определен: {file.FileName}");
|
||||||
|
}
|
||||||
|
|
||||||
|
var quantityColumn = lookupRange.Columns().Skip(skuColumn.ColumnNumber())
|
||||||
|
.Select(column => new
|
||||||
|
{
|
||||||
|
Column = column,
|
||||||
|
IsColumnWithNumbers = column.CellsUsed()
|
||||||
|
.Count(cell => cell.Value.IsNumber == true) > column.CellsUsed().Count() / 4
|
||||||
|
})
|
||||||
|
.First(x => x.IsColumnWithNumbers)
|
||||||
|
.Column;
|
||||||
|
|
||||||
|
if (quantityColumn == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentException($"Столбец с количеством не определен: {file.FileName}");
|
||||||
|
}
|
||||||
|
|
||||||
|
List<ProductQuantity> result = new();
|
||||||
|
var rows = quantityColumn.CellsUsed().Select(x => x.Address.RowNumber);
|
||||||
|
|
||||||
|
foreach (var row in rows)
|
||||||
|
{
|
||||||
|
var quantity = quantityColumn.Cell(row).Value;
|
||||||
|
var sku = skuColumn.Cell(row).Value;
|
||||||
|
|
||||||
|
if (quantity.IsNumber
|
||||||
|
&& Product.TryParse(sku.ToString(), out Product? p))
|
||||||
|
{
|
||||||
|
ProductQuantity pq = new()
|
||||||
|
{
|
||||||
|
Product = p!,
|
||||||
|
Quantity = quantity.GetNumber()
|
||||||
|
};
|
||||||
|
result.Add(pq);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
7
RhSolutions.SkuParser.Api/Services/ISkuParser.cs
Normal file
7
RhSolutions.SkuParser.Api/Services/ISkuParser.cs
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
using RhSolutions.SkuParser.Models;
|
||||||
|
|
||||||
|
namespace RhSolutions.SkuParser.Services;
|
||||||
|
public interface ISkuParser
|
||||||
|
{
|
||||||
|
public IEnumerable<ProductQuantity> ParseProducts(IFormFile file);
|
||||||
|
}
|
48
RhSolutions.SkuParser.Tests/ExcelParserTests.cs
Normal file
48
RhSolutions.SkuParser.Tests/ExcelParserTests.cs
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
using RhSolutions.SkuParser.Services;
|
||||||
|
|
||||||
|
namespace RhSolutions.SkuParser.Tests;
|
||||||
|
|
||||||
|
public class ExcelParserTests
|
||||||
|
{
|
||||||
|
private static readonly List<ProductQuantity> _expected = new()
|
||||||
|
{
|
||||||
|
new ProductQuantity() {Product= new Product() {Sku = "11303703100"}, Quantity = 2129.5},
|
||||||
|
new ProductQuantity() {Product= new Product() {Sku = "11303803100"}, Quantity = 503},
|
||||||
|
new ProductQuantity() {Product= new Product() {Sku = "11303903050"}, Quantity = 52},
|
||||||
|
new ProductQuantity() {Product= new Product() {Sku = "11080011001"}, Quantity = 2154},
|
||||||
|
new ProductQuantity() {Product= new Product() {Sku = "11080021001"}, Quantity = 134},
|
||||||
|
new ProductQuantity() {Product= new Product() {Sku = "11080031001"}, Quantity = 6},
|
||||||
|
new ProductQuantity() {Product= new Product() {Sku = "11080311001"}, Quantity = 462},
|
||||||
|
new ProductQuantity() {Product= new Product() {Sku = "11080611001"}, Quantity = 38},
|
||||||
|
new ProductQuantity() {Product= new Product() {Sku = "11080811001"}, Quantity = 24},
|
||||||
|
new ProductQuantity() {Product= new Product() {Sku = "11080831001"}, Quantity = 2},
|
||||||
|
};
|
||||||
|
|
||||||
|
[TestCase("simple.xlsx")]
|
||||||
|
[TestCase("simpleWithNames.xlsx")]
|
||||||
|
[TestCase("withHeader.xlsx")]
|
||||||
|
[TestCase("withHeaderAndGarbage.xlsx")]
|
||||||
|
[TestCase("twoTables.xlsx")]
|
||||||
|
[TestCase("rhSolutionsBsTable.xlsx")]
|
||||||
|
[TestCase("simpleWithFormulas.xlsx")]
|
||||||
|
public void XlsxTests(string filename)
|
||||||
|
{
|
||||||
|
var mockFile = FormFileUtil.GetMockFormFile(filename);
|
||||||
|
var parser = new ExcelParser();
|
||||||
|
var actual = parser.ParseProducts(mockFile.Object);
|
||||||
|
Assert.That(actual.Count, Is.EqualTo(_expected.Count()));
|
||||||
|
CollectionAssert.AllItemsAreInstancesOfType(actual, typeof(ProductQuantity));
|
||||||
|
CollectionAssert.AreEqual(_expected, actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestCase("simple.csv")]
|
||||||
|
public void CsvTests(string filename)
|
||||||
|
{
|
||||||
|
var mockFile = FormFileUtil.GetMockFormFile(filename);
|
||||||
|
var parser = new CsvParser();
|
||||||
|
var actual = parser.ParseProducts(mockFile.Object);
|
||||||
|
Assert.That(actual.Count, Is.EqualTo(_expected.Count()));
|
||||||
|
CollectionAssert.AllItemsAreInstancesOfType(actual, typeof(ProductQuantity));
|
||||||
|
CollectionAssert.AreEqual(_expected, actual);
|
||||||
|
}
|
||||||
|
}
|
17
RhSolutions.SkuParser.Tests/FormFileUtil.cs
Normal file
17
RhSolutions.SkuParser.Tests/FormFileUtil.cs
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Moq;
|
||||||
|
|
||||||
|
namespace RhSolutions.SkuParser.Tests;
|
||||||
|
|
||||||
|
public static class FormFileUtil
|
||||||
|
{
|
||||||
|
public static Mock<IFormFile> GetMockFormFile(string workbookName)
|
||||||
|
{
|
||||||
|
string filepath = "./../../../Workbooks/" + workbookName;
|
||||||
|
var mockFile = new Mock<IFormFile>();
|
||||||
|
var memoryStream = new MemoryStream([.. File.ReadAllBytes(filepath)]);
|
||||||
|
mockFile.Setup(x => x.OpenReadStream())
|
||||||
|
.Returns(memoryStream);
|
||||||
|
return mockFile;
|
||||||
|
}
|
||||||
|
}
|
2
RhSolutions.SkuParser.Tests/GlobalUsings.cs
Normal file
2
RhSolutions.SkuParser.Tests/GlobalUsings.cs
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
global using NUnit.Framework;
|
||||||
|
global using RhSolutions.SkuParser.Models;
|
75
RhSolutions.SkuParser.Tests/ProductTests.cs
Normal file
75
RhSolutions.SkuParser.Tests/ProductTests.cs
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
namespace RhSolutions.SkuParser.Tests;
|
||||||
|
|
||||||
|
public class ProductTests
|
||||||
|
{
|
||||||
|
[TestCase("12222221001")]
|
||||||
|
[TestCase("12222223001")]
|
||||||
|
[TestCase("160001-001")]
|
||||||
|
public void SimpleParse(string value)
|
||||||
|
{
|
||||||
|
Assert.True(Product.TryParse(value, out _));
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestCase("string 12222221001")]
|
||||||
|
[TestCase("12222223001 string")]
|
||||||
|
[TestCase("string 160001-001")]
|
||||||
|
[TestCase("160001-001 string ")]
|
||||||
|
[TestCase("11096641001 Трубка РЕХАУ из. нерж. стали для подкл. радиатора, Г-образная 16/250")]
|
||||||
|
public void AdvancedParse(string value)
|
||||||
|
{
|
||||||
|
Assert.True(Product.TryParse(value, out _));
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestCase("11600011001")]
|
||||||
|
[TestCase("160001-001")]
|
||||||
|
public void ProductIsCorrect(string value)
|
||||||
|
{
|
||||||
|
if (Product.TryParse(value, out Product? product))
|
||||||
|
{
|
||||||
|
Assert.That(product!.Sku, Is.EqualTo("11600011001"));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Assert.Fail($"Parsing failed on {value}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestCase("1222222001")]
|
||||||
|
[TestCase("12222225001")]
|
||||||
|
public void NotParses(string value)
|
||||||
|
{
|
||||||
|
Assert.False(Product.TryParse(value, out _));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void ProductEquality()
|
||||||
|
{
|
||||||
|
string value = "12222223001";
|
||||||
|
Product.TryParse(value, out Product? first);
|
||||||
|
Product.TryParse(value, out Product? second);
|
||||||
|
if (first == null || second == null)
|
||||||
|
{
|
||||||
|
Assert.Fail($"Parsing failed on {value}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Assert.True(first.Equals(second));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void HashTest()
|
||||||
|
{
|
||||||
|
string value = "12222223001";
|
||||||
|
HashSet<Product> set = new();
|
||||||
|
if (Product.TryParse(value, out var product))
|
||||||
|
{
|
||||||
|
set.Add(product!);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Assert.Fail($"Parsing failed on {value}");
|
||||||
|
}
|
||||||
|
Assert.True(set.Contains(product!));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
|
||||||
|
<IsPackable>false</IsPackable>
|
||||||
|
<IsTestProject>true</IsTestProject>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.0" />
|
||||||
|
<PackageReference Include="Moq" Version="4.20.70" />
|
||||||
|
<PackageReference Include="NUnit" Version="3.13.3" />
|
||||||
|
<PackageReference Include="NUnit3TestAdapter" Version="4.2.1" />
|
||||||
|
<PackageReference Include="NUnit.Analyzers" Version="3.6.1" />
|
||||||
|
<PackageReference Include="coverlet.collector" Version="6.0.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\RhSolutions.SkuParser.Api\RhSolutions.SkuParser.Api.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
BIN
RhSolutions.SkuParser.Tests/Workbooks/rhSolutionsBsTable.xlsx
Normal file
BIN
RhSolutions.SkuParser.Tests/Workbooks/rhSolutionsBsTable.xlsx
Normal file
Binary file not shown.
10
RhSolutions.SkuParser.Tests/Workbooks/simple.csv
Normal file
10
RhSolutions.SkuParser.Tests/Workbooks/simple.csv
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
11303703100;2129,5
|
||||||
|
11303803100;503
|
||||||
|
11303903050;52
|
||||||
|
11080011001;2154
|
||||||
|
11080021001;134
|
||||||
|
11080031001;6
|
||||||
|
11080311001;462
|
||||||
|
11080611001;38
|
||||||
|
11080811001;24
|
||||||
|
11080831001;2
|
|
BIN
RhSolutions.SkuParser.Tests/Workbooks/simple.xlsx
Normal file
BIN
RhSolutions.SkuParser.Tests/Workbooks/simple.xlsx
Normal file
Binary file not shown.
BIN
RhSolutions.SkuParser.Tests/Workbooks/simpleWithFormulas.xlsx
Normal file
BIN
RhSolutions.SkuParser.Tests/Workbooks/simpleWithFormulas.xlsx
Normal file
Binary file not shown.
BIN
RhSolutions.SkuParser.Tests/Workbooks/simpleWithNames.xlsx
Normal file
BIN
RhSolutions.SkuParser.Tests/Workbooks/simpleWithNames.xlsx
Normal file
Binary file not shown.
BIN
RhSolutions.SkuParser.Tests/Workbooks/twoTables.xlsx
Normal file
BIN
RhSolutions.SkuParser.Tests/Workbooks/twoTables.xlsx
Normal file
Binary file not shown.
BIN
RhSolutions.SkuParser.Tests/Workbooks/withHeader.xlsx
Normal file
BIN
RhSolutions.SkuParser.Tests/Workbooks/withHeader.xlsx
Normal file
Binary file not shown.
BIN
RhSolutions.SkuParser.Tests/Workbooks/withHeaderAndGarbage.xlsx
Normal file
BIN
RhSolutions.SkuParser.Tests/Workbooks/withHeaderAndGarbage.xlsx
Normal file
Binary file not shown.
@ -5,6 +5,8 @@ VisualStudioVersion = 17.0.31903.59
|
|||||||
MinimumVisualStudioVersion = 10.0.40219.1
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RhSolutions.SkuParser.Api", "RhSolutions.SkuParser.Api\RhSolutions.SkuParser.Api.csproj", "{5178E712-F984-48F4-9C68-6DC8CCAA4053}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RhSolutions.SkuParser.Api", "RhSolutions.SkuParser.Api\RhSolutions.SkuParser.Api.csproj", "{5178E712-F984-48F4-9C68-6DC8CCAA4053}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RhSolutions.SkuParser.Tests", "RhSolutions.SkuParser.Tests\RhSolutions.SkuParser.Tests.csproj", "{8EB147E6-E7B9-42AB-B634-BA2EA1A7A542}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
@ -18,5 +20,9 @@ Global
|
|||||||
{5178E712-F984-48F4-9C68-6DC8CCAA4053}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{5178E712-F984-48F4-9C68-6DC8CCAA4053}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{5178E712-F984-48F4-9C68-6DC8CCAA4053}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{5178E712-F984-48F4-9C68-6DC8CCAA4053}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{5178E712-F984-48F4-9C68-6DC8CCAA4053}.Release|Any CPU.Build.0 = Release|Any CPU
|
{5178E712-F984-48F4-9C68-6DC8CCAA4053}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{8EB147E6-E7B9-42AB-B634-BA2EA1A7A542}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{8EB147E6-E7B9-42AB-B634-BA2EA1A7A542}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{8EB147E6-E7B9-42AB-B634-BA2EA1A7A542}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{8EB147E6-E7B9-42AB-B634-BA2EA1A7A542}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
EndGlobal
|
EndGlobal
|
||||||
|
Reference in New Issue
Block a user