Initial commit

This commit is contained in:
github-classroom[bot] 2025-02-25 17:05:26 +00:00 committed by GitHub
commit b5ba03f7a8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 4909 additions and 0 deletions

3553
.editorconfig Normal file

File diff suppressed because it is too large Load diff

583
.gitignore vendored Normal file
View file

@ -0,0 +1,583 @@
# Created by https://www.toptal.com/developers/gitignore/api/csharp,visualstudio
# Edit at https://www.toptal.com/developers/gitignore?templates=csharp,visualstudio
### Csharp ###
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
# 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 Core
project.lock.json
project.fragment.lock.json
artifacts/
# 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
# 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
### VisualStudio ###
# User-specific files
# User-specific files (MonoDevelop/Xamarin Studio)
# Mono auto generated files
# Build results
# Visual Studio 2015/2017 cache/options directory
# Uncomment if you have tasks that create the project's static files in wwwroot
# Visual Studio 2017 auto generated files
# MSTest test Results
# NUnit
# Build Results of an ATL Project
# Benchmark Results
# .NET Core
# ASP.NET Scaffolding
# StyleCop
# Files built by Visual Studio
# Chutzpah Test files
# Visual C++ cache files
# Visual Studio profiler
# Visual Studio Trace Files
# TFS 2012 Local Workspace
# Guidance Automation Toolkit
# ReSharper is a .NET coding add-in
# TeamCity is a build add-in
# DotCover is a Code Coverage Tool
# AxoCover is a Code Coverage Tool
# Coverlet is a free, cross platform Code Coverage Tool
# Visual Studio code coverage results
# NCrunch
# MightyMoose
# Web workbench (sass)
# Installshield output folder
# DocProject is a documentation generator add-in
# Click-Once directory
# Publish Web Output
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
# 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
# NuGet Packages
# NuGet Symbol Packages
# The packages folder can be ignored because of Package Restore
# except build/, which is used as an MSBuild target.
# Uncomment if necessary however generally it will be regenerated when needed
# NuGet v3's project.json files produces more ignorable files
# Microsoft Azure Build Output
# Microsoft Azure Emulator
# Windows Store app package directories and files
# Visual Studio cache files
# files ending in .cache can be ignored
# but keep track of directories ending in .cache
# Others
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
# RIA/Silverlight projects
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
# SQL Server files
# Business Intelligence projects
# Microsoft Fakes
# GhostDoc plugin setting file
# Node.js Tools for Visual Studio
# Visual Studio 6 build log
# Visual Studio 6 workspace options file
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
# Visual Studio 6 auto-generated project file (contains which files were open etc.)
# Visual Studio 6 workspace and project file (working project files containing files to include in project)
# Visual Studio 6 technical files
# Visual Studio LightSwitch build output
# Paket dependency manager
# FAKE - F# Make
# CodeRush personal settings
# Python Tools for Visual Studio (PTVS)
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# Tabs Studio
# Telerik's JustMock configuration file
# BizTalk build output
# OpenCover UI analysis results
# Azure Stream Analytics local run output
# MSBuild Binary and Structured Log
# NVidia Nsight GPU debugger configuration file
# MFractors (Xamarin productivity tool) working folder
# Local History for Visual Studio
# Visual Studio History (VSHistory) files
# BeatPulse healthcheck temp database
# Backup folder for Package Reference Convert tool in Visual Studio 2017
# Ionide (cross platform F# VS Code tools) working folder
# Fody - auto-generated XML schema
# VS Code files for those working on multiple tools
# Local History for Visual Studio Code
# Windows Installer files from build outputs
# JetBrains Rider
### VisualStudio Patch ###
# Additional files built by Visual Studio
# End of https://www.toptal.com/developers/gitignore/api/csharp,visualstudio

View file

@ -0,0 +1,192 @@
namespace Supermarket.Test;
public sealed class FoodTests
{
private const string Barcode = "12345670";
[Fact]
public void Construction_Simple()
{
const string Name = "Fischstäbchen";
const int Quantity = 4;
var fishStick = new Food(Name, Barcode, Quantity,
AllergenType.A, AllergenType.C, AllergenType.D);
fishStick.ProductName.Should().Be(Name);
fishStick.Barcode.Should().Be(Barcode);
fishStick.Quantity.Should().Be(Quantity);
fishStick.Allergens.Should()
.NotBeNull()
.And.HaveCount(3)
.And.ContainInOrder(AllergenType.A, AllergenType.C, AllergenType.D);
fishStick.Should().BeAssignableTo<Product>("a food item is a product");
}
[Fact]
public void Construction_DuplicateAllergens()
{
var fishStick = new Food("Fischstäbchen", Barcode, 18,
AllergenType.A, AllergenType.A, AllergenType.C, AllergenType.D);
fishStick.Allergens.Should()
.NotBeNull()
.And.HaveCount(3, "duplicates are not added")
.And.ContainInOrder(AllergenType.A, AllergenType.C, AllergenType.D);
}
[Fact]
public void Construction_AllergensInWrongOrder()
{
var fishStick = new Food("Fischstäbchen", Barcode, 18,
AllergenType.C, AllergenType.D, AllergenType.A);
fishStick.Allergens.Should()
.NotBeNull()
.And.HaveCount(3)
.And.ContainInOrder([AllergenType.A, AllergenType.C, AllergenType.D],
"allergens are always stored in order");
}
[Fact]
public void Construction_NoAllergens()
{
const string Name = "Steak";
const int Quantity = 11;
CheckSteak(new Food(Name, Barcode, Quantity));
CheckSteak(new Food(Name, Barcode, Quantity, Array.Empty<AllergenType>()));
return;
static void CheckSteak(Food steak)
{
steak.ProductName.Should().Be(Name);
steak.Barcode.Should().Be(Barcode);
steak.Quantity.Should().Be(Quantity);
steak.Allergens.Should()
.NotBeNull("an empty list is always created")
.And.BeEmpty();
}
}
[Fact]
public void ContainsAnyAllergen()
{
var cake = new Food("Torte", Barcode, 5,
AllergenType.A, AllergenType.C, AllergenType.F, AllergenType.G);
cake.ContainsAnyAllergen(AllergenType.A).Should().BeTrue("allergen is contained");
cake.ContainsAnyAllergen(AllergenType.F).Should().BeTrue("allergen is contained");
cake.ContainsAnyAllergen(AllergenType.C, AllergenType.G)
.Should().BeTrue("all allergens are contained");
cake.ContainsAnyAllergen(AllergenType.F, AllergenType.C, AllergenType.A, AllergenType.G)
.Should().BeTrue("all allergens are contained, order is irrelevant");
cake.ContainsAnyAllergen(AllergenType.B).Should().BeFalse("allergen is not contained");
cake.ContainsAnyAllergen(AllergenType.B, AllergenType.M)
.Should().BeFalse("none of the allergens are contained");
cake.ContainsAnyAllergen(AllergenType.B, AllergenType.F)
.Should().BeTrue("some of the allergens are contained");
}
[Fact]
public void AddAllergen_Simple()
{
var cake = new Food("Torte", Barcode, 5);
cake.AddAllergen(AllergenType.B).Should().BeTrue("can be added");
cake.Allergens.Should()
.NotBeNull()
.And.HaveCount(1)
.And.ContainInOrder(AllergenType.B);
cake.AddAllergen(AllergenType.A).Should().BeTrue("can be added");
cake.Allergens.Should()
.NotBeNull()
.And.HaveCount(2)
.And.ContainInOrder([AllergenType.A, AllergenType.B], "insert happens in order");
}
[Fact]
public void AddAllergen_AlreadyContained()
{
var cake = new Food("Torte", Barcode, 5);
cake.AddAllergen(AllergenType.A).Should().BeTrue("can be added");
cake.Allergens.Should()
.NotBeNull()
.And.HaveCount(1)
.And.ContainInOrder(AllergenType.A);
cake.AddAllergen(AllergenType.A).Should().BeFalse("already in the list");
cake.Allergens.Should()
.NotBeNull()
.And.HaveCount(1, "unchanged")
.And.ContainInOrder(AllergenType.A);
}
[Fact]
public void RemoveAllergen_Simple()
{
var cake = new Food("Torte", Barcode, 5, AllergenType.A, AllergenType.B);
cake.RemoveAllergen(AllergenType.A).Should().BeTrue("can be removed");
cake.Allergens.Should()
.NotBeNull()
.And.HaveCount(1, "decreased")
.And.ContainInOrder(AllergenType.B);
cake.RemoveAllergen(AllergenType.B).Should().BeTrue("can be removed");
cake.Allergens.Should()
.NotBeNull()
.And.BeEmpty("now empty");
}
[Fact]
public void RemoveAllergen_NotContained()
{
var cake = new Food("Torte", Barcode, 5,
AllergenType.A, AllergenType.B, AllergenType.C);
cake.RemoveAllergen(AllergenType.B).Should().BeTrue("can be removed");
cake.Allergens.Should()
.NotBeNull()
.And.HaveCount(2, "one removed")
.And.ContainInOrder(AllergenType.A, AllergenType.C);
cake.RemoveAllergen(AllergenType.B).Should().BeFalse("already removed");
cake.Allergens.Should()
.NotBeNull()
.And.HaveCount(2, "unchanged")
.And.ContainInOrder(AllergenType.A, AllergenType.C);
cake.RemoveAllergen(AllergenType.F).Should().BeFalse("was never contained");
cake.Allergens.Should()
.NotBeNull()
.And.HaveCount(2, "unchanged")
.And.ContainInOrder(AllergenType.A, AllergenType.C);
}
[Fact]
public void GetCsvHeader()
{
var burger = new Food("Burger", Barcode, 2,
AllergenType.A, AllergenType.F, AllergenType.M);
burger.GetCsvHeader().Should().Be("Barcode;ProductName;Quantity;Allergens");
}
[Fact]
public void ToCsv()
{
var burger = new Food("Burger", Barcode, 2,
AllergenType.A, AllergenType.F, AllergenType.M);
var water = new Food("Leitungswasser", Barcode, 1);
burger.ToCsv().Should().Be($"{Barcode};Burger;2;A|F|M");
water.ToCsv().Should().Be($"{Barcode};Leitungswasser;1;",
"no allergens, but column is still present");
}
}

View file

@ -0,0 +1,94 @@
namespace Supermarket.Test;
public sealed class NonFoodTests
{
private const string Barcode = "12345670";
[Theory]
[InlineData("", Barcode, 4, Product.Invalid, Barcode, 4, "empty name")]
[InlineData("Foo", "", 4, "Foo", Product.Invalid, 4, "empty barcode")]
[InlineData("Foo", "1234", 4, "Foo", Product.Invalid, 4, "invalid barcode")]
[InlineData("Foo", Barcode, -4, "Foo", Barcode, 0, "negative quantity set to 0")]
[InlineData("Foo", Barcode, 4, "Foo", Barcode, 4, "correct values")]
public void Construction_ProductBasics(string name, string barcode, int quantity,
string expectedName, string expectedBarcode, int expectedQuantity,
string reason)
{
var product = new NonFood(name, barcode, quantity);
(product.ProductName, product.Barcode, product.Quantity)
.Should().Be((expectedName, expectedBarcode, expectedQuantity), reason);
}
[Fact]
public void Construction_Simple()
{
const string Name = "Chalk";
const int Quantity = 9;
var chalk = new NonFood(Name, Barcode, Quantity);
chalk.ProductName.Should().Be(Name);
chalk.Quantity.Should().Be(Quantity);
chalk.Barcode.Should().Be(Barcode);
chalk.AverageRating.Should().BeNull("no reviews yet");
chalk.Reviews.Should()
.NotBeNull("empty list was created")
.And.BeEmpty("no reviews yet");
chalk.Should().BeAssignableTo<Product>("a non food item is a product");
}
[Fact]
public void AddReview_Single()
{
var review = new Review(DateTime.Now, Rating.FourStars, "Great fridge!");
var fridge = new NonFood("Fridge", Barcode, 6);
fridge.AddReview(review);
fridge.Reviews.Should()
.NotBeEmpty()
.And.HaveCount(1, "one review added")
.And.Contain(review);
fridge.AverageRating.Should().BeApproximately(4D, double.Epsilon,
"with a single review that is the average");
}
[Fact]
public void AddReview_Multiple()
{
var fridge = new NonFood("Fridge", Barcode, 6);
fridge.AddReview(new Review(DateTime.Now, Rating.FourStars, "Great fridge!"));
fridge.AddReview(new Review(DateTime.Now, Rating.OneStar, "Broke on the first day :("));
fridge.Reviews.Should()
.NotBeEmpty()
.And.HaveCount(2, "two reviews added");
fridge.AverageRating.Should().BeApproximately(2.5D, double.Epsilon,
"average rating");
}
[Fact]
public void GetCsvHeader()
{
var fridge = new NonFood("Fridge", Barcode, 5);
fridge.GetCsvHeader().Should().Be("Barcode;ProductName;Quantity;AverageRating");
}
[Fact]
public void ToCsv()
{
var fridge = new NonFood("Fridge", Barcode, 5);
fridge.ToCsv().Should().Be($"{Barcode};Fridge;5;",
"with no ratings the avg. rating column is present but empty");
fridge.AddReview(new Review(DateTime.Now, Rating.FourStars, "Great fridge!"));
fridge.AddReview(new Review(DateTime.Now, Rating.FiveStars, "Amazing fridge!"));
fridge.AddReview(new Review(DateTime.Now, Rating.TwoStars, "Very loud"));
fridge.ToCsv().Should().Be($"{Barcode};Fridge;5;3.7",
"avg. rating is rounded to 1 decimal place and uses invariant culture");
}
}

View file

@ -0,0 +1,39 @@
namespace Supermarket.Test;
public sealed class ProductTests
{
[Theory]
[InlineData("10000007")]
[InlineData("90311017")]
[InlineData("12345670")]
[InlineData("73513537")]
public void IsBarcodeValid_Valid(string barcode)
{
Product.IsBarcodeValid(barcode)
.Should().BeTrue();
}
[Theory]
[InlineData("10000008")]
[InlineData("90311016")]
[InlineData("12345677")]
[InlineData("123xy670")]
[InlineData("1234567z")]
public void IsBarcodeValid_Invalid(string barcode)
{
Product.IsBarcodeValid(barcode)
.Should().BeFalse();
}
[Theory]
[InlineData("", "no content")]
[InlineData(null, "no content")]
[InlineData("1", "too short")]
[InlineData("903110178", "too long")]
public void IsBarcodeValid_InvalidLength(string? barcode, string reason)
{
Product.IsBarcodeValid(barcode)
.Should().BeFalse(reason);
}
}

View file

@ -0,0 +1,35 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<Using Include="FluentAssertions" />
<Using Include="Xunit" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="AwesomeAssertions" Version="8.0.0" />
<PackageReference Include="HTLLeonding.Utility.LeoAnalyzers" Version="1.0.2" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.13.0" />
<PackageReference Include="xunit" Version="2.9.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.0.2">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="6.0.4">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Supermarket\Supermarket.csproj" />
</ItemGroup>
</Project>

31
Supermarket.sln Normal file
View file

@ -0,0 +1,31 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.4.33103.184
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Supermarket", "Supermarket\Supermarket.csproj", "{7F30E637-BAFE-42FA-A173-F42B3902ED3B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Supermarket.Test", "Supermarket.Test\Supermarket.Test.csproj", "{161D9B2A-4E8B-43B6-A77E-40BED559521F}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{7F30E637-BAFE-42FA-A173-F42B3902ED3B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7F30E637-BAFE-42FA-A173-F42B3902ED3B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7F30E637-BAFE-42FA-A173-F42B3902ED3B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7F30E637-BAFE-42FA-A173-F42B3902ED3B}.Release|Any CPU.Build.0 = Release|Any CPU
{161D9B2A-4E8B-43B6-A77E-40BED559521F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{161D9B2A-4E8B-43B6-A77E-40BED559521F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{161D9B2A-4E8B-43B6-A77E-40BED559521F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{161D9B2A-4E8B-43B6-A77E-40BED559521F}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {D322E690-379C-4802-8F7E-1AC6AD090D59}
EndGlobalSection
EndGlobal

42
Supermarket/Food.cs Normal file
View file

@ -0,0 +1,42 @@
namespace Supermarket;
public sealed class Food : Product
{
private const char AllergenSeparator = '|';
private readonly SortedSet<AllergenType> _allergens;
public Food(string productName, string barcode, int quantity, params AllergenType[] allergens)
: base(productName, barcode, quantity)
{
// TODO
_allergens = null!;
Console.WriteLine($"Remove me, I just make code compile {_allergens}");
}
// TODO
public AllergenType[] Allergens => null!;
// TODO
protected override string[] CsvColumnNames => null!;
protected override string[] CsvColumnValues
{
get
{
// TODO
return null!;
}
}
// TODO
public bool AddAllergen(AllergenType allergen) => false;
// TODO
public bool RemoveAllergen(AllergenType allergen) => false;
public bool ContainsAnyAllergen(params AllergenType[] allergens)
{
// TODO
return false;
}
}

85
Supermarket/Model.cs Normal file
View file

@ -0,0 +1,85 @@
namespace Supermarket;
public sealed record Review(DateTime Timestamp, Rating Rating, string Comment);
public enum Rating
{
OneStar = 1,
TwoStars = 2,
ThreeStars = 3,
FourStars = 4,
FiveStars = 5
}
public enum AllergenType
{
/// <summary>
/// Gluten
/// </summary>
A,
/// <summary>
/// Crustaceans
/// </summary>
B,
/// <summary>
/// Egg
/// </summary>
C,
/// <summary>
/// Fish
/// </summary>
D,
/// <summary>
/// Peanut
/// </summary>
E,
/// <summary>
/// Soy
/// </summary>
F,
/// <summary>
/// Milk / Lactose
/// </summary>
G,
/// <summary>
/// Legumes
/// </summary>
H,
/// <summary>
/// Celery
/// </summary>
L,
/// <summary>
/// Mustard
/// </summary>
M,
/// <summary>
/// Sesame
/// </summary>
N,
/// <summary>
/// Sulfides
/// </summary>
O,
/// <summary>
/// Lupines
/// </summary>
P,
/// <summary>
/// Molluscs
/// </summary>
R
}

40
Supermarket/NonFood.cs Normal file
View file

@ -0,0 +1,40 @@
using System.Globalization;
namespace Supermarket;
public sealed class NonFood : Product
{
private readonly List<Review> _reviews;
public NonFood(string productName, string barcode, int quantity)
: base(productName, barcode, quantity)
{
// TODO
_reviews = null!;
Console.WriteLine($"Remove me, I just make code compile {_reviews}");
}
// TODO
public Review[] Reviews => null!;
public double? AverageRating
{
get
{
// TODO
return -1D;
}
}
// TODO
protected override string[] CsvColumnNames => null!;
// TODO
// hint: .ToString("F1", CultureInfo.InvariantCulture)
protected override string[] CsvColumnValues => null!;
public void AddReview(Review review)
{
// TODO
}
}

48
Supermarket/Product.cs Normal file
View file

@ -0,0 +1,48 @@
namespace Supermarket;
public abstract class Product
{
public const char Separator = ';';
public const string Invalid = "Invalid!";
protected Product(string productName, string barcode, int quantity)
{
// TODO
ProductName = null!;
Barcode = null!;
}
public string ProductName { get; }
public string Barcode { get; }
public int Quantity { get; }
// TODO
protected virtual string[] CsvColumnNames => null!;
// TODO
protected virtual string[] CsvColumnValues => null!;
// TODO
public string GetCsvHeader() => null!;
// TODO
public string ToCsv() => null!;
// TODO
protected static string ToCsvLine(string[] values, char separator) => null!;
public static bool IsBarcodeValid(string? barcode)
{
// TODO
return false;
}
protected static T[] AppendToArray<T>(T[] existingArray, params T[] newValues)
{
// TODO
return null!;
}
}

1
Supermarket/Program.cs Normal file
View file

@ -0,0 +1 @@
Console.WriteLine("*** Supermarket ***");

View file

@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="HTLLeonding.Utility.LeoAnalyzers" Version="1.0.2" />
</ItemGroup>
</Project>

BIN
pics/barcode.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

BIN
pics/food.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 KiB

BIN
pics/non-food.jpeg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

151
readme.adoc Normal file
View file

@ -0,0 +1,151 @@
:sectnums:
:nofooter:
:toc: left
:icons: font
:data-uri:
:source-highlighter: highlightjs
:stem: latexmath
= Inh.04 -- Supermarket
We are processing products sold in a supermarket for its new online-shop offering.
Unlike a traditional grocer, a supermarket offers not only food, but also non-food articles.
[cols="a,a",frame=none, grid=none]
|===
| image::pics/food.jpg[Food]
| image::pics/non-food.jpeg[Non-Food]
|===
== Products
Both food and non-food products share some common properties:
* A barcode
** Has to be a valid barcode, see <<validation,validation>>
* A product name
** Cannot be empty
* A stock quantity
** Must not be negative
Invalid values for barcode & product name are replaced by `"Invalid!"`, a negative quantity is set to `0`.
=== Barcode Validation[[validation]]
* All products have an https://en.wikipedia.org/wiki/EAN-8[EAN-8] barcode
** image:pics/barcode.png[EAN-8,width=200]
* It consists of 8 digits
** So no letters are allowed
** Shorter or longer EANs are not supported
* The last (eight) digit is a _check digit_ which is calculated from the first 7 digits
** Each digit is multiplied by a certain weight
*** A digit at an _even_ index has a weight of 3
*** A digit at an _uneven_ index has a weight of 1
** All _weighted_ digits are summed up
** Then the _difference_ of the _unit place_ to _the next multiple of ten_ is calculated
** This difference has to _match_ the check digit => then the barcode is valid
==== Example
* For the EAN 73513537
* => Check digit is 7
[cols="3h,7*"]
|===
|Position
|0
|1
|2
|3
|4
|5
|6
|Weight
|3
|1
|3
|1
|3
|1
|3
|EAN
|7
|3
|5
|1
|3
|5
|3
|Multiplication-Product
|21
|3
|15
|1
|9
|5
|9
|===
* Sum of all multiplication products is 63
* The next (bigger) multiple of ten is 70
* The difference between 70 and 63 is 7 => the check digit should be 7
** Actually, it is sufficient to subtract the unit place (_remainder_) from 10
* => This is a valid EAN-8 barcode
=== CSV Export
* All products are ready to be exported as CSV
* To support this task they have to provide two functions:
.. Provide a header with all individual column names
.. Turn the instance values into a CSV string
NOTE: The actual export to a file does _not_ have to be implemented this time -- but you should be able to do that, including proper path handling, without any problems by now!
==== `AppendToArray`
* This method is useful for tasks related to the CSV export capabilities
* It is a _generic_ method
** Despite only being called for `string` in this assignment 🙄
* Its job is to create a _new_ array which contains the content of the original array and (at the end) additional elements
=== Food
* Food products can contain various allergens
* The supermarket is legally required to list those to avoid life-threatening reactions for affected customers
* Those allergens are identified by a standardized code
** Already available in the `AllergenType` `enum`
** As defined by the Wirtschaftskammer Österreich (WKO)
* The list must not contain duplicates
* We need to offer a way of checking if any (one or more) specific allergen(s) is contained within the product
* No matter the order allergens are added or removed, they are always stored _in order_
** You are already _so very good_ at implementing sorting algorithms (😝) that we will use a proper one this time by utilizing a https://learn.microsoft.com/en-us/dotnet/api/system.collections.generic.sortedset-1[`SortedSet`]
*** Try to remember what we recently learnt about collections: why aren't we using a https://learn.microsoft.com/en-us/dotnet/api/system.collections.generic.sortedlist-2[`SortedList`] 🤔
* The list of allergens will be _encoded_ in the CSV string as _another_ CSV string using `'|'` as separator
=== Non-Food
* Non-food products can be reviewed by customers
* Each `Review` has the following properties:
** Date & Time it was posted
** A star-rating
*** As defined in the `Rating` `enum`
** A comment
* Based on all posted reviews an _average_ rating can be calculated
== Tasks
. Create a UML class diagram using PlantUML
. Complete the program by implementing all missing code pieces
** Look for `TODOs`
** Extensive unit tests have been provided
. Write all necessary XMLDoc comments
** Usually everything `public` & `protected` if _not_ inherited in a meaningful way
TIP: This assignment makes liberal use of the `params` feature -- if you can't remember what that is, https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/params[check it out again]