diff --git a/351002/vorobei/.gitattributes b/351002/vorobei/.gitattributes
new file mode 100644
index 000000000..1ff0c4230
--- /dev/null
+++ b/351002/vorobei/.gitattributes
@@ -0,0 +1,63 @@
+###############################################################################
+# Set default behavior to automatically normalize line endings.
+###############################################################################
+* text=auto
+
+###############################################################################
+# Set default behavior for command prompt diff.
+#
+# This is need for earlier builds of msysgit that does not have it on by
+# default for csharp files.
+# Note: This is only used by command line
+###############################################################################
+#*.cs diff=csharp
+
+###############################################################################
+# Set the merge driver for project and solution files
+#
+# Merging from the command prompt will add diff markers to the files if there
+# are conflicts (Merging from VS is not affected by the settings below, in VS
+# the diff markers are never inserted). Diff markers may cause the following
+# file extensions to fail to load in VS. An alternative would be to treat
+# these files as binary and thus will always conflict and require user
+# intervention with every merge. To do so, just uncomment the entries below
+###############################################################################
+#*.sln merge=binary
+#*.csproj merge=binary
+#*.vbproj merge=binary
+#*.vcxproj merge=binary
+#*.vcproj merge=binary
+#*.dbproj merge=binary
+#*.fsproj merge=binary
+#*.lsproj merge=binary
+#*.wixproj merge=binary
+#*.modelproj merge=binary
+#*.sqlproj merge=binary
+#*.wwaproj merge=binary
+
+###############################################################################
+# behavior for image files
+#
+# image files are treated as binary by default.
+###############################################################################
+#*.jpg binary
+#*.png binary
+#*.gif binary
+
+###############################################################################
+# diff behavior for common document formats
+#
+# Convert binary document formats to text before diffing them. This feature
+# is only available from the command line. Turn it on by uncommenting the
+# entries below.
+###############################################################################
+#*.doc diff=astextplain
+#*.DOC diff=astextplain
+#*.docx diff=astextplain
+#*.DOCX diff=astextplain
+#*.dot diff=astextplain
+#*.DOT diff=astextplain
+#*.pdf diff=astextplain
+#*.PDF diff=astextplain
+#*.rtf diff=astextplain
+#*.RTF diff=astextplain
diff --git a/351002/vorobei/.gitignore b/351002/vorobei/.gitignore
new file mode 100644
index 000000000..9491a2fda
--- /dev/null
+++ b/351002/vorobei/.gitignore
@@ -0,0 +1,363 @@
+## 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/master/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/
+[Oo]ut/
+[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
+*.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 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/
+
+# 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
\ No newline at end of file
diff --git a/351002/vorobei/BusinessLogic/BusinessLogic.csproj b/351002/vorobei/BusinessLogic/BusinessLogic.csproj
new file mode 100644
index 000000000..6a1b46bc5
--- /dev/null
+++ b/351002/vorobei/BusinessLogic/BusinessLogic.csproj
@@ -0,0 +1,17 @@
+
+
+
+ net8.0
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+
+
diff --git a/351002/vorobei/BusinessLogic/DTO/Request/CreatorRequestTo.cs b/351002/vorobei/BusinessLogic/DTO/Request/CreatorRequestTo.cs
new file mode 100644
index 000000000..222fc9814
--- /dev/null
+++ b/351002/vorobei/BusinessLogic/DTO/Request/CreatorRequestTo.cs
@@ -0,0 +1,20 @@
+using System.ComponentModel.DataAnnotations;
+using DataAccess.Models;
+
+namespace BusinessLogic.DTO.Request
+{
+ public class CreatorRequestTo : BaseEntity
+ {
+ [StringLength(64, MinimumLength = 2, ErrorMessage = "Login should be from 2 to 64 symbols")]
+ public string Login { get; set; } = string.Empty;
+
+ [StringLength(128, MinimumLength = 8, ErrorMessage = "Password should be from 8 to 128 symbols")]
+ public string Password { get; set; } = string.Empty;
+
+ [StringLength(64, MinimumLength = 2, ErrorMessage = "FirstName should be from 2 to 64 symbols")]
+ public string FirstName { get; set; } = string.Empty;
+
+ [StringLength(64, MinimumLength = 2, ErrorMessage = "LastName should be from 2 to 64 symbols")]
+ public string LastName { get; set; } = string.Empty;
+ }
+}
diff --git a/351002/vorobei/BusinessLogic/DTO/Request/MarkRequestTo.cs b/351002/vorobei/BusinessLogic/DTO/Request/MarkRequestTo.cs
new file mode 100644
index 000000000..115301a09
--- /dev/null
+++ b/351002/vorobei/BusinessLogic/DTO/Request/MarkRequestTo.cs
@@ -0,0 +1,11 @@
+using System.ComponentModel.DataAnnotations;
+using DataAccess.Models;
+
+namespace BusinessLogic.DTO.Request
+{
+ public class MarkRequestTo : BaseEntity
+ {
+ [StringLength(32, MinimumLength = 2, ErrorMessage = "Name should be from 2 to 32 symbols")]
+ public string Name { get; set; } = string.Empty;
+ }
+}
diff --git a/351002/vorobei/BusinessLogic/DTO/Request/PostRequestTo.cs b/351002/vorobei/BusinessLogic/DTO/Request/PostRequestTo.cs
new file mode 100644
index 000000000..01c0d717f
--- /dev/null
+++ b/351002/vorobei/BusinessLogic/DTO/Request/PostRequestTo.cs
@@ -0,0 +1,13 @@
+using System.ComponentModel.DataAnnotations;
+using DataAccess.Models;
+
+namespace BusinessLogic.DTO.Request
+{
+ public class PostRequestTo : BaseEntity
+ {
+ public int StoryId { get; set; }
+
+ [StringLength(2048, MinimumLength = 2, ErrorMessage = "Content should be from 2 to 2048 symbols")]
+ public string Content { get; set; } = string.Empty;
+ }
+}
diff --git a/351002/vorobei/BusinessLogic/DTO/Request/StoryRequestTo.cs b/351002/vorobei/BusinessLogic/DTO/Request/StoryRequestTo.cs
new file mode 100644
index 000000000..52708a122
--- /dev/null
+++ b/351002/vorobei/BusinessLogic/DTO/Request/StoryRequestTo.cs
@@ -0,0 +1,16 @@
+using System.ComponentModel.DataAnnotations;
+using DataAccess.Models;
+
+namespace BusinessLogic.DTO.Request
+{
+ public class StoryRequestTo : BaseEntity
+ {
+ public int CreatorId { get; set; }
+
+ [StringLength(64, MinimumLength = 2, ErrorMessage = "Title should be from 2 to 64 symbols")]
+ public string Title { get; set; } = string.Empty;
+
+ [StringLength(2048, MinimumLength = 4, ErrorMessage = "Content should be from 4 to 2048 symbols")]
+ public string Content { get; set; } = string.Empty;
+ }
+}
diff --git a/351002/vorobei/BusinessLogic/DTO/Response/CreatorResponseTo.cs b/351002/vorobei/BusinessLogic/DTO/Response/CreatorResponseTo.cs
new file mode 100644
index 000000000..326376a66
--- /dev/null
+++ b/351002/vorobei/BusinessLogic/DTO/Response/CreatorResponseTo.cs
@@ -0,0 +1,21 @@
+using BusinessLogic.DTO.Request;
+using DataAccess.Models;
+using System.Text.Json.Serialization;
+
+namespace BusinessLogic.DTO.Response
+{
+ public class CreatorResponseTo : BaseEntity
+ {
+ [JsonPropertyName("login")]
+ public string Login { get; set; } = string.Empty;
+
+ [JsonPropertyName("password")]
+ public string Password { get; set; } = string.Empty;
+
+ [JsonPropertyName("firstname")]
+ public string FirstName { get; set; } = string.Empty;
+
+ [JsonPropertyName("lastname")]
+ public string LastName { get; set; } = string.Empty;
+ }
+}
diff --git a/351002/vorobei/BusinessLogic/DTO/Response/MarkResponseTo.cs b/351002/vorobei/BusinessLogic/DTO/Response/MarkResponseTo.cs
new file mode 100644
index 000000000..ed683a0d2
--- /dev/null
+++ b/351002/vorobei/BusinessLogic/DTO/Response/MarkResponseTo.cs
@@ -0,0 +1,12 @@
+using System.ComponentModel.DataAnnotations;
+using System.Text.Json.Serialization;
+using DataAccess.Models;
+
+namespace BusinessLogic.DTO.Response
+{
+ public class MarkResponseTo : BaseEntity
+ {
+ [JsonPropertyName("name")]
+ public string Name { get; set; } = string.Empty;
+ }
+}
diff --git a/351002/vorobei/BusinessLogic/DTO/Response/PostResponseTo.cs b/351002/vorobei/BusinessLogic/DTO/Response/PostResponseTo.cs
new file mode 100644
index 000000000..6561f0d4c
--- /dev/null
+++ b/351002/vorobei/BusinessLogic/DTO/Response/PostResponseTo.cs
@@ -0,0 +1,14 @@
+using System.Text.Json.Serialization;
+using DataAccess.Models;
+
+namespace BusinessLogic.DTO.Response
+{
+ public class PostResponseTo : BaseEntity
+ {
+ [JsonPropertyName("storyId")]
+ public int StoryId { get; set; }
+
+ [JsonPropertyName("content")]
+ public string Content { get; set; } = string.Empty;
+ }
+}
diff --git a/351002/vorobei/BusinessLogic/DTO/Response/StoryResponseTo.cs b/351002/vorobei/BusinessLogic/DTO/Response/StoryResponseTo.cs
new file mode 100644
index 000000000..6072b6d1e
--- /dev/null
+++ b/351002/vorobei/BusinessLogic/DTO/Response/StoryResponseTo.cs
@@ -0,0 +1,23 @@
+using System.Text.Json.Serialization;
+using DataAccess.Models;
+
+namespace BusinessLogic.DTO.Response
+{
+ public class StoryResponseTo : BaseEntity
+ {
+ [JsonPropertyName("creatorId")]
+ public int CreatorId { get; set; }
+
+ [JsonPropertyName("title")]
+ public string Title { get; set; } = string.Empty;
+
+ [JsonPropertyName("content")]
+ public string Content { get; set; } = string.Empty;
+
+ [JsonPropertyName("created")]
+ public DateTime Created { get; set; }
+
+ [JsonPropertyName("modified")]
+ public DateTime Modified { get; set; }
+ }
+}
diff --git a/351002/vorobei/BusinessLogic/Profiles/UserProfile.cs b/351002/vorobei/BusinessLogic/Profiles/UserProfile.cs
new file mode 100644
index 000000000..06e9ea238
--- /dev/null
+++ b/351002/vorobei/BusinessLogic/Profiles/UserProfile.cs
@@ -0,0 +1,25 @@
+using AutoMapper;
+using BusinessLogic.DTO.Request;
+using BusinessLogic.DTO.Response;
+using DataAccess.Models;
+
+namespace BusinessLogic.Profiles
+{
+ public class UserProfile : Profile
+ {
+ public UserProfile()
+ {
+ CreateMap();
+ CreateMap();
+
+ CreateMap();
+ CreateMap();
+
+ CreateMap();
+ CreateMap();
+
+ CreateMap();
+ CreateMap();
+ }
+ }
+}
diff --git a/351002/vorobei/BusinessLogic/Repositories/IRepository.cs b/351002/vorobei/BusinessLogic/Repositories/IRepository.cs
new file mode 100644
index 000000000..30bb65a23
--- /dev/null
+++ b/351002/vorobei/BusinessLogic/Repositories/IRepository.cs
@@ -0,0 +1,13 @@
+namespace BusinessLogic.Repositories
+{
+ public interface IRepository where TEntity : class
+ {
+ List GetAll();
+ TEntity GetById(int id);
+ TEntity Create(TEntity entity);
+ TEntity Update(TEntity entity);
+ void Delete(int id);
+ bool Exists(int id);
+ int GetLastId();
+ }
+}
diff --git a/351002/vorobei/BusinessLogic/Services/IBaseService.cs b/351002/vorobei/BusinessLogic/Services/IBaseService.cs
new file mode 100644
index 000000000..608fde653
--- /dev/null
+++ b/351002/vorobei/BusinessLogic/Services/IBaseService.cs
@@ -0,0 +1,15 @@
+using DataAccess.Models;
+using BusinessLogic.Repositories;
+
+namespace BusinessLogic.Servicies
+{
+ public interface IBaseService where TEntityRequest : class
+ where TEntityResponse : class
+ {
+ List GetAll();
+ TEntityResponse? GetById(int id);
+ TEntityResponse Create(TEntityRequest entity);
+ TEntityResponse? Update(TEntityRequest entity);
+ bool DeleteById(int id);
+ }
+}
diff --git a/351002/vorobei/DataAccess/DataAccess.csproj b/351002/vorobei/DataAccess/DataAccess.csproj
new file mode 100644
index 000000000..fa71b7ae6
--- /dev/null
+++ b/351002/vorobei/DataAccess/DataAccess.csproj
@@ -0,0 +1,9 @@
+
+
+
+ net8.0
+ enable
+ enable
+
+
+
diff --git a/351002/vorobei/DataAccess/Models/BaseEntity.cs b/351002/vorobei/DataAccess/Models/BaseEntity.cs
new file mode 100644
index 000000000..0c9dea65b
--- /dev/null
+++ b/351002/vorobei/DataAccess/Models/BaseEntity.cs
@@ -0,0 +1,10 @@
+using System.Text.Json.Serialization;
+
+namespace DataAccess.Models
+{
+ public class BaseEntity
+ {
+ [JsonPropertyName("id")]
+ public int Id { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/351002/vorobei/DataAccess/Models/Creator.cs b/351002/vorobei/DataAccess/Models/Creator.cs
new file mode 100644
index 000000000..485f4db5b
--- /dev/null
+++ b/351002/vorobei/DataAccess/Models/Creator.cs
@@ -0,0 +1,15 @@
+using System.ComponentModel.DataAnnotations;
+
+namespace DataAccess.Models
+{
+ public class Creator : BaseEntity
+ {
+ public string Login { get; set; } = string.Empty;
+
+ public string Password { get; set; } = string.Empty;
+
+ public string Firstname { get; set; } = string.Empty;
+
+ public string Lastname { get; set; } = string.Empty;
+ }
+}
diff --git a/351002/vorobei/DataAccess/Models/Mark.cs b/351002/vorobei/DataAccess/Models/Mark.cs
new file mode 100644
index 000000000..8c752ae1d
--- /dev/null
+++ b/351002/vorobei/DataAccess/Models/Mark.cs
@@ -0,0 +1,7 @@
+namespace DataAccess.Models
+{
+ public class Mark : BaseEntity
+ {
+ public string Name { get; set; } = string.Empty;
+ }
+}
diff --git a/351002/vorobei/DataAccess/Models/Post.cs b/351002/vorobei/DataAccess/Models/Post.cs
new file mode 100644
index 000000000..a668ca9ba
--- /dev/null
+++ b/351002/vorobei/DataAccess/Models/Post.cs
@@ -0,0 +1,8 @@
+namespace DataAccess.Models
+{
+ public class Post : BaseEntity
+ {
+ public int StoryId { get; set; }
+ public string Content { get; set; } = string.Empty;
+ }
+}
diff --git a/351002/vorobei/DataAccess/Models/Story.cs b/351002/vorobei/DataAccess/Models/Story.cs
new file mode 100644
index 000000000..c35053315
--- /dev/null
+++ b/351002/vorobei/DataAccess/Models/Story.cs
@@ -0,0 +1,11 @@
+namespace DataAccess.Models
+{
+ public class Story : BaseEntity
+ {
+ public int CreatorId { get; set; }
+ public string Title { get; set; } = string.Empty;
+ public string Content { get; set; } = string.Empty;
+ public DateTime Created { get; set; }
+ public DateTime Modified { get; set; }
+ }
+}
diff --git a/351002/vorobei/Infrastructure/Infrastructure.csproj b/351002/vorobei/Infrastructure/Infrastructure.csproj
new file mode 100644
index 000000000..2a9892699
--- /dev/null
+++ b/351002/vorobei/Infrastructure/Infrastructure.csproj
@@ -0,0 +1,14 @@
+
+
+
+ net8.0
+ enable
+ enable
+
+
+
+
+
+
+
+
diff --git a/351002/vorobei/Infrastructure/RepositoryImplementation/Repository.cs b/351002/vorobei/Infrastructure/RepositoryImplementation/Repository.cs
new file mode 100644
index 000000000..f631be7af
--- /dev/null
+++ b/351002/vorobei/Infrastructure/RepositoryImplementation/Repository.cs
@@ -0,0 +1,43 @@
+using BusinessLogic.Repositories;
+using DataAccess.Models;
+
+namespace Infrastructure.RepositoriesImplementation
+{
+ public class InMemoryRepository : IRepository where TEntity : BaseEntity
+ {
+ protected readonly Dictionary _entities = new();
+
+ public List GetAll()
+ {
+ return _entities.Values.ToList();
+ }
+ public TEntity GetById(int id)
+ {
+ return _entities[id];
+ }
+ public TEntity Create(TEntity entity)
+ {
+ _entities[entity.Id] = entity;
+ return entity;
+ }
+ public TEntity Update(TEntity entity)
+ {
+ _entities[entity.Id] = entity;
+ return entity;
+ }
+ public void Delete(int id)
+ {
+ _entities.Remove(id);
+ }
+ public bool Exists(int id)
+ {
+ if (_entities.ContainsKey(id))
+ return true;
+ return false;
+ }
+ public int GetLastId()
+ {
+ return _entities.Keys.DefaultIfEmpty(0).Max();
+ }
+ }
+}
diff --git a/351002/vorobei/Infrastructure/ServiceImplementation/BaseService.cs b/351002/vorobei/Infrastructure/ServiceImplementation/BaseService.cs
new file mode 100644
index 000000000..175773b29
--- /dev/null
+++ b/351002/vorobei/Infrastructure/ServiceImplementation/BaseService.cs
@@ -0,0 +1,63 @@
+using AutoMapper;
+using BusinessLogic.DTO.Response;
+using BusinessLogic.Servicies;
+using DataAccess.Models;
+using BusinessLogic.Repositories;
+
+namespace BusinessLogic.Services
+{
+ public class BaseService : IBaseService
+ where TEntity : BaseEntity
+ where TEntityRequest : class
+ where TEntityResponse : BaseEntity
+ {
+ protected readonly IRepository _repository;
+ protected readonly IMapper _mapper;
+
+ public BaseService(IRepository repository, IMapper mapper)
+ {
+ _repository = repository;
+ _mapper = mapper;
+ }
+
+ public List GetAll()
+ {
+ return _mapper.Map>(_repository.GetAll());
+ }
+ public TEntityResponse? GetById(int id)
+ {
+ if (_repository.Exists(id))
+ {
+ return _mapper.Map(_repository.GetById(id));
+ }
+ return null;
+ }
+ public bool DeleteById(int id)
+ {
+ if (_repository.Exists(id))
+ {
+ _repository.Delete(id);
+ return true;
+ }
+ return false;
+ }
+ public virtual TEntityResponse Create(TEntityRequest entity)
+ {
+ TEntity creator = _mapper.Map(entity);
+ creator.Id = _repository.GetLastId() + 1;
+ _repository.Create(creator);
+ return _mapper.Map(creator);
+ }
+ public virtual TEntityResponse? Update(TEntityRequest entity)
+ {
+ var creator = _mapper.Map(entity);
+ if (_repository.Exists(creator.Id))
+ {
+ _repository.Update(creator);
+ return _mapper.Map(creator);
+ }
+ return null;
+ }
+
+ }
+}
diff --git a/351002/vorobei/Presentation/Controllers/BaseController.cs b/351002/vorobei/Presentation/Controllers/BaseController.cs
new file mode 100644
index 000000000..a13d9c593
--- /dev/null
+++ b/351002/vorobei/Presentation/Controllers/BaseController.cs
@@ -0,0 +1,68 @@
+using Microsoft.AspNetCore.Mvc;
+using BusinessLogic.Servicies;
+
+using DataAccess.Models;
+
+namespace Presentation.Controllers
+{
+ [Route("api/v1.0/[controller]")]
+ [ApiController]
+ public abstract class BaseController : ControllerBase
+ where TEntity : BaseEntity
+ where TRequest : class
+ where TResponse : BaseEntity
+ {
+ protected readonly IBaseService _service;
+
+ protected BaseController(IBaseService service)
+ {
+ _service = service;
+ }
+
+ [HttpGet]
+ public virtual ActionResult> GetAll()
+ {
+ return Ok(_service.GetAll());
+ }
+
+ [HttpGet("{id}")]
+ public virtual ActionResult GetById(int id)
+ {
+ TResponse? response = _service.GetById(id);
+ if (response != null)
+ {
+ return Ok(response);
+ }
+ return NotFound();
+ }
+
+ [HttpPost]
+ public virtual ActionResult Create([FromBody] TRequest entity)
+ {
+ TResponse response = _service.Create(entity);
+ return Created($"{response.Id}", response);
+ }
+
+ [HttpPut]
+ public virtual ActionResult Update([FromBody] TRequest entity)
+ {
+ TResponse? response = _service.Update(entity);
+ if (response != null)
+ {
+ return Ok(response);
+ }
+ return NotFound();
+ }
+
+ [HttpDelete("{id}")]
+ public virtual ActionResult Delete(int id)
+ {
+ bool wasFound = _service.DeleteById(id);
+ if (wasFound)
+ {
+ return NoContent();
+ }
+ return NotFound();
+ }
+ }
+}
\ No newline at end of file
diff --git a/351002/vorobei/Presentation/Controllers/CreatorsController.cs b/351002/vorobei/Presentation/Controllers/CreatorsController.cs
new file mode 100644
index 000000000..3e7248ebb
--- /dev/null
+++ b/351002/vorobei/Presentation/Controllers/CreatorsController.cs
@@ -0,0 +1,17 @@
+using Microsoft.AspNetCore.Mvc;
+using BusinessLogic.Servicies;
+using BusinessLogic.DTO.Response;
+using BusinessLogic.DTO.Request;
+using DataAccess.Models;
+
+namespace Presentation.Controllers
+{
+ [Route("api/v1.0/[controller]")]
+ [ApiController]
+ public class CreatorsController : BaseController
+ {
+ public CreatorsController(IBaseService service) : base(service)
+ {
+ }
+ }
+}
diff --git a/351002/vorobei/Presentation/Controllers/MarksController.cs b/351002/vorobei/Presentation/Controllers/MarksController.cs
new file mode 100644
index 000000000..847afed25
--- /dev/null
+++ b/351002/vorobei/Presentation/Controllers/MarksController.cs
@@ -0,0 +1,17 @@
+using Microsoft.AspNetCore.Mvc;
+using BusinessLogic.Servicies;
+using BusinessLogic.DTO.Response;
+using BusinessLogic.DTO.Request;
+using DataAccess.Models;
+
+namespace Presentation.Controllers
+{
+ [Route("api/v1.0/[controller]")]
+ [ApiController]
+ public class MarksController : BaseController
+ {
+ public MarksController(IBaseService service) : base(service)
+ {
+ }
+ }
+}
diff --git a/351002/vorobei/Presentation/Controllers/PostsContoller.cs b/351002/vorobei/Presentation/Controllers/PostsContoller.cs
new file mode 100644
index 000000000..b0ea4498c
--- /dev/null
+++ b/351002/vorobei/Presentation/Controllers/PostsContoller.cs
@@ -0,0 +1,17 @@
+using BusinessLogic.DTO.Request;
+using BusinessLogic.DTO.Response;
+using BusinessLogic.Servicies;
+using DataAccess.Models;
+using Microsoft.AspNetCore.Mvc;
+
+namespace Presentation.Controllers
+{
+ [Route("api/v1.0/[controller]")]
+ [ApiController]
+ public class PostsController : BaseController
+ {
+ public PostsController(IBaseService service) : base(service)
+ {
+ }
+ }
+}
diff --git a/351002/vorobei/Presentation/Controllers/StoriesController.cs b/351002/vorobei/Presentation/Controllers/StoriesController.cs
new file mode 100644
index 000000000..8d673539a
--- /dev/null
+++ b/351002/vorobei/Presentation/Controllers/StoriesController.cs
@@ -0,0 +1,17 @@
+using BusinessLogic.DTO.Request;
+using BusinessLogic.DTO.Response;
+using BusinessLogic.Servicies;
+using DataAccess.Models;
+using Microsoft.AspNetCore.Mvc;
+
+namespace Presentation.Controllers
+{
+ [Route("api/v1.0/[controller]")]
+ [ApiController]
+ public class StoriesController : BaseController
+ {
+ public StoriesController(IBaseService service) : base(service)
+ {
+ }
+ }
+}
diff --git a/351002/vorobei/Presentation/Presentation.csproj b/351002/vorobei/Presentation/Presentation.csproj
new file mode 100644
index 000000000..c29468f7d
--- /dev/null
+++ b/351002/vorobei/Presentation/Presentation.csproj
@@ -0,0 +1,19 @@
+
+
+
+ net8.0
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/351002/vorobei/Presentation/Presentation.http b/351002/vorobei/Presentation/Presentation.http
new file mode 100644
index 000000000..8dac3c218
--- /dev/null
+++ b/351002/vorobei/Presentation/Presentation.http
@@ -0,0 +1,6 @@
+@Presentation_HostAddress = http://localhost:5232
+
+GET {{Presentation_HostAddress}}/weatherforecast/
+Accept: application/json
+
+###
diff --git a/351002/vorobei/Presentation/Program.cs b/351002/vorobei/Presentation/Program.cs
new file mode 100644
index 000000000..bbeb13495
--- /dev/null
+++ b/351002/vorobei/Presentation/Program.cs
@@ -0,0 +1,45 @@
+using BusinessLogic.DTO.Request;
+using BusinessLogic.DTO.Response;
+using BusinessLogic.Profiles;
+using BusinessLogic.Services;
+using BusinessLogic.Servicies;
+using DataAccess.Models;
+using BusinessLogic.Repositories;
+using Infrastructure.RepositoriesImplementation;
+
+var builder = WebApplication.CreateBuilder(args);
+
+builder.Services.AddControllers();
+builder.Services.AddSingleton, InMemoryRepository>();
+builder.Services.AddScoped,
+ BaseService>();
+builder.Services.AddSingleton, InMemoryRepository>();
+builder.Services.AddScoped,
+ BaseService>();
+builder.Services.AddSingleton, InMemoryRepository>();
+builder.Services.AddScoped,
+ BaseService>();
+builder.Services.AddSingleton, InMemoryRepository>();
+builder.Services.AddScoped,
+ BaseService>();
+
+builder.Services.AddAutoMapper(typeof(UserProfile));
+
+builder.Services.AddEndpointsApiExplorer();
+builder.Services.AddSwaggerGen();
+
+var app = builder.Build();
+
+if (app.Environment.IsDevelopment())
+{
+ app.UseSwagger();
+ app.UseSwaggerUI();
+}
+
+app.UseHttpsRedirection();
+
+app.UseAuthorization();
+
+app.MapControllers();
+
+app.Run();
diff --git a/351002/vorobei/Presentation/Properties/launchSettings.json b/351002/vorobei/Presentation/Properties/launchSettings.json
new file mode 100644
index 000000000..c0b247ee8
--- /dev/null
+++ b/351002/vorobei/Presentation/Properties/launchSettings.json
@@ -0,0 +1,41 @@
+{
+ "$schema": "http://json.schemastore.org/launchsettings.json",
+ "iisSettings": {
+ "windowsAuthentication": false,
+ "anonymousAuthentication": true,
+ "iisExpress": {
+ "applicationUrl": "http://localhost:8494",
+ "sslPort": 44375
+ }
+ },
+ "profiles": {
+ "http": {
+ "commandName": "Project",
+ "dotnetRunMessages": true,
+ "launchBrowser": true,
+ "launchUrl": "swagger",
+ "applicationUrl": "http://localhost:5232",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ },
+ "https": {
+ "commandName": "Project",
+ "dotnetRunMessages": true,
+ "launchBrowser": true,
+ "launchUrl": "swagger",
+ "applicationUrl": "https://localhost:7058;http://localhost:5232",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ },
+ "IIS Express": {
+ "commandName": "IISExpress",
+ "launchBrowser": true,
+ "launchUrl": "swagger",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ }
+ }
+}
diff --git a/351002/vorobei/Presentation/appsettings.Development.json b/351002/vorobei/Presentation/appsettings.Development.json
new file mode 100644
index 000000000..0c208ae91
--- /dev/null
+++ b/351002/vorobei/Presentation/appsettings.Development.json
@@ -0,0 +1,8 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft.AspNetCore": "Warning"
+ }
+ }
+}
diff --git a/351002/vorobei/Presentation/appsettings.json b/351002/vorobei/Presentation/appsettings.json
new file mode 100644
index 000000000..18bde127c
--- /dev/null
+++ b/351002/vorobei/Presentation/appsettings.json
@@ -0,0 +1,10 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft.AspNetCore": "Warning"
+ }
+ },
+ "AllowedHosts": "*",
+ "Urls": "http://localhost:24110"
+}
diff --git a/351002/vorobei/UnitTests/UnitTestCreator.cs b/351002/vorobei/UnitTests/UnitTestCreator.cs
new file mode 100644
index 000000000..65ec8252c
--- /dev/null
+++ b/351002/vorobei/UnitTests/UnitTestCreator.cs
@@ -0,0 +1,898 @@
+using BusinessLogic.DTO.Request;
+using BusinessLogic.DTO.Response;
+using BusinessLogic.Servicies;
+using Presentation.Controllers;
+using Microsoft.AspNetCore.Mvc;
+using Moq;
+
+namespace WebApp.UnitTests
+{
+ public class UnitTestCreator_ErrorCodes
+ {
+ private readonly Mock> _mockService;
+ private readonly CreatorsController _controller;
+ private readonly List _testCreators;
+
+ public UnitTestCreator_ErrorCodes()
+ {
+ _mockService = new Mock>();
+ _controller = new CreatorsController(_mockService.Object);
+ _testCreators = SeedTestData();
+ }
+
+ ///
+ /// Íà÷àëüíîå çàïîëíåíèå òåñòîâûìè äàííûìè
+ ///
+ private List SeedTestData()
+ {
+ var creators = new List
+ {
+ new CreatorResponseTo
+ {
+ Id = 1,
+ Login = "ivanov",
+ Password = "password123",
+ FirstName = "Èâàí",
+ LastName = "Èâàíîâ"
+ },
+ new CreatorResponseTo
+ {
+ Id = 2,
+ Login = "petrov",
+ Password = "password456",
+ FirstName = "Ïåòð",
+ LastName = "Ïåòðîâ"
+ },
+ new CreatorResponseTo
+ {
+ Id = 3,
+ Login = "sergeev",
+ Password = "password789",
+ FirstName = "Ñåðãåé",
+ LastName = "Ñåðãååâ"
+ }
+ };
+
+ // Íàñòðàèâàåì ìîê äëÿ ñóùåñòâóþùèõ ID
+ foreach (var creator in creators)
+ {
+ _mockService
+ .Setup(service => service.GetById(creator.Id))
+ .Returns(creator);
+ }
+
+ // Íàñòðàèâàåì ìîê äëÿ íåñóùåñòâóþùåãî ID
+ _mockService
+ .Setup(service => service.GetById(It.Is(id => id > 100)))
+ .Returns((CreatorResponseTo)null);
+
+ // Íàñòðàèâàåì ìîê äëÿ GetAll
+ _mockService
+ .Setup(service => service.GetAll())
+ .Returns(creators);
+
+ // Íàñòðàèâàåì ìîê äëÿ Update ñóùåñòâóþùèõ
+ foreach (var creator in creators)
+ {
+ var updateRequest = new CreatorRequestTo
+ {
+ Id = creator.Id,
+ Login = creator.Login,
+ Password = creator.Password,
+ FirstName = creator.FirstName,
+ LastName = creator.LastName
+ };
+
+ _mockService
+ .Setup(service => service.Update(It.Is(r => r.Id == creator.Id)))
+ .Returns(creator);
+ }
+
+ // Íàñòðàèâàåì ìîê äëÿ Update íåñóùåñòâóþùèõ
+ _mockService
+ .Setup(service => service.Update(It.Is(r => r.Id > 100)))
+ .Returns((CreatorResponseTo)null);
+
+ // Íàñòðàèâàåì ìîê äëÿ Delete ñóùåñòâóþùèõ
+ foreach (var creator in creators)
+ {
+ _mockService
+ .Setup(service => service.DeleteById(creator.Id))
+ .Returns(true);
+ }
+
+ // Íàñòðàèâàåì ìîê äëÿ Delete íåñóùåñòâóþùèõ
+ _mockService
+ .Setup(service => service.DeleteById(It.Is(id => id > 100)))
+ .Returns(false);
+
+ return creators;
+ }
+
+ #region GET Tests - Error Codes
+
+ [Fact]
+ public void GetById_NonExistingId_Returns404NotFound()
+ {
+ // Arrange
+ var nonExistingId = 999;
+
+ // Act
+ var result = _controller.GetById(nonExistingId);
+
+ // Assert
+ Assert.IsType(result.Result);
+ Assert.Equal(404, (result.Result as NotFoundResult)?.StatusCode);
+
+ // Ïðîâåðÿåì, ÷òî ñåðâèñ âûçâàëñÿ ñ ïðàâèëüíûì ID
+ _mockService.Verify(service => service.GetById(nonExistingId), Times.Once);
+ }
+
+ [Fact]
+ public void GetById_ServiceThrowsException_ThrowsException()
+ {
+ // Arrange
+ var id = 1;
+ _mockService
+ .Setup(service => service.GetById(id))
+ .Throws(new Exception("Database connection failed"));
+
+ // Act & Assert
+ var exception = Assert.Throws(() => _controller.GetById(id));
+ Assert.Equal("Database connection failed", exception.Message);
+ }
+
+ [Fact]
+ public void GetById_ExistingId_ReturnsOkWithCreator()
+ {
+ // Arrange
+ var existingId = 1;
+ var expectedCreator = _testCreators.First(c => c.Id == existingId);
+
+ // Act
+ var result = _controller.GetById(existingId);
+
+ // Assert
+ var okResult = Assert.IsType(result.Result);
+ Assert.Equal(200, okResult.StatusCode);
+
+ var returnedCreator = Assert.IsType(okResult.Value);
+ Assert.Equal(expectedCreator.Id, returnedCreator.Id);
+ Assert.Equal(expectedCreator.Login, returnedCreator.Login);
+ Assert.Equal(expectedCreator.FirstName, returnedCreator.FirstName);
+ Assert.Equal(expectedCreator.LastName, returnedCreator.LastName);
+
+ _mockService.Verify(service => service.GetById(existingId), Times.Once);
+ }
+
+ [Fact]
+ public void GetAll_ReturnsOkWithAllCreators()
+ {
+ // Act
+ var result = _controller.GetAll();
+
+ // Assert
+ var okResult = Assert.IsType(result.Result);
+ Assert.Equal(200, okResult.StatusCode);
+
+ var returnedCreators = Assert.IsType>(okResult.Value);
+ Assert.Equal(_testCreators.Count, returnedCreators.Count);
+
+ for (int i = 0; i < _testCreators.Count; i++)
+ {
+ Assert.Equal(_testCreators[i].Id, returnedCreators[i].Id);
+ Assert.Equal(_testCreators[i].Login, returnedCreators[i].Login);
+ }
+
+ _mockService.Verify(service => service.GetAll(), Times.Once);
+ }
+
+ #endregion
+
+ #region CREATE Tests - Error Codes
+
+ [Fact]
+ public void Create_ValidData_ReturnsCreated()
+ {
+ // Arrange
+ var newRequest = new CreatorRequestTo
+ {
+ Login = "newuser",
+ Password = "newpassword123",
+ FirstName = "Íîâûé",
+ LastName = "Ïîëüçîâàòåëü"
+ };
+
+ var createdResponse = new CreatorResponseTo
+ {
+ Id = 4,
+ Login = newRequest.Login,
+ Password = newRequest.Password,
+ FirstName = newRequest.FirstName,
+ LastName = newRequest.LastName
+ };
+
+ _mockService
+ .Setup(service => service.Create(newRequest))
+ .Returns(createdResponse);
+
+ // Act
+ var result = _controller.Create(newRequest);
+
+ // Assert
+ var createdResult = Assert.IsType(result.Result);
+ Assert.Equal(201, createdResult.StatusCode);
+ Assert.Equal($"{createdResponse.Id}", createdResult.Location);
+
+ var returnedCreator = Assert.IsType(createdResult.Value);
+ Assert.Equal(createdResponse.Id, returnedCreator.Id);
+ Assert.Equal(createdResponse.Login, returnedCreator.Login);
+
+ _mockService.Verify(service => service.Create(newRequest), Times.Once);
+ }
+
+ [Fact]
+ public void Create_LoginTooShort_ThrowsException()
+ {
+ // Arrange
+ var invalidRequest = new CreatorRequestTo
+ {
+ Login = "a",
+ Password = "password123",
+ FirstName = "Èâàí",
+ LastName = "Èâàíîâ"
+ };
+
+ _mockService
+ .Setup(service => service.Create(invalidRequest))
+ .Throws(new Exception("Login should be from 2 to 64 symbols"));
+
+ // Act & Assert
+ var exception = Assert.Throws(() => _controller.Create(invalidRequest));
+ Assert.Equal("Login should be from 2 to 64 symbols", exception.Message);
+
+ _mockService.Verify(service => service.Create(invalidRequest), Times.Once);
+ }
+
+ [Fact]
+ public void Create_LoginTooLong_ThrowsException()
+ {
+ // Arrange
+ var invalidRequest = new CreatorRequestTo
+ {
+ Login = new string('a', 65),
+ Password = "password123",
+ FirstName = "Èâàí",
+ LastName = "Èâàíîâ"
+ };
+
+ _mockService
+ .Setup(service => service.Create(invalidRequest))
+ .Throws(new Exception("Login should be from 2 to 64 symbols"));
+
+ // Act & Assert
+ var exception = Assert.Throws(() => _controller.Create(invalidRequest));
+ Assert.Equal("Login should be from 2 to 64 symbols", exception.Message);
+
+ _mockService.Verify(service => service.Create(invalidRequest), Times.Once);
+ }
+
+ [Fact]
+ public void Create_PasswordTooShort_ThrowsException()
+ {
+ // Arrange
+ var invalidRequest = new CreatorRequestTo
+ {
+ Login = "validlogin",
+ Password = "short",
+ FirstName = "Èâàí",
+ LastName = "Èâàíîâ"
+ };
+
+ _mockService
+ .Setup(service => service.Create(invalidRequest))
+ .Throws(new Exception("Password should be from 8 to 128 symbols"));
+
+ // Act & Assert
+ var exception = Assert.Throws(() => _controller.Create(invalidRequest));
+ Assert.Equal("Password should be from 8 to 128 symbols", exception.Message);
+
+ _mockService.Verify(service => service.Create(invalidRequest), Times.Once);
+ }
+
+ [Fact]
+ public void Create_PasswordTooLong_ThrowsException()
+ {
+ // Arrange
+ var invalidRequest = new CreatorRequestTo
+ {
+ Login = "validlogin",
+ Password = new string('p', 129),
+ FirstName = "Èâàí",
+ LastName = "Èâàíîâ"
+ };
+
+ _mockService
+ .Setup(service => service.Create(invalidRequest))
+ .Throws(new Exception("Password should be from 8 to 128 symbols"));
+
+ // Act & Assert
+ var exception = Assert.Throws(() => _controller.Create(invalidRequest));
+ Assert.Equal("Password should be from 8 to 128 symbols", exception.Message);
+
+ _mockService.Verify(service => service.Create(invalidRequest), Times.Once);
+ }
+
+ [Fact]
+ public void Create_FirstNameTooShort_ThrowsException()
+ {
+ // Arrange
+ var invalidRequest = new CreatorRequestTo
+ {
+ Login = "validlogin",
+ Password = "password123",
+ FirstName = "I",
+ LastName = "Èâàíîâ"
+ };
+
+ _mockService
+ .Setup(service => service.Create(invalidRequest))
+ .Throws(new Exception("FirstName should be from 2 to 64 symbols"));
+
+ // Act & Assert
+ var exception = Assert.Throws(() => _controller.Create(invalidRequest));
+ Assert.Equal("FirstName should be from 2 to 64 symbols", exception.Message);
+
+ _mockService.Verify(service => service.Create(invalidRequest), Times.Once);
+ }
+
+ [Fact]
+ public void Create_FirstNameTooLong_ThrowsException()
+ {
+ // Arrange
+ var invalidRequest = new CreatorRequestTo
+ {
+ Login = "validlogin",
+ Password = "password123",
+ FirstName = new string('I', 65),
+ LastName = "Èâàíîâ"
+ };
+
+ _mockService
+ .Setup(service => service.Create(invalidRequest))
+ .Throws(new Exception("FirstName should be from 2 to 64 symbols"));
+
+ // Act & Assert
+ var exception = Assert.Throws(() => _controller.Create(invalidRequest));
+ Assert.Equal("FirstName should be from 2 to 64 symbols", exception.Message);
+
+ _mockService.Verify(service => service.Create(invalidRequest), Times.Once);
+ }
+
+ [Fact]
+ public void Create_LastNameTooShort_ThrowsException()
+ {
+ // Arrange
+ var invalidRequest = new CreatorRequestTo
+ {
+ Login = "validlogin",
+ Password = "password123",
+ FirstName = "Èâàí",
+ LastName = "P"
+ };
+
+ _mockService
+ .Setup(service => service.Create(invalidRequest))
+ .Throws(new Exception("LastName should be from 2 to 64 symbols"));
+
+ // Act & Assert
+ var exception = Assert.Throws(() => _controller.Create(invalidRequest));
+ Assert.Equal("LastName should be from 2 to 64 symbols", exception.Message);
+
+ _mockService.Verify(service => service.Create(invalidRequest), Times.Once);
+ }
+
+ [Fact]
+ public void Create_LastNameTooLong_ThrowsException()
+ {
+ // Arrange
+ var invalidRequest = new CreatorRequestTo
+ {
+ Login = "validlogin",
+ Password = "password123",
+ FirstName = "Èâàí",
+ LastName = new string('P', 65)
+ };
+
+ _mockService
+ .Setup(service => service.Create(invalidRequest))
+ .Throws(new Exception("LastName should be from 2 to 64 symbols"));
+
+ // Act & Assert
+ var exception = Assert.Throws(() => _controller.Create(invalidRequest));
+ Assert.Equal("LastName should be from 2 to 64 symbols", exception.Message);
+
+ _mockService.Verify(service => service.Create(invalidRequest), Times.Once);
+ }
+
+ [Fact]
+ public void Create_LoginEmpty_ThrowsException()
+ {
+ // Arrange
+ var invalidRequest = new CreatorRequestTo
+ {
+ Login = "",
+ Password = "password123",
+ FirstName = "Èâàí",
+ LastName = "Èâàíîâ"
+ };
+
+ _mockService
+ .Setup(service => service.Create(invalidRequest))
+ .Throws(new Exception("Login is required"));
+
+ // Act & Assert
+ var exception = Assert.Throws(() => _controller.Create(invalidRequest));
+ Assert.Equal("Login is required", exception.Message);
+
+ _mockService.Verify(service => service.Create(invalidRequest), Times.Once);
+ }
+
+ [Fact]
+ public void Create_PasswordEmpty_ThrowsException()
+ {
+ // Arrange
+ var invalidRequest = new CreatorRequestTo
+ {
+ Login = "validlogin",
+ Password = "",
+ FirstName = "Èâàí",
+ LastName = "Èâàíîâ"
+ };
+
+ _mockService
+ .Setup(service => service.Create(invalidRequest))
+ .Throws(new Exception("Password is required"));
+
+ // Act & Assert
+ var exception = Assert.Throws(() => _controller.Create(invalidRequest));
+ Assert.Equal("Password is required", exception.Message);
+
+ _mockService.Verify(service => service.Create(invalidRequest), Times.Once);
+ }
+
+ [Fact]
+ public void Create_FirstNameEmpty_ThrowsException()
+ {
+ // Arrange
+ var invalidRequest = new CreatorRequestTo
+ {
+ Login = "validlogin",
+ Password = "password123",
+ FirstName = "",
+ LastName = "Èâàíîâ"
+ };
+
+ _mockService
+ .Setup(service => service.Create(invalidRequest))
+ .Throws(new Exception("FirstName is required"));
+
+ // Act & Assert
+ var exception = Assert.Throws(() => _controller.Create(invalidRequest));
+ Assert.Equal("FirstName is required", exception.Message);
+
+ _mockService.Verify(service => service.Create(invalidRequest), Times.Once);
+ }
+
+ [Fact]
+ public void Create_LastNameEmpty_ThrowsException()
+ {
+ // Arrange
+ var invalidRequest = new CreatorRequestTo
+ {
+ Login = "validlogin",
+ Password = "password123",
+ FirstName = "Èâàí",
+ LastName = ""
+ };
+
+ _mockService
+ .Setup(service => service.Create(invalidRequest))
+ .Throws(new Exception("LastName is required"));
+
+ // Act & Assert
+ var exception = Assert.Throws(() => _controller.Create(invalidRequest));
+ Assert.Equal("LastName is required", exception.Message);
+
+ _mockService.Verify(service => service.Create(invalidRequest), Times.Once);
+ }
+
+ [Fact]
+ public void Create_MultipleErrors_ThrowsException()
+ {
+ // Arrange
+ var invalidRequest = new CreatorRequestTo
+ {
+ Login = "a",
+ Password = "short",
+ FirstName = "",
+ LastName = "P"
+ };
+
+ _mockService
+ .Setup(service => service.Create(invalidRequest))
+ .Throws(new Exception("Validation failed: Login too short, Password too short, FirstName required, LastName too short"));
+
+ // Act & Assert
+ var exception = Assert.Throws(() => _controller.Create(invalidRequest));
+ Assert.Contains("Validation failed", exception.Message);
+
+ _mockService.Verify(service => service.Create(invalidRequest), Times.Once);
+ }
+
+ [Fact]
+ public void Create_ServiceThrowsException_ThrowsException()
+ {
+ // Arrange
+ var validRequest = new CreatorRequestTo
+ {
+ Login = "validlogin",
+ Password = "password123",
+ FirstName = "Èâàí",
+ LastName = "Èâàíîâ"
+ };
+
+ _mockService
+ .Setup(service => service.Create(validRequest))
+ .Throws(new Exception("Database error"));
+
+ // Act & Assert
+ var exception = Assert.Throws(() => _controller.Create(validRequest));
+ Assert.Equal("Database error", exception.Message);
+
+ _mockService.Verify(service => service.Create(validRequest), Times.Once);
+ }
+
+ #endregion
+
+ #region UPDATE Tests - Error Codes
+
+ [Fact]
+ public void Update_ExistingId_ReturnsOkWithUpdatedCreator()
+ {
+ // Arrange
+ var existingId = 1;
+ var updateRequest = new CreatorRequestTo
+ {
+ Id = existingId,
+ Login = "ivanov_updated",
+ Password = "newpassword123",
+ FirstName = "Èâàí",
+ LastName = "Èâàíîâ"
+ };
+
+ var updatedResponse = new CreatorResponseTo
+ {
+ Id = existingId,
+ Login = updateRequest.Login,
+ Password = updateRequest.Password,
+ FirstName = updateRequest.FirstName,
+ LastName = updateRequest.LastName
+ };
+
+ _mockService
+ .Setup(service => service.Update(updateRequest))
+ .Returns(updatedResponse);
+
+ // Act
+ var result = _controller.Update(updateRequest);
+
+ // Assert
+ var okResult = Assert.IsType(result.Result);
+ Assert.Equal(200, okResult.StatusCode);
+
+ var returnedCreator = Assert.IsType(okResult.Value);
+ Assert.Equal(updateRequest.Id, returnedCreator.Id);
+ Assert.Equal(updateRequest.Login, returnedCreator.Login);
+
+ _mockService.Verify(service => service.Update(updateRequest), Times.Once);
+ }
+
+ [Fact]
+ public void Update_NonExistingId_Returns404NotFound()
+ {
+ // Arrange
+ var nonExistingRequest = new CreatorRequestTo
+ {
+ Id = 999,
+ Login = "validlogin",
+ Password = "password123",
+ FirstName = "Èâàí",
+ LastName = "Èâàíîâ"
+ };
+
+ _mockService
+ .Setup(service => service.Update(nonExistingRequest))
+ .Returns((CreatorResponseTo)null);
+
+ // Act
+ var result = _controller.Update(nonExistingRequest);
+
+ // Assert
+ Assert.IsType(result.Result);
+ Assert.Equal(404, (result.Result as NotFoundResult)?.StatusCode);
+
+ _mockService.Verify(service => service.Update(nonExistingRequest), Times.Once);
+ }
+
+ [Fact]
+ public void Update_LoginTooShort_ThrowsException()
+ {
+ // Arrange
+ var invalidRequest = new CreatorRequestTo
+ {
+ Id = 1,
+ Login = "a",
+ Password = "password123",
+ FirstName = "Èâàí",
+ LastName = "Èâàíîâ"
+ };
+
+ _mockService
+ .Setup(service => service.Update(invalidRequest))
+ .Throws(new Exception("Login should be from 2 to 64 symbols"));
+
+ // Act & Assert
+ var exception = Assert.Throws(() => _controller.Update(invalidRequest));
+ Assert.Equal("Login should be from 2 to 64 symbols", exception.Message);
+
+ _mockService.Verify(service => service.Update(invalidRequest), Times.Once);
+ }
+
+ [Fact]
+ public void Update_LoginTooLong_ThrowsException()
+ {
+ // Arrange
+ var invalidRequest = new CreatorRequestTo
+ {
+ Id = 1,
+ Login = new string('a', 65),
+ Password = "password123",
+ FirstName = "Èâàí",
+ LastName = "Èâàíîâ"
+ };
+
+ _mockService
+ .Setup(service => service.Update(invalidRequest))
+ .Throws(new Exception("Login should be from 2 to 64 symbols"));
+
+ // Act & Assert
+ var exception = Assert.Throws(() => _controller.Update(invalidRequest));
+ Assert.Equal("Login should be from 2 to 64 symbols", exception.Message);
+
+ _mockService.Verify(service => service.Update(invalidRequest), Times.Once);
+ }
+
+ [Fact]
+ public void Update_PasswordTooShort_ThrowsException()
+ {
+ // Arrange
+ var invalidRequest = new CreatorRequestTo
+ {
+ Id = 1,
+ Login = "validlogin",
+ Password = "short",
+ FirstName = "Èâàí",
+ LastName = "Èâàíîâ"
+ };
+
+ _mockService
+ .Setup(service => service.Update(invalidRequest))
+ .Throws(new Exception("Password should be from 8 to 128 symbols"));
+
+ // Act & Assert
+ var exception = Assert.Throws(() => _controller.Update(invalidRequest));
+ Assert.Equal("Password should be from 8 to 128 symbols", exception.Message);
+
+ _mockService.Verify(service => service.Update(invalidRequest), Times.Once);
+ }
+
+ [Fact]
+ public void Update_PasswordTooLong_ThrowsException()
+ {
+ // Arrange
+ var invalidRequest = new CreatorRequestTo
+ {
+ Id = 1,
+ Login = "validlogin",
+ Password = new string('p', 129),
+ FirstName = "Èâàí",
+ LastName = "Èâàíîâ"
+ };
+
+ _mockService
+ .Setup(service => service.Update(invalidRequest))
+ .Throws(new Exception("Password should be from 8 to 128 symbols"));
+
+ // Act & Assert
+ var exception = Assert.Throws(() => _controller.Update(invalidRequest));
+ Assert.Equal("Password should be from 8 to 128 symbols", exception.Message);
+
+ _mockService.Verify(service => service.Update(invalidRequest), Times.Once);
+ }
+
+ [Fact]
+ public void Update_FirstNameTooShort_ThrowsException()
+ {
+ // Arrange
+ var invalidRequest = new CreatorRequestTo
+ {
+ Id = 1,
+ Login = "validlogin",
+ Password = "password123",
+ FirstName = "I",
+ LastName = "Èâàíîâ"
+ };
+
+ _mockService
+ .Setup(service => service.Update(invalidRequest))
+ .Throws(new Exception("FirstName should be from 2 to 64 symbols"));
+
+ // Act & Assert
+ var exception = Assert.Throws(() => _controller.Update(invalidRequest));
+ Assert.Equal("FirstName should be from 2 to 64 symbols", exception.Message);
+
+ _mockService.Verify(service => service.Update(invalidRequest), Times.Once);
+ }
+
+ [Fact]
+ public void Update_FirstNameTooLong_ThrowsException()
+ {
+ // Arrange
+ var invalidRequest = new CreatorRequestTo
+ {
+ Id = 1,
+ Login = "validlogin",
+ Password = "password123",
+ FirstName = new string('I', 65),
+ LastName = "Èâàíîâ"
+ };
+
+ _mockService
+ .Setup(service => service.Update(invalidRequest))
+ .Throws(new Exception("FirstName should be from 2 to 64 symbols"));
+
+ // Act & Assert
+ var exception = Assert.Throws(() => _controller.Update(invalidRequest));
+ Assert.Equal("FirstName should be from 2 to 64 symbols", exception.Message);
+
+ _mockService.Verify(service => service.Update(invalidRequest), Times.Once);
+ }
+
+ [Fact]
+ public void Update_LastNameTooShort_ThrowsException()
+ {
+ // Arrange
+ var invalidRequest = new CreatorRequestTo
+ {
+ Id = 1,
+ Login = "validlogin",
+ Password = "password123",
+ FirstName = "Èâàí",
+ LastName = "P"
+ };
+
+ _mockService
+ .Setup(service => service.Update(invalidRequest))
+ .Throws(new Exception("LastName should be from 2 to 64 symbols"));
+
+ // Act & Assert
+ var exception = Assert.Throws(() => _controller.Update(invalidRequest));
+ Assert.Equal("LastName should be from 2 to 64 symbols", exception.Message);
+
+ _mockService.Verify(service => service.Update(invalidRequest), Times.Once);
+ }
+
+ [Fact]
+ public void Update_LastNameTooLong_ThrowsException()
+ {
+ // Arrange
+ var invalidRequest = new CreatorRequestTo
+ {
+ Id = 1,
+ Login = "validlogin",
+ Password = "password123",
+ FirstName = "Èâàí",
+ LastName = new string('P', 65)
+ };
+
+ _mockService
+ .Setup(service => service.Update(invalidRequest))
+ .Throws(new Exception("LastName should be from 2 to 64 symbols"));
+
+ // Act & Assert
+ var exception = Assert.Throws(() => _controller.Update(invalidRequest));
+ Assert.Equal("LastName should be from 2 to 64 symbols", exception.Message);
+
+ _mockService.Verify(service => service.Update(invalidRequest), Times.Once);
+ }
+
+ [Fact]
+ public void Update_ServiceThrowsException_ThrowsException()
+ {
+ // Arrange
+ var validRequest = new CreatorRequestTo
+ {
+ Id = 1,
+ Login = "validlogin",
+ Password = "password123",
+ FirstName = "Èâàí",
+ LastName = "Èâàíîâ"
+ };
+
+ _mockService
+ .Setup(service => service.Update(validRequest))
+ .Throws(new Exception("Database error"));
+
+ // Act & Assert
+ var exception = Assert.Throws(() => _controller.Update(validRequest));
+ Assert.Equal("Database error", exception.Message);
+
+ _mockService.Verify(service => service.Update(validRequest), Times.Once);
+ }
+
+ #endregion
+
+ #region DELETE Tests - Error Codes
+
+ [Fact]
+ public void Delete_ExistingId_ReturnsNoContent()
+ {
+ // Arrange
+ var existingId = 1;
+
+ // Act
+ var result = _controller.Delete(existingId);
+
+ // Assert
+ Assert.IsType(result);
+ Assert.Equal(204, (result as NoContentResult)?.StatusCode);
+
+ _mockService.Verify(service => service.DeleteById(existingId), Times.Once);
+ }
+
+ [Fact]
+ public void Delete_NonExistingId_Returns404NotFound()
+ {
+ // Arrange
+ var nonExistingId = 999;
+
+ // Act
+ var result = _controller.Delete(nonExistingId);
+
+ // Assert
+ Assert.IsType(result);
+ Assert.Equal(404, (result as NotFoundResult)?.StatusCode);
+
+ _mockService.Verify(service => service.DeleteById(nonExistingId), Times.Once);
+ }
+
+ [Fact]
+ public void Delete_ServiceThrowsException_ThrowsException()
+ {
+ // Arrange
+ var id = 1;
+ _mockService
+ .Setup(service => service.DeleteById(id))
+ .Throws(new Exception("Database error"));
+
+ // Act & Assert
+ var exception = Assert.Throws(() => _controller.Delete(id));
+ Assert.Equal("Database error", exception.Message);
+
+ _mockService.Verify(service => service.DeleteById(id), Times.Once);
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/351002/vorobei/UnitTests/WebApp.UnitTests.csproj b/351002/vorobei/UnitTests/WebApp.UnitTests.csproj
new file mode 100644
index 000000000..b096817a2
--- /dev/null
+++ b/351002/vorobei/UnitTests/WebApp.UnitTests.csproj
@@ -0,0 +1,28 @@
+
+
+
+ net8.0
+ enable
+ enable
+
+ false
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/351002/vorobei/WebApp.sln b/351002/vorobei/WebApp.sln
new file mode 100644
index 000000000..a134dea80
--- /dev/null
+++ b/351002/vorobei/WebApp.sln
@@ -0,0 +1,49 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.14.36616.10
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Presentation", "Presentation\Presentation.csproj", "{735C42DA-2A37-48A5-B38F-B2CC33617528}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DataAccess", "DataAccess\DataAccess.csproj", "{4CD6B562-75DE-4AAB-8E20-2AC547F25B0A}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BusinessLogic", "BusinessLogic\BusinessLogic.csproj", "{B0EC1074-F788-4EE8-9452-9FFAA6FD8800}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Infrastructure", "Infrastructure\Infrastructure.csproj", "{DE6A933D-FA17-438E-B0FD-896F594D42F4}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebApp.UnitTests", "UnitTests\WebApp.UnitTests.csproj", "{BEB950EC-98C7-4752-A8AC-E92727EE64A7}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {735C42DA-2A37-48A5-B38F-B2CC33617528}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {735C42DA-2A37-48A5-B38F-B2CC33617528}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {735C42DA-2A37-48A5-B38F-B2CC33617528}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {735C42DA-2A37-48A5-B38F-B2CC33617528}.Release|Any CPU.Build.0 = Release|Any CPU
+ {4CD6B562-75DE-4AAB-8E20-2AC547F25B0A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {4CD6B562-75DE-4AAB-8E20-2AC547F25B0A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {4CD6B562-75DE-4AAB-8E20-2AC547F25B0A}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {4CD6B562-75DE-4AAB-8E20-2AC547F25B0A}.Release|Any CPU.Build.0 = Release|Any CPU
+ {B0EC1074-F788-4EE8-9452-9FFAA6FD8800}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {B0EC1074-F788-4EE8-9452-9FFAA6FD8800}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B0EC1074-F788-4EE8-9452-9FFAA6FD8800}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {B0EC1074-F788-4EE8-9452-9FFAA6FD8800}.Release|Any CPU.Build.0 = Release|Any CPU
+ {DE6A933D-FA17-438E-B0FD-896F594D42F4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {DE6A933D-FA17-438E-B0FD-896F594D42F4}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {DE6A933D-FA17-438E-B0FD-896F594D42F4}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {DE6A933D-FA17-438E-B0FD-896F594D42F4}.Release|Any CPU.Build.0 = Release|Any CPU
+ {BEB950EC-98C7-4752-A8AC-E92727EE64A7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {BEB950EC-98C7-4752-A8AC-E92727EE64A7}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {BEB950EC-98C7-4752-A8AC-E92727EE64A7}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {BEB950EC-98C7-4752-A8AC-E92727EE64A7}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {EDAB7B55-C210-4D69-9A6E-9D9C20A1ADC2}
+ EndGlobalSection
+EndGlobal