1
0

Add devcontainers

This commit is contained in:
Serghei Cebotari 2025-01-14 05:59:55 +00:00
parent 1b89c80965
commit 7b6f2fedf1
23 changed files with 1080 additions and 1045 deletions

View File

@ -0,0 +1,35 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/dotnet
{
"name": "C# (.NET)",
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
"image": "mcr.microsoft.com/devcontainers/dotnet:1-8.0",
"customizations": {
"vscode": {
"extensions": [
"Ironcutter24.cscurlyformatter",
"ms-dotnettools.csdevkit"
]
}
}
// Features to add to the dev container. More info: https://containers.dev/features.
// "features": {},
// Use 'forwardPorts' to make a list of ports inside the container available locally.
// "forwardPorts": [5000, 5001],
// "portsAttributes": {
// "5001": {
// "protocol": "https"
// }
// }
// Use 'postCreateCommand' to run commands after the container is created.
// "postCreateCommand": "dotnet restore",
// Configure tool-specific properties.
// "customizations": {},
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
// "remoteUser": "root"
}

View File

@ -1,12 +1,12 @@
# directories
**/bin/
**/obj/
**/out/
# files
Dockerfile*
**/*.trx
**/*.md
**/*.ps1
**/*.cmd
# directories
**/bin/
**/obj/
**/out/
# files
Dockerfile*
**/*.trx
**/*.md
**/*.ps1
**/*.cmd
**/*.sh

968
.gitignore vendored
View File

@ -1,484 +1,484 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from `dotnet new gitignore`
# dotenv files
.env
# User-specific files
*.rsuser
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Mono auto generated files
mono_crash.*
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
[Ww][Ii][Nn]32/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
[Ll]ogs/
# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# Visual Studio 2017 auto generated files
Generated\ Files/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUnit
*.VisualState.xml
TestResult.xml
nunit-*.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET
project.lock.json
project.fragment.lock.json
artifacts/
# Tye
.tye/
# ASP.NET Scaffolding
ScaffoldingReadMe.txt
# StyleCop
StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_h.h
*.ilk
*.meta
*.obj
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*_wpftmp.csproj
*.log
*.tlog
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# Visual Studio Trace Files
*.e2e
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Coverlet is a free, cross platform Code Coverage Tool
coverage*.json
coverage*.xml
coverage*.info
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# NuGet Symbol Packages
*.snupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
*.appxbundle
*.appxupload
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!?*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
*- [Bb]ackup.rdl
*- [Bb]ackup ([0-9]).rdl
*- [Bb]ackup ([0-9][0-9]).rdl
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio 6 auto-generated project file (contains which files were open etc.)
*.vbp
# Visual Studio 6 workspace and project file (working project files containing files to include in project)
*.dsw
*.dsp
# Visual Studio 6 technical files
*.ncb
*.aps
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# CodeRush personal settings
.cr/personal
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# Tabs Studio
*.tss
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
*.binlog
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
.mfractor/
# Local History for Visual Studio
.localhistory/
# Visual Studio History (VSHistory) files
.vshistory/
# BeatPulse healthcheck temp database
healthchecksdb
# Backup folder for Package Reference Convert tool in Visual Studio 2017
MigrationBackup/
# Ionide (cross platform F# VS Code tools) working folder
.ionide/
# Fody - auto-generated XML schema
FodyWeavers.xsd
# VS Code files for those working on multiple tools
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
*.code-workspace
# Local History for Visual Studio Code
.history/
# Windows Installer files from build outputs
*.cab
*.msi
*.msix
*.msm
*.msp
# JetBrains Rider
*.sln.iml
.idea
##
## Visual studio for Mac
##
# globs
Makefile.in
*.userprefs
*.usertasks
config.make
config.status
aclocal.m4
install-sh
autom4te.cache/
*.tar.gz
tarballs/
test-results/
# Mac bundle stuff
*.dmg
*.app
# content below from: https://github.com/github/gitignore/blob/master/Global/macOS.gitignore
# General
.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
# content below from: https://github.com/github/gitignore/blob/master/Global/Windows.gitignore
# Windows thumbnail cache files
Thumbs.db
ehthumbs.db
ehthumbs_vista.db
# Dump file
*.stackdump
# Folder config file
[Dd]esktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Windows Installer files
*.cab
*.msi
*.msix
*.msm
*.msp
# Windows shortcuts
*.lnk
# Vim temporary swap files
*.swp
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from `dotnet new gitignore`
# dotenv files
.env
# User-specific files
*.rsuser
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Mono auto generated files
mono_crash.*
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
[Ww][Ii][Nn]32/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
[Ll]ogs/
# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# Visual Studio 2017 auto generated files
Generated\ Files/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUnit
*.VisualState.xml
TestResult.xml
nunit-*.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET
project.lock.json
project.fragment.lock.json
artifacts/
# Tye
.tye/
# ASP.NET Scaffolding
ScaffoldingReadMe.txt
# StyleCop
StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_h.h
*.ilk
*.meta
*.obj
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*_wpftmp.csproj
*.log
*.tlog
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# Visual Studio Trace Files
*.e2e
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Coverlet is a free, cross platform Code Coverage Tool
coverage*.json
coverage*.xml
coverage*.info
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# NuGet Symbol Packages
*.snupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
*.appxbundle
*.appxupload
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!?*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
*- [Bb]ackup.rdl
*- [Bb]ackup ([0-9]).rdl
*- [Bb]ackup ([0-9][0-9]).rdl
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio 6 auto-generated project file (contains which files were open etc.)
*.vbp
# Visual Studio 6 workspace and project file (working project files containing files to include in project)
*.dsw
*.dsp
# Visual Studio 6 technical files
*.ncb
*.aps
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# CodeRush personal settings
.cr/personal
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# Tabs Studio
*.tss
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
*.binlog
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
.mfractor/
# Local History for Visual Studio
.localhistory/
# Visual Studio History (VSHistory) files
.vshistory/
# BeatPulse healthcheck temp database
healthchecksdb
# Backup folder for Package Reference Convert tool in Visual Studio 2017
MigrationBackup/
# Ionide (cross platform F# VS Code tools) working folder
.ionide/
# Fody - auto-generated XML schema
FodyWeavers.xsd
# VS Code files for those working on multiple tools
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
*.code-workspace
# Local History for Visual Studio Code
.history/
# Windows Installer files from build outputs
*.cab
*.msi
*.msix
*.msm
*.msp
# JetBrains Rider
*.sln.iml
.idea
##
## Visual studio for Mac
##
# globs
Makefile.in
*.userprefs
*.usertasks
config.make
config.status
aclocal.m4
install-sh
autom4te.cache/
*.tar.gz
tarballs/
test-results/
# Mac bundle stuff
*.dmg
*.app
# content below from: https://github.com/github/gitignore/blob/master/Global/macOS.gitignore
# General
.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
# content below from: https://github.com/github/gitignore/blob/master/Global/Windows.gitignore
# Windows thumbnail cache files
Thumbs.db
ehthumbs.db
ehthumbs_vista.db
# Dump file
*.stackdump
# Folder config file
[Dd]esktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Windows Installer files
*.cab
*.msi
*.msix
*.msm
*.msp
# Windows shortcuts
*.lnk
# Vim temporary swap files
*.swp

View File

@ -1,25 +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
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"]

View File

@ -1,6 +1,6 @@
# RhSolutions.SkuParser.Api
Сервис для парсинга актикулов РЕХАУ и их количества из файлов `xlsx`, `xlsm` и `csv`
Доступен через POST-метод `/api/Products/`. Отправлять файлы в `form-data`.
# RhSolutions.SkuParser.Api
Сервис для парсинга актикулов РЕХАУ и их количества из файлов `xlsx`, `xlsm` и `csv`
Доступен через POST-метод `/api/Products/`. Отправлять файлы в `form-data`.
Количества одноименных артикулов в одном или нескольких файлах суммируются.

View File

@ -1,48 +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 }));
}
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 }));
}
}

View File

@ -1,65 +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;
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;
}

View File

@ -1,30 +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();
}
}
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();
}
}

View File

@ -1,12 +1,12 @@
using RhSolutions.SkuParser.Services;
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();
app.MapControllers();
using RhSolutions.SkuParser.Services;
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();
app.MapControllers();
app.Run();

View File

@ -1,29 +1,29 @@
{
"$schema": "http://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:8080",
"sslPort": 44355
}
},
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": false,
"applicationUrl": "http://localhost:8080",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
{
"$schema": "http://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:8080",
"sslPort": 44355
}
},
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": false,
"applicationUrl": "http://localhost:8080",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}

View File

@ -1,14 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="ClosedXML" Version="0.102.3" />
<PackageReference Include="CsvHelper" Version="33.0.1" />
</ItemGroup>
</Project>
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="ClosedXML" Version="0.102.3" />
<PackageReference Include="CsvHelper" Version="33.0.1" />
</ItemGroup>
</Project>

View File

@ -1,24 +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();
}
}
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();
}
}

View File

@ -1,76 +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;
}
}
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;
}
}

View File

@ -1,7 +1,7 @@
using RhSolutions.SkuParser.Models;
namespace RhSolutions.SkuParser.Services;
public interface ISkuParser
{
public IEnumerable<ProductQuantity> ParseProducts(IFormFile file);
}
using RhSolutions.SkuParser.Models;
namespace RhSolutions.SkuParser.Services;
public interface ISkuParser
{
public IEnumerable<ProductQuantity> ParseProducts(IFormFile file);
}

View File

@ -1,8 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}

View File

@ -1,9 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}

View File

@ -1,48 +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);
}
}
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);
}
}

View File

@ -1,17 +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;
}
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;
}
}

View File

@ -1,2 +1,2 @@
global using NUnit.Framework;
global using NUnit.Framework;
global using RhSolutions.SkuParser.Models;

View File

@ -1,75 +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!));
}
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!));
}
}

View File

@ -1,25 +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>
<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>

View File

@ -1,10 +1,10 @@
11303703100;2129,5
11303803100;503
11303903050;52
11080011001;2154
11080021001;134
11080031001;6
11080311001;462
11080611001;38
11080811001;24
11080831001;2
11303703100;2129,5
11303803100;503
11303903050;52
11080011001;2154
11080021001;134
11080031001;6
11080311001;462
11080611001;38
11080811001;24
11080831001;2

1 11303703100 2129,5
2 11303803100 503
3 11303903050 52
4 11080011001 2154
5 11080021001 134
6 11080031001 6
7 11080311001 462
8 11080611001 38
9 11080811001 24
10 11080831001 2

View File

@ -1,28 +1,28 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31903.59
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}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RhSolutions.SkuParser.Tests", "RhSolutions.SkuParser.Tests\RhSolutions.SkuParser.Tests.csproj", "{8EB147E6-E7B9-42AB-B634-BA2EA1A7A542}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{5178E712-F984-48F4-9C68-6DC8CCAA4053}.Debug|Any CPU.ActiveCfg = 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.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
EndGlobal

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31903.59
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}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RhSolutions.SkuParser.Tests", "RhSolutions.SkuParser.Tests\RhSolutions.SkuParser.Tests.csproj", "{8EB147E6-E7B9-42AB-B634-BA2EA1A7A542}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{5178E712-F984-48F4-9C68-6DC8CCAA4053}.Debug|Any CPU.ActiveCfg = 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.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
EndGlobal