From 76dde1465a67bd37eb5ed2a3a3c54c3112f7182b Mon Sep 17 00:00:00 2001 From: HlibBondarev Date: Mon, 2 Dec 2024 17:49:03 +0200 Subject: [PATCH 01/28] Divided WorkshopCreatRequestDto class into 5 classes (WorkshopMainRequiredPropertiesDto, WorkshopRequiredPropertiesDto, WorkshopDescriptionDto, WorkshopContactsDto, WorkshopStaffDto) and added polymorphism between them. --- .../Workshops/Drafts/WorkshopContactsDto.cs | 26 +++ .../Drafts/WorkshopDescriptionDto.cs | 40 ++++ .../WorkshopMainRequiredPropertiesDto.cs | 106 ++++++++++ .../Drafts/WorkshopRequiredPropertiesDto.cs | 40 ++++ .../Workshops/Drafts/WorkshopStaffDto.cs | 15 ++ .../Workshops/WorkshopCreateRequestDto.cs | 187 +----------------- 6 files changed, 229 insertions(+), 185 deletions(-) create mode 100644 OutOfSchool/OutOfSchool.BusinessLogic/Models/Workshops/Drafts/WorkshopContactsDto.cs create mode 100644 OutOfSchool/OutOfSchool.BusinessLogic/Models/Workshops/Drafts/WorkshopDescriptionDto.cs create mode 100644 OutOfSchool/OutOfSchool.BusinessLogic/Models/Workshops/Drafts/WorkshopMainRequiredPropertiesDto.cs create mode 100644 OutOfSchool/OutOfSchool.BusinessLogic/Models/Workshops/Drafts/WorkshopRequiredPropertiesDto.cs create mode 100644 OutOfSchool/OutOfSchool.BusinessLogic/Models/Workshops/Drafts/WorkshopStaffDto.cs diff --git a/OutOfSchool/OutOfSchool.BusinessLogic/Models/Workshops/Drafts/WorkshopContactsDto.cs b/OutOfSchool/OutOfSchool.BusinessLogic/Models/Workshops/Drafts/WorkshopContactsDto.cs new file mode 100644 index 000000000..45c37c2e3 --- /dev/null +++ b/OutOfSchool/OutOfSchool.BusinessLogic/Models/Workshops/Drafts/WorkshopContactsDto.cs @@ -0,0 +1,26 @@ +using System.ComponentModel.DataAnnotations; +using Microsoft.AspNetCore.Mvc; +using OutOfSchool.BusinessLogic.Util.JsonTools; + +namespace OutOfSchool.BusinessLogic.Models.Workshops.Drafts; +public class WorkshopContactsDto : WorkshopDescriptionDto +{ + [DataType(DataType.Url)] + [MaxLength(Constants.MaxUnifiedUrlLength)] + public string Website { get; set; } = string.Empty; + + [DataType(DataType.Url)] + [MaxLength(Constants.MaxUnifiedUrlLength)] + public string Facebook { get; set; } = string.Empty; + + [DataType(DataType.Url)] + [MaxLength(Constants.MaxUnifiedUrlLength)] + public string Instagram { get; set; } = string.Empty; + + [Required] + public long AddressId { get; set; } + + [Required] + [ModelBinder(BinderType = typeof(JsonModelBinder))] + public AddressDto Address { get; set; } +} diff --git a/OutOfSchool/OutOfSchool.BusinessLogic/Models/Workshops/Drafts/WorkshopDescriptionDto.cs b/OutOfSchool/OutOfSchool.BusinessLogic/Models/Workshops/Drafts/WorkshopDescriptionDto.cs new file mode 100644 index 000000000..7df53cd47 --- /dev/null +++ b/OutOfSchool/OutOfSchool.BusinessLogic/Models/Workshops/Drafts/WorkshopDescriptionDto.cs @@ -0,0 +1,40 @@ +using System.ComponentModel.DataAnnotations; +using Microsoft.AspNetCore.Mvc; +using OutOfSchool.BusinessLogic.Util.CustomValidation; +using OutOfSchool.BusinessLogic.Util.JsonTools; + +namespace OutOfSchool.BusinessLogic.Models.Workshops.Drafts; + +public class WorkshopDescriptionDto : WorkshopRequiredPropertiesDto +{ + [ModelBinder(BinderType = typeof(JsonModelBinder))] + [CollectionNotEmpty(ErrorMessage = "At least one description item is required")] + public IEnumerable WorkshopDescriptionItems { get; set; } + + public bool WithDisabilityOptions { get; set; } = default; + + [MaxLength(200)] + public string DisabilityOptionsDesc { get; set; } = string.Empty; + + public Guid? InstitutionId { get; set; } + + public Guid? InstitutionHierarchyId { get; set; } + + public List DirectionIds { get; set; } + + [ModelBinder(BinderType = typeof(JsonModelBinder))] + public IEnumerable Keywords { get; set; } = default; + + [MaxLength(500)] + public string AdditionalDescription { get; set; } + + public bool AreThereBenefits { get; set; } = default; + + [MaxLength(500)] + public string PreferentialTermsOfParticipation { get; set; } + + public uint CoverageId { get; set; } = 0; + + [ModelBinder(BinderType = typeof(JsonModelBinder))] + public List TagIds { get; set; } = []; +} diff --git a/OutOfSchool/OutOfSchool.BusinessLogic/Models/Workshops/Drafts/WorkshopMainRequiredPropertiesDto.cs b/OutOfSchool/OutOfSchool.BusinessLogic/Models/Workshops/Drafts/WorkshopMainRequiredPropertiesDto.cs new file mode 100644 index 000000000..7c017cdac --- /dev/null +++ b/OutOfSchool/OutOfSchool.BusinessLogic/Models/Workshops/Drafts/WorkshopMainRequiredPropertiesDto.cs @@ -0,0 +1,106 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Text.Json.Serialization; +using Microsoft.AspNetCore.Mvc; +using OutOfSchool.BusinessLogic.Util.CustomValidation; +using OutOfSchool.BusinessLogic.Util.JsonTools; +using OutOfSchool.Common.Enums; +using OutOfSchool.Common.Validators; +using OutOfSchool.Services.Enums; + +namespace OutOfSchool.BusinessLogic.Models.Workshops.Drafts; + +[JsonDerivedType(typeof(WorkshopMainRequiredPropertiesDto), typeDiscriminator: "withMainProperties")] +[JsonDerivedType(typeof(WorkshopRequiredPropertiesDto), typeDiscriminator: "withOtherRequiredProperties")] +[JsonDerivedType(typeof(WorkshopDescriptionDto), typeDiscriminator: "withDescription")] +[JsonDerivedType(typeof(WorkshopContactsDto), typeDiscriminator: "withContacts")] +[JsonDerivedType(typeof(WorkshopStaffDto), typeDiscriminator: "withStaff")] +public class WorkshopMainRequiredPropertiesDto : IValidatableObject //16 + 12 + 12 + 5 + 3 = 48 +{ + public Guid Id { get; set; } + + [Required(ErrorMessage = "Workshop title is required")] + [MinLength(Constants.MinWorkshopTitleLength)] + [MaxLength(Constants.MaxWorkshopTitleLength)] + public string Title { get; set; } = string.Empty; + + [Required(ErrorMessage = "Workshop short title is required")] + [MinLength(Constants.MinWorkshopShortTitleLength)] + [MaxLength(Constants.MaxWorkshopShortTitleLength)] + public string ShortTitle { get; set; } = string.Empty; + + [DataType(DataType.PhoneNumber)] + [Required(ErrorMessage = "Phone number is required")] + [CustomPhoneNumber(ErrorMessage = Constants.PhoneErrorMessage)] + [DisplayFormat(DataFormatString = Constants.PhoneNumberFormat)] + public string Phone { get; set; } = string.Empty; + + [DataType(DataType.EmailAddress)] + [Required(ErrorMessage = "Email is required")] + [MaxLength(256)] + public string Email { get; set; } = string.Empty; + + [Required(ErrorMessage = "Children's min age is required")] + [Range(0, 120, ErrorMessage = "Min age should be a number from 0 to 120")] + public int MinAge { get; set; } + + [Required(ErrorMessage = "Children's max age is required")] + [Range(0, 120, ErrorMessage = "Max age should be a number from 0 to 120")] + public int MaxAge { get; set; } + + [ModelBinder(BinderType = typeof(JsonModelBinder))] + [CollectionNotEmpty(ErrorMessage = "At least one DateTime range is required")] + public List DateTimeRanges { get; set; } + + [Required(ErrorMessage = "Form of learning is required")] + [EnumDataType(typeof(FormOfLearning), ErrorMessage = Constants.EnumErrorMessage)] + public FormOfLearning FormOfLearning { get; set; } = FormOfLearning.Offline; + + [Required(ErrorMessage = "Property IsPaid is required")] + public bool IsPaid { get; set; } = false; + + [Column(TypeName = "decimal(18,2)")] + [Range(0, 100000, ErrorMessage = "Field value should be in a range from 1 to 100 000")] + public decimal? Price { get; set; } = default; + + [EnumDataType(typeof(PayRateType), ErrorMessage = Constants.EnumErrorMessage)] + public PayRateType? PayRate { get; set; } = PayRateType.Classes; + + [Required(ErrorMessage = "Available seats are required")] + public uint? AvailableSeats { get; set; } = uint.MaxValue; + + [Required(ErrorMessage = "Property CompetitiveSelection is required")] + public bool CompetitiveSelection { get; set; } + + [MaxLength(500)] + public string CompetitiveSelectionDescription { get; set; } + + [Required] + public Guid ProviderId { get; set; } + + public IEnumerable Validate(ValidationContext validationContext) + { + // TODO: Validate DateTimeRanges are not empty when frontend is ready + foreach (var dateTimeRange in DateTimeRanges) + { + if (dateTimeRange.StartTime > dateTimeRange.EndTime) + { + yield return new ValidationResult( + "End date can't be earlier that start date"); + } + + if (dateTimeRange.Workdays.IsNullOrEmpty() || dateTimeRange.Workdays.Any(workday => workday == DaysBitMask.None)) + { + yield return new ValidationResult( + "Workdays are required"); + } + + var daysHs = new HashSet(); + if (!dateTimeRange.Workdays.All(daysHs.Add)) + { + yield return new ValidationResult( + "Workdays contain duplications"); + } + } + } +} \ No newline at end of file diff --git a/OutOfSchool/OutOfSchool.BusinessLogic/Models/Workshops/Drafts/WorkshopRequiredPropertiesDto.cs b/OutOfSchool/OutOfSchool.BusinessLogic/Models/Workshops/Drafts/WorkshopRequiredPropertiesDto.cs new file mode 100644 index 000000000..a6ae2e806 --- /dev/null +++ b/OutOfSchool/OutOfSchool.BusinessLogic/Models/Workshops/Drafts/WorkshopRequiredPropertiesDto.cs @@ -0,0 +1,40 @@ +using System.ComponentModel.DataAnnotations; + +namespace OutOfSchool.BusinessLogic.Models.Workshops.Drafts; + +public class WorkshopRequiredPropertiesDto : WorkshopMainRequiredPropertiesDto +{ + [Required(ErrorMessage = "Short stay is required")] + public bool ShortStay { get; set; } = false; + + [Required(ErrorMessage = "Should be indicated if the Workshop operates with funds from parents or benefactors")] + public bool IsSelfFinanced { get; set; } = false; + + [Required(ErrorMessage = "Property IsSpecial is required")] + public bool IsSpecial { get; set; } = false; + + public uint SpecialNeedsId { get; set; } = 0; + + [Required(ErrorMessage = "Property IsInclusive is required")] + public bool IsInclusive { get; set; } = false; + + [Required(ErrorMessage = "Educational shift is required")] + public uint EducationalShiftId { get; set; } = 0; + + [Required(ErrorMessage = "Language of education is required")] + public uint LanguageOfEducationId { get; set; } = 0; + + [Required(ErrorMessage = "Type of age composition is required")] + public uint TypeOfAgeCompositionId { get; set; } = 0; + + [Required(ErrorMessage = "Educational disciplines is required")] + public Guid EducationalDisciplines { get; set; } = Guid.Empty; + + [Required(ErrorMessage = "Category is required")] + public uint CategoryId { get; set; } = 0; + + [Required(ErrorMessage = "GropeType is required")] + public uint GropeTypeId { get; set; } = 0; + + public Guid? MemberOfWorkshopId { get; set; } +} \ No newline at end of file diff --git a/OutOfSchool/OutOfSchool.BusinessLogic/Models/Workshops/Drafts/WorkshopStaffDto.cs b/OutOfSchool/OutOfSchool.BusinessLogic/Models/Workshops/Drafts/WorkshopStaffDto.cs new file mode 100644 index 000000000..890618b3d --- /dev/null +++ b/OutOfSchool/OutOfSchool.BusinessLogic/Models/Workshops/Drafts/WorkshopStaffDto.cs @@ -0,0 +1,15 @@ +using Microsoft.AspNetCore.Mvc; +using OutOfSchool.BusinessLogic.Util.JsonTools; + +namespace OutOfSchool.BusinessLogic.Models.Workshops.Drafts; + +public class WorkshopStaffDto : WorkshopContactsDto +{ + [ModelBinder(BinderType = typeof(JsonModelBinder))] + public TeacherDTO DefaultTeacher { get; set; } + + [ModelBinder(BinderType = typeof(JsonModelBinder))] + public List Teachers { get; set; } + + public Guid? DefaultTeacherId { get; set; } +} \ No newline at end of file diff --git a/OutOfSchool/OutOfSchool.BusinessLogic/Models/Workshops/WorkshopCreateRequestDto.cs b/OutOfSchool/OutOfSchool.BusinessLogic/Models/Workshops/WorkshopCreateRequestDto.cs index b477ecc20..802887b6c 100644 --- a/OutOfSchool/OutOfSchool.BusinessLogic/Models/Workshops/WorkshopCreateRequestDto.cs +++ b/OutOfSchool/OutOfSchool.BusinessLogic/Models/Workshops/WorkshopCreateRequestDto.cs @@ -1,190 +1,7 @@ -using Microsoft.AspNetCore.Mvc; -using OutOfSchool.BusinessLogic.Util.CustomValidation; -using OutOfSchool.BusinessLogic.Util.JsonTools; -using OutOfSchool.Common.Enums; -using OutOfSchool.Common.Validators; -using OutOfSchool.Services.Enums; -using System.ComponentModel.DataAnnotations.Schema; -using System.ComponentModel.DataAnnotations; +using OutOfSchool.BusinessLogic.Models.Workshops.Drafts; namespace OutOfSchool.BusinessLogic.Models.Workshops; -public class WorkshopCreateRequestDto : IValidatableObject +public class WorkshopCreateRequestDto : WorkshopStaffDto { - public Guid Id { get; set; } - - [Required(ErrorMessage = "Workshop title is required")] - [MinLength(Constants.MinWorkshopTitleLength)] - [MaxLength(Constants.MaxWorkshopTitleLength)] - public string Title { get; set; } = string.Empty; - - [Required(ErrorMessage = "Workshop short title is required")] - [MinLength(Constants.MinWorkshopShortTitleLength)] - [MaxLength(Constants.MaxWorkshopShortTitleLength)] - public string ShortTitle { get; set; } = string.Empty; - - [DataType(DataType.PhoneNumber)] - [Required(ErrorMessage = "Phone number is required")] - [CustomPhoneNumber(ErrorMessage = Constants.PhoneErrorMessage)] - [DisplayFormat(DataFormatString = Constants.PhoneNumberFormat)] - public string Phone { get; set; } = string.Empty; - - [DataType(DataType.EmailAddress)] - [Required(ErrorMessage = "Email is required")] - [MaxLength(256)] - public string Email { get; set; } = string.Empty; - - [DataType(DataType.Url)] - [MaxLength(Constants.MaxUnifiedUrlLength)] - public string Website { get; set; } = string.Empty; - - [DataType(DataType.Url)] - [MaxLength(Constants.MaxUnifiedUrlLength)] - public string Facebook { get; set; } = string.Empty; - - [DataType(DataType.Url)] - [MaxLength(Constants.MaxUnifiedUrlLength)] - public string Instagram { get; set; } = string.Empty; - - [Required(ErrorMessage = "Children's min age is required")] - [Range(0, 120, ErrorMessage = "Min age should be a number from 0 to 120")] - public int MinAge { get; set; } - - [Required(ErrorMessage = "Children's max age is required")] - [Range(0, 120, ErrorMessage = "Max age should be a number from 0 to 120")] - public int MaxAge { get; set; } - - [ModelBinder(BinderType = typeof(JsonModelBinder))] - [CollectionNotEmpty(ErrorMessage = "At least one DateTime range is required")] - public List DateTimeRanges { get; set; } - - public bool IsPaid { get; set; } = false; - - [Column(TypeName = "decimal(18,2)")] - [Range(0, 100000, ErrorMessage = "Field value should be in a range from 1 to 100 000")] - public decimal? Price { get; set; } = default; - - [EnumDataType(typeof(PayRateType), ErrorMessage = Constants.EnumErrorMessage)] - public PayRateType? PayRate { get; set; } = PayRateType.Classes; - - [Required(ErrorMessage = "Form of learning is required")] - [EnumDataType(typeof(FormOfLearning), ErrorMessage = Constants.EnumErrorMessage)] - public FormOfLearning FormOfLearning { get; set; } = FormOfLearning.Offline; - - [Required(ErrorMessage = "Available seats are required")] - public uint? AvailableSeats { get; set; } = uint.MaxValue; - - public bool CompetitiveSelection { get; set; } - - [MaxLength(500)] - public string CompetitiveSelectionDescription { get; set; } - - [ModelBinder(BinderType = typeof(JsonModelBinder))] - [CollectionNotEmpty(ErrorMessage = "At least one description item is required")] - public IEnumerable WorkshopDescriptionItems { get; set; } - - public bool WithDisabilityOptions { get; set; } = default; - - [MaxLength(200)] - public string DisabilityOptionsDesc { get; set; } = string.Empty; - - public Guid? InstitutionId { get; set; } - - public Guid? InstitutionHierarchyId { get; set; } - - [ModelBinder(BinderType = typeof(JsonModelBinder))] - public TeacherDTO DefaultTeacher { get; set; } - - public List DirectionIds { get; set; } - - [ModelBinder(BinderType = typeof(JsonModelBinder))] - public IEnumerable Keywords { get; set; } = default; - - [ModelBinder(BinderType = typeof(JsonModelBinder))] - public List Teachers { get; set; } - - [Required] - public Guid ProviderId { get; set; } - - public DateOnly ActiveFrom { get; set; } - - public DateOnly ActiveTo { get; set; } - - public bool ShortStay { get; set; } = false; - - public bool IsSelfFinanced { get; set; } = false; - - public bool IsSpecial { get; set; } = false; - - public uint SpecialNeedsId { get; set; } = 0; - - public bool IsInclusive { get; set; } = false; - - [MaxLength(500)] - public string AdditionalDescription { get; set; } - - public bool AreThereBenefits { get; set; } = default; - - [MaxLength(500)] - public string PreferentialTermsOfParticipation { get; set; } - - [Required] - public uint EducationalShiftId { get; set; } = 0; - - [Required(ErrorMessage = "Language of education is required")] - public uint LanguageOfEducationId { get; set; } = 0; - - [Required(ErrorMessage = "Type of age composition is required")] - public uint TypeOfAgeCompositionId { get; set; } = 0; - - [Required(ErrorMessage = "Educational disciplines is required")] - public Guid EducationalDisciplines { get; set; } = Guid.Empty; - - [Required(ErrorMessage = "Category is required")] - public uint CategoryId { get; set; } = 0; - - [Required(ErrorMessage = "GropeType is required")] - public uint GropeTypeId { get; set; } = 0; - - public uint CoverageId { get; set; } = 0; - - public Guid? DefaultTeacherId { get; set; } - - public Guid? MemberOfWorkshopId { get; set; } - - [Required] - public long AddressId { get; set; } - - [Required] - [ModelBinder(BinderType = typeof(JsonModelBinder))] - public AddressDto Address { get; set; } - - [ModelBinder(BinderType = typeof(JsonModelBinder))] - public List TagIds { get; set; } = []; - - public IEnumerable Validate(ValidationContext validationContext) - { - // TODO: Validate DateTimeRanges are not empty when frontend is ready - foreach (var dateTimeRange in DateTimeRanges) - { - if (dateTimeRange.StartTime > dateTimeRange.EndTime) - { - yield return new ValidationResult( - "End date can't be earlier that start date"); - } - - if (dateTimeRange.Workdays.IsNullOrEmpty() || dateTimeRange.Workdays.Any(workday => workday == DaysBitMask.None)) - { - yield return new ValidationResult( - "Workdays are required"); - } - - var daysHs = new HashSet(); - if (!dateTimeRange.Workdays.All(daysHs.Add)) - { - yield return new ValidationResult( - "Workdays contain duplications"); - } - } - } } \ No newline at end of file From 7fe58350fc8864f1dc0878eaceb3e2d91c406264 Mon Sep 17 00:00:00 2001 From: HlibBondarev Date: Mon, 2 Dec 2024 23:31:36 +0200 Subject: [PATCH 02/28] 1) Added IReadWriteCacheService interface. 2) Implemented ReadAsync() and WriteAsync() methods of IReadWriteCacheService interface in CasheService class. 3) Changed GetOrAddAsync() and RemoveAsync() methods of CasheService class. --- OutOfSchool/OutOfSchool.Redis/CacheService.cs | 54 ++++++++++++++++++- .../IReadWriteCacheService.cs | 15 ++++++ 2 files changed, 67 insertions(+), 2 deletions(-) create mode 100644 OutOfSchool/OutOfSchool.Redis/IReadWriteCacheService.cs diff --git a/OutOfSchool/OutOfSchool.Redis/CacheService.cs b/OutOfSchool/OutOfSchool.Redis/CacheService.cs index e2cbfe53a..8033f9044 100644 --- a/OutOfSchool/OutOfSchool.Redis/CacheService.cs +++ b/OutOfSchool/OutOfSchool.Redis/CacheService.cs @@ -8,7 +8,7 @@ namespace OutOfSchool.Redis; -public class CacheService : ICacheService, IDisposable +public class CacheService : ICacheService, IReadWriteCacheService, IDisposable { private readonly ReaderWriterLockSlim cacheLock = new ReaderWriterLockSlim(); private readonly IDistributedCache cache; @@ -18,7 +18,6 @@ public class CacheService : ICacheService, IDisposable private readonly bool isEnabled = false; private readonly object lockObject = new object(); - private bool isDisposed; public CacheService( @@ -107,6 +106,57 @@ public Task RemoveAsync(string key) } }); + public async Task ReadAsync(string key) + { + ArgumentException.ThrowIfNullOrEmpty(key); + + string returnValue = string.Empty; + + await ExecuteRedisMethod(() => + { + cacheLock.EnterReadLock(); + try + { + returnValue = cache.GetString(key) ?? string.Empty; + } + finally + { + cacheLock.ExitReadLock(); + } + }); + + return returnValue; + } + + public async Task WriteAsync( + string key, + string value, + TimeSpan? absoluteExpirationRelativeToNowInterval = null, + TimeSpan? slidingExpirationInterval = null) + { + ArgumentException.ThrowIfNullOrEmpty(key); + ArgumentException.ThrowIfNullOrEmpty(value); + + await ExecuteRedisMethod(() => + { + cacheLock.EnterWriteLock(); + try + { + var options = new DistributedCacheEntryOptions + { + AbsoluteExpirationRelativeToNow = absoluteExpirationRelativeToNowInterval ?? redisConfig!.AbsoluteExpirationRelativeToNowInterval, + SlidingExpiration = slidingExpirationInterval ?? redisConfig!.SlidingExpirationInterval, + }; + + cache.SetString(key, value, options); + } + finally + { + cacheLock.ExitWriteLock(); + } + }); + } + public void Dispose() { Dispose(true); diff --git a/OutOfSchool/OutOfSchool.Redis/IReadWriteCacheService.cs b/OutOfSchool/OutOfSchool.Redis/IReadWriteCacheService.cs new file mode 100644 index 000000000..d5c70362a --- /dev/null +++ b/OutOfSchool/OutOfSchool.Redis/IReadWriteCacheService.cs @@ -0,0 +1,15 @@ +using System.Threading.Tasks; +using System; + +namespace OutOfSchool.Redis; + +public interface IReadWriteCacheService +{ + Task ReadAsync(string key); + + Task WriteAsync( + string key, + string value, + TimeSpan? absoluteExpirationRelativeToNowInterval = null, + TimeSpan? slidingExpirationInterval = null); +} From 25be54be6d3afb60afb5e88870a77862b565a27c Mon Sep 17 00:00:00 2001 From: HlibBondarev Date: Tue, 3 Dec 2024 00:30:28 +0200 Subject: [PATCH 03/28] 1) Changed IReadWriteCacheService interface - added: Task RemoveAsync(string key). 2) Added IDraftStorageService interface and DraftStorageService class. 3) Changed OutOfSchool.BusinessLogic project file - added: enable 4) Changed Startup class - added services: services.AddSingleton(); services.AddSingleton(typeof(IDraftStorageService<>), typeof(DraftStorageService<>)); --- .../DraftStorage/DraftStorageService.cs | 81 +++++++++++++++++++ .../DraftStorage/IDraftStorageService.cs | 10 +++ .../IReadWriteCacheService.cs | 2 + OutOfSchool/OutOfSchool.WebApi/Startup.cs | 3 + 4 files changed, 96 insertions(+) create mode 100644 OutOfSchool/OutOfSchool.BusinessLogic/Services/DraftStorage/DraftStorageService.cs create mode 100644 OutOfSchool/OutOfSchool.BusinessLogic/Services/DraftStorage/IDraftStorageService.cs diff --git a/OutOfSchool/OutOfSchool.BusinessLogic/Services/DraftStorage/DraftStorageService.cs b/OutOfSchool/OutOfSchool.BusinessLogic/Services/DraftStorage/DraftStorageService.cs new file mode 100644 index 000000000..25ec8eda1 --- /dev/null +++ b/OutOfSchool/OutOfSchool.BusinessLogic/Services/DraftStorage/DraftStorageService.cs @@ -0,0 +1,81 @@ +using System.Diagnostics.CodeAnalysis; + +namespace OutOfSchool.BusinessLogic.Services.DraftStorage; + +/// +/// Implements the IDraftStorageService{T} interface to store an entity draft of type T in a cache. +/// +/// T is the entity draft type that should be stored in the cache. +public class DraftStorageService : IDraftStorageService +{ + private readonly IReadWriteCacheService cacheService; + private readonly ILogger> logger; + + /// Initializes a new instance of the class. + /// The cache service. + /// The logger. + public DraftStorageService( + IReadWriteCacheService cacheService, + ILogger> logger) + { + this.cacheService = cacheService; + this.logger = logger; + } + + /// Restores the entity draft of T type entity. + /// The key. + /// Representing the asynchronous operation with result of T type. + public async Task RestoreAsync([NotNull] string key) + { + ArgumentException.ThrowIfNullOrEmpty(key); + + var draftValue = await cacheService.ReadAsync(GetKey(key)).ConfigureAwait(false); + + if (draftValue.IsNullOrEmpty()) + { + logger.LogInformation("The {EntityType} draft for User with key = {Key} was not found in the cache.", typeof(T).Name, key); + return default; + } + + return JsonSerializerHelper.Deserialize(draftValue); + } + + /// Creates the entity draft of T type in the cache. + /// The key. + /// The value. + /// + /// Representing the asynchronous operation - creating the entity draft of T type in the cache. + /// + public async Task CreateAsync([NotNull] string key, [NotNull] T value) + { + ArgumentException.ThrowIfNullOrEmpty(key); + ArgumentNullException.ThrowIfNull(value); + + await cacheService.WriteAsync(GetKey(key), JsonSerializerHelper.Serialize(value)).ConfigureAwait(false); + } + + /// Asynchronously removes an entity draft from the cache. + /// The key. + /// Representation of an asynchronous operation - removing an entity draft from the cache. + public async Task RemoveAsync([NotNull] string key) + { + ArgumentException.ThrowIfNullOrEmpty(key); + + var draftKey = GetKey(key); + var valueToRemove = await cacheService.ReadAsync(draftKey); + + if (valueToRemove.IsNullOrEmpty()) + { + logger.LogInformation("The {EntityType} draft with key = {DraftKey} was not found in the cache.", typeof(T).Name, draftKey); + return; + } + + logger.LogInformation("Start removing the {EntityType} draft with key = {DraftKey} from cache.", typeof(T).Name, draftKey); + await cacheService.RemoveAsync(draftKey).ConfigureAwait(false); + } + + private static string GetKey(string key) + { + return $"{key}_{typeof(T).Name}"; + } +} diff --git a/OutOfSchool/OutOfSchool.BusinessLogic/Services/DraftStorage/IDraftStorageService.cs b/OutOfSchool/OutOfSchool.BusinessLogic/Services/DraftStorage/IDraftStorageService.cs new file mode 100644 index 000000000..973894a21 --- /dev/null +++ b/OutOfSchool/OutOfSchool.BusinessLogic/Services/DraftStorage/IDraftStorageService.cs @@ -0,0 +1,10 @@ +namespace OutOfSchool.BusinessLogic.Services.DraftStorage; + +public interface IDraftStorageService +{ + Task RestoreAsync(string key); + + Task CreateAsync(string key, T value); + + Task RemoveAsync(string key); +} \ No newline at end of file diff --git a/OutOfSchool/OutOfSchool.Redis/IReadWriteCacheService.cs b/OutOfSchool/OutOfSchool.Redis/IReadWriteCacheService.cs index d5c70362a..20ab364d8 100644 --- a/OutOfSchool/OutOfSchool.Redis/IReadWriteCacheService.cs +++ b/OutOfSchool/OutOfSchool.Redis/IReadWriteCacheService.cs @@ -12,4 +12,6 @@ Task WriteAsync( string value, TimeSpan? absoluteExpirationRelativeToNowInterval = null, TimeSpan? slidingExpirationInterval = null); + + Task RemoveAsync(string key); } diff --git a/OutOfSchool/OutOfSchool.WebApi/Startup.cs b/OutOfSchool/OutOfSchool.WebApi/Startup.cs index d4b1d3014..9e7b902a4 100644 --- a/OutOfSchool/OutOfSchool.WebApi/Startup.cs +++ b/OutOfSchool/OutOfSchool.WebApi/Startup.cs @@ -17,6 +17,7 @@ using OutOfSchool.BackgroundJobs.Extensions.Startup; using OutOfSchool.BusinessLogic.Config.SearchString; using OutOfSchool.BusinessLogic.Services.AverageRatings; +using OutOfSchool.BusinessLogic.Services.DraftStorage; using OutOfSchool.BusinessLogic.Services.Elasticsearch; using OutOfSchool.BusinessLogic.Services.ProviderServices; using OutOfSchool.BusinessLogic.Services.SearchString; @@ -546,6 +547,8 @@ public static async Task AddApplicationServices(this WebApplicationBuilder build services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(typeof(IDraftStorageService<>), typeof(DraftStorageService<>)); services.AddHealthChecks() .AddCheck("Liveness", () => HealthCheckResult.Healthy()) From e9a4a787189093ee65bedd4ddfb0c6b8f44b1070 Mon Sep 17 00:00:00 2001 From: HlibBondarev Date: Tue, 3 Dec 2024 11:15:00 +0200 Subject: [PATCH 04/28] 1) Added preprocessor directive (#nullable enable) to JsonSerializerHelper, DraftStorageService classes and IDraftStorageService interface. 2) Changed parameter type (JsonSerializerOptions => JsonSerializerOptions?) in JsonSerializerHelper class methods. 3) Changed OutOfSchool.BusinessLogic project file - removed: enable --- .../Services/DraftStorage/DraftStorageService.cs | 2 ++ .../Services/DraftStorage/IDraftStorageService.cs | 4 +++- .../OutOfSchool.Common/JsonSerializerHelper.cs | 12 +++++++----- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/OutOfSchool/OutOfSchool.BusinessLogic/Services/DraftStorage/DraftStorageService.cs b/OutOfSchool/OutOfSchool.BusinessLogic/Services/DraftStorage/DraftStorageService.cs index 25ec8eda1..cbfc47b4c 100644 --- a/OutOfSchool/OutOfSchool.BusinessLogic/Services/DraftStorage/DraftStorageService.cs +++ b/OutOfSchool/OutOfSchool.BusinessLogic/Services/DraftStorage/DraftStorageService.cs @@ -1,5 +1,7 @@ using System.Diagnostics.CodeAnalysis; +#nullable enable + namespace OutOfSchool.BusinessLogic.Services.DraftStorage; /// diff --git a/OutOfSchool/OutOfSchool.BusinessLogic/Services/DraftStorage/IDraftStorageService.cs b/OutOfSchool/OutOfSchool.BusinessLogic/Services/DraftStorage/IDraftStorageService.cs index 973894a21..54e3a11b5 100644 --- a/OutOfSchool/OutOfSchool.BusinessLogic/Services/DraftStorage/IDraftStorageService.cs +++ b/OutOfSchool/OutOfSchool.BusinessLogic/Services/DraftStorage/IDraftStorageService.cs @@ -1,4 +1,6 @@ -namespace OutOfSchool.BusinessLogic.Services.DraftStorage; +#nullable enable + +namespace OutOfSchool.BusinessLogic.Services.DraftStorage; public interface IDraftStorageService { diff --git a/OutOfSchool/OutOfSchool.Common/JsonSerializerHelper.cs b/OutOfSchool/OutOfSchool.Common/JsonSerializerHelper.cs index 07248327b..dbaae5544 100644 --- a/OutOfSchool/OutOfSchool.Common/JsonSerializerHelper.cs +++ b/OutOfSchool/OutOfSchool.Common/JsonSerializerHelper.cs @@ -2,6 +2,8 @@ using System.IO; using System.Text.Json; +#nullable enable + namespace OutOfSchool.Common; public static class JsonSerializerHelper @@ -15,7 +17,7 @@ public static class JsonSerializerHelper /// A representation of the JSON value. /// JSON text to parse. /// Options to control the behavior during parsing. - public static TValue? Deserialize(string json, JsonSerializerOptions options = null) + public static TValue? Deserialize(string json, JsonSerializerOptions? options = null) { return JsonSerializer.Deserialize(json, options ?? JsonSerializerOptionsWeb); } @@ -31,7 +33,7 @@ public static class JsonSerializerHelper /// /// is . /// - public static TValue? Deserialize(Stream stream, JsonSerializerOptions options = null) + public static TValue? Deserialize(Stream stream, JsonSerializerOptions? options = null) { ArgumentNullException.ThrowIfNull(stream); @@ -45,7 +47,7 @@ public static class JsonSerializerHelper /// JSON text to parse. /// The type of the object to convert to and return. /// Options to control the behavior during parsing. - public static object? Deserialize(string json, Type returnType, JsonSerializerOptions options = null) + public static object? Deserialize(string json, Type returnType, JsonSerializerOptions? options = null) { return JsonSerializer.Deserialize(json, returnType, options ?? JsonSerializerOptionsWeb); } @@ -57,7 +59,7 @@ public static class JsonSerializerHelper /// A representation of the value. /// The value to convert. /// Options to control the conversion behavior. - public static string Serialize(TValue value, JsonSerializerOptions options = null) + public static string Serialize(TValue value, JsonSerializerOptions? options = null) { return JsonSerializer.Serialize(value, options ?? JsonSerializerOptionsWeb); } @@ -72,7 +74,7 @@ public static string Serialize(TValue value, JsonSerializerOptions optio /// /// is . /// - public static void Serialize(Utf8JsonWriter writer, TValue value, JsonSerializerOptions options = null) + public static void Serialize(Utf8JsonWriter writer, TValue value, JsonSerializerOptions? options = null) { ArgumentNullException.ThrowIfNull(writer); From 86ff2165ca6b0be40f39b4327c318c5e279977e8 Mon Sep 17 00:00:00 2001 From: HlibBondarev Date: Tue, 3 Dec 2024 11:47:50 +0200 Subject: [PATCH 05/28] Added generic DraftStorageController class. --- .../Controllers/V1/DraftStorageController.cs | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 OutOfSchool/OutOfSchool.WebApi/Controllers/V1/DraftStorageController.cs diff --git a/OutOfSchool/OutOfSchool.WebApi/Controllers/V1/DraftStorageController.cs b/OutOfSchool/OutOfSchool.WebApi/Controllers/V1/DraftStorageController.cs new file mode 100644 index 000000000..64677fa05 --- /dev/null +++ b/OutOfSchool/OutOfSchool.WebApi/Controllers/V1/DraftStorageController.cs @@ -0,0 +1,62 @@ +using Microsoft.AspNetCore.Mvc; +using OutOfSchool.BusinessLogic.Common; +using OutOfSchool.BusinessLogic.Services.DraftStorage; + +namespace OutOfSchool.WebApi.Controllers.V1; + +/// Abstract controller with operations for storing the entity draft in cache. +/// T is the entity draft type that should be stored in the cache. +public abstract class DraftStorageController : ControllerBase +{ + private readonly IDraftStorageService draftStorageService; + + /// Initializes a new instance of the class. + /// The draft storage service. + protected DraftStorageController(IDraftStorageService draftStorageService) + { + this.draftStorageService = draftStorageService; + } + + /// Stores the entity draft. + /// The entity draft dto for type T. + /// + /// Information about the stored entity of type T in the cache. + /// + [HttpPost] + [Authorize(Roles = "provider, ministryadmin, areaadmin, regionadmin, techadmin")] + public async Task StoreDraft([FromBody] T draftDto) + { + if (!ModelState.IsValid) + { + return this.BadRequest(ModelState); + } + + await draftStorageService.CreateAsync(GettingUserProperties.GetUserId(User), draftDto).ConfigureAwait(false); + + return Ok($"{typeof(T).Name} is stored ({JsonSerializerHelper.Serialize(draftDto)})"); + } + + /// Restores the entity draft. + /// The entity draft dto of type T. + [HttpGet] + [Authorize(Roles = "provider, ministryadmin, areaadmin, regionadmin, techadmin")] + public async Task RestoreDraft() + { + var draft = await draftStorageService.RestoreAsync(GettingUserProperties.GetUserId(User)).ConfigureAwait(false); + + return Ok(draft); + } + + /// Removes the entity draft from the cache. + /// Information about removing an entity of type T from the cache. + [HttpDelete] + [Authorize(Roles = "provider, ministryadmin, areaadmin, regionadmin, techadmin")] + public async Task RemoveDraft() + { + var userId = GettingUserProperties.GetUserId(User); + await draftStorageService.RemoveAsync(userId).ConfigureAwait(false); + + return NoContent(); + } +} + From b3fb7881b39ff0e5f6eb7dbec0a9347d9a62a1db Mon Sep 17 00:00:00 2001 From: HlibBondarev Date: Tue, 3 Dec 2024 15:06:34 +0200 Subject: [PATCH 06/28] Changed Startup class - replaced the line of code: .AddJsonOptions(options => options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter())); with: .AddJsonOptions(options => options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter(JsonNamingPolicy.CamelCase))); --- OutOfSchool/OutOfSchool.WebApi/Startup.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OutOfSchool/OutOfSchool.WebApi/Startup.cs b/OutOfSchool/OutOfSchool.WebApi/Startup.cs index 9e7b902a4..6b5d3c0c1 100644 --- a/OutOfSchool/OutOfSchool.WebApi/Startup.cs +++ b/OutOfSchool/OutOfSchool.WebApi/Startup.cs @@ -207,7 +207,7 @@ public static async Task AddApplicationServices(this WebApplicationBuilder build }) .AddJsonOptions(options => - options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter())); + options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter(JsonNamingPolicy.CamelCase))); services.AddHttpClient(configuration["Communication:ClientName"]) .AddHttpMessageHandler(handler => From 7c0e8a17269fedfdf6094bed3101e22ed2927df3 Mon Sep 17 00:00:00 2001 From: HlibBondarev Date: Wed, 4 Dec 2024 00:54:55 +0200 Subject: [PATCH 07/28] 1) Added the WorkshopDraftStorageController class. 2) Fixed the RemoveAsync() method - a synchronous method is used instead of an asynchronous one. --- OutOfSchool/OutOfSchool.Redis/CacheService.cs | 9 +++++++-- .../V1/WorkshopDraftStorageController.cs | 15 +++++++++++++++ 2 files changed, 22 insertions(+), 2 deletions(-) create mode 100644 OutOfSchool/OutOfSchool.WebApi/Controllers/V1/WorkshopDraftStorageController.cs diff --git a/OutOfSchool/OutOfSchool.Redis/CacheService.cs b/OutOfSchool/OutOfSchool.Redis/CacheService.cs index 8033f9044..b189af19c 100644 --- a/OutOfSchool/OutOfSchool.Redis/CacheService.cs +++ b/OutOfSchool/OutOfSchool.Redis/CacheService.cs @@ -94,17 +94,22 @@ await ExecuteRedisMethod(() => } public Task RemoveAsync(string key) - => ExecuteRedisMethod(async () => { + { + ArgumentException.ThrowIfNullOrEmpty(key); + + return ExecuteRedisMethod(() => + { cacheLock.EnterWriteLock(); try { - await cache.RemoveAsync(key); + cache.Remove(key); } finally { cacheLock.ExitWriteLock(); } }); + } public async Task ReadAsync(string key) { diff --git a/OutOfSchool/OutOfSchool.WebApi/Controllers/V1/WorkshopDraftStorageController.cs b/OutOfSchool/OutOfSchool.WebApi/Controllers/V1/WorkshopDraftStorageController.cs new file mode 100644 index 000000000..e174853f3 --- /dev/null +++ b/OutOfSchool/OutOfSchool.WebApi/Controllers/V1/WorkshopDraftStorageController.cs @@ -0,0 +1,15 @@ +using Asp.Versioning; +using Microsoft.AspNetCore.Mvc; +using OutOfSchool.BusinessLogic.Models.Workshops.Drafts; +using OutOfSchool.BusinessLogic.Services.DraftStorage; + +namespace OutOfSchool.WebApi.Controllers.V1; + +/// Controller with operations for storing the Workshop draft in cache. +[ApiController] +[ApiVersion("1.0")] +[Route("api/v{version:apiVersion}/[controller]/[action]")] +public class WorkshopDraftStorageController(IDraftStorageService draftStorageService) + : DraftStorageController(draftStorageService) +{ +} \ No newline at end of file From fb13a87621295e32fc75b4508dd060a6faaac41a Mon Sep 17 00:00:00 2001 From: HlibBondarev Date: Wed, 4 Dec 2024 01:17:27 +0200 Subject: [PATCH 08/28] Changed MappingProfile class - fixed mapping WorkshopCreateRequestDto -> Workshop and WorkshopV2CreateRequestDto -> Workshop. --- OutOfSchool/OutOfSchool.BusinessLogic/Util/MappingProfile.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/OutOfSchool/OutOfSchool.BusinessLogic/Util/MappingProfile.cs b/OutOfSchool/OutOfSchool.BusinessLogic/Util/MappingProfile.cs index b1d5d902e..97c19cd4c 100644 --- a/OutOfSchool/OutOfSchool.BusinessLogic/Util/MappingProfile.cs +++ b/OutOfSchool/OutOfSchool.BusinessLogic/Util/MappingProfile.cs @@ -146,7 +146,9 @@ public MappingProfile() .ForMember(dest => dest.MemberOfWorkshop, opt => opt.Ignore()) .ForMember(dest => dest.IncludedStudyGroups, opt => opt.Ignore()) .ForMember(dest => dest.Images, opt => opt.Ignore()) - .ForMember(dest => dest.IsBlocked, opt => opt.Ignore()); + .ForMember(dest => dest.IsBlocked, opt => opt.Ignore()) + .ForMember(dest => dest.ActiveFrom, opt => opt.Ignore()) + .ForMember(dest => dest.ActiveTo, opt => opt.Ignore()); CreateMap() .ForMember( From e5a5bf9a10d2b07ad195dcfd90a0599840969063 Mon Sep 17 00:00:00 2001 From: HlibBondarev Date: Wed, 4 Dec 2024 15:53:26 +0200 Subject: [PATCH 09/28] Updated version of Microsoft.Extensions.Caching.StackExchangeRedis from 6.0.2 to 8.0.11 --- OutOfSchool/OutOfSchool.Redis/OutOfSchool.Redis.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OutOfSchool/OutOfSchool.Redis/OutOfSchool.Redis.csproj b/OutOfSchool/OutOfSchool.Redis/OutOfSchool.Redis.csproj index 48fa02e2d..0f604a0be 100644 --- a/OutOfSchool/OutOfSchool.Redis/OutOfSchool.Redis.csproj +++ b/OutOfSchool/OutOfSchool.Redis/OutOfSchool.Redis.csproj @@ -1,6 +1,6 @@  - + From dfedcc48a4816c505c7633b8902156c1a4c15f9f Mon Sep 17 00:00:00 2001 From: HlibBondarev Date: Wed, 4 Dec 2024 16:15:26 +0200 Subject: [PATCH 10/28] Chamged OutOfSchool.Redis.csproj file - added code lines: net8.0 direct --- OutOfSchool/OutOfSchool.Redis/OutOfSchool.Redis.csproj | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/OutOfSchool/OutOfSchool.Redis/OutOfSchool.Redis.csproj b/OutOfSchool/OutOfSchool.Redis/OutOfSchool.Redis.csproj index 0f604a0be..a0fc19d0e 100644 --- a/OutOfSchool/OutOfSchool.Redis/OutOfSchool.Redis.csproj +++ b/OutOfSchool/OutOfSchool.Redis/OutOfSchool.Redis.csproj @@ -1,4 +1,8 @@  + + net8.0 + direct + From 89c1cb92b79ae51c82d69fb9a2ed79fd7973dbea Mon Sep 17 00:00:00 2001 From: HlibBondarev Date: Wed, 4 Dec 2024 16:26:11 +0200 Subject: [PATCH 11/28] Changed OutOfSchool.AuthorizationServer.csproj file - added code lines: net8.0 direct --- .../OutOfSchool.AuthorizationServer.csproj | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/OutOfSchool/OutOfSchool.AuthorizationServer/OutOfSchool.AuthorizationServer.csproj b/OutOfSchool/OutOfSchool.AuthorizationServer/OutOfSchool.AuthorizationServer.csproj index 0f718ed17..6f37852db 100644 --- a/OutOfSchool/OutOfSchool.AuthorizationServer/OutOfSchool.AuthorizationServer.csproj +++ b/OutOfSchool/OutOfSchool.AuthorizationServer/OutOfSchool.AuthorizationServer.csproj @@ -7,6 +7,11 @@ enable + + net8.0 + direct + + From 11184b1748d8413b3cc2532a8e64a70178a26f03 Mon Sep 17 00:00:00 2001 From: HlibBondarev Date: Wed, 4 Dec 2024 16:38:28 +0200 Subject: [PATCH 12/28] Updated version of Microsoft.Extensions.Caching.StackExchangeRedis from 8.0.11 to 9.0.0. --- OutOfSchool/Directory.Packages.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OutOfSchool/Directory.Packages.props b/OutOfSchool/Directory.Packages.props index abbda4896..e6b06b3a8 100644 --- a/OutOfSchool/Directory.Packages.props +++ b/OutOfSchool/Directory.Packages.props @@ -47,7 +47,7 @@ - + From 08cabec7f127d047ed6b5e27306e02cfa7f41154 Mon Sep 17 00:00:00 2001 From: HlibBondarev Date: Wed, 4 Dec 2024 17:02:13 +0200 Subject: [PATCH 13/28] Changed project files - OutOfSchool.AuthCommon, OutOfSchool.BackgroundJobs, OutOfSchool.BusinessLogic, OutOfSchool.Common, OutOfSchool.DataAccess, OutOfSchool.ElasticsearchData, OutOfSchool.EmailSender, OutOfSchool.Encryption, OutOfSchool.GRPC, OutOfSchool.Migrations, OutOfSchool.RazorTemplatesData, OutOfSchool.WebApi, added the following lines of code: net8.0 direct --- .../OutOfSchool.AuthCommon.csproj | 5 +++++ .../OutOfSchool.BackgroundJobs.csproj | 5 +++++ .../OutOfSchool.BusinessLogic.csproj | 11 ++++++++--- .../OutOfSchool.Common/OutOfSchool.Common.csproj | 4 ++++ .../OutOfSchool.DataAccess.csproj | 5 +++++ .../OutOfSchool.ElasticsearchData.csproj | 5 +++++ .../OutOfSchool.EmailSender.csproj | 5 +++++ .../OutOfSchool.Encryption.csproj | 1 + OutOfSchool/OutOfSchool.GRPC/OutOfSchool.GRPC.csproj | 6 ++++++ .../OutOfSchool.Migrations.csproj | 5 +++++ .../OutOfSchool.RazorTemplatesData.csproj | 5 +++++ .../OutOfSchool.WebApi/OutOfSchool.WebApi.csproj | 1 + 12 files changed, 55 insertions(+), 3 deletions(-) diff --git a/OutOfSchool/OutOfSchool.AuthCommon/OutOfSchool.AuthCommon.csproj b/OutOfSchool/OutOfSchool.AuthCommon/OutOfSchool.AuthCommon.csproj index 6cd93a7f4..dda5c0831 100644 --- a/OutOfSchool/OutOfSchool.AuthCommon/OutOfSchool.AuthCommon.csproj +++ b/OutOfSchool/OutOfSchool.AuthCommon/OutOfSchool.AuthCommon.csproj @@ -6,6 +6,11 @@ true + + net8.0 + direct + + diff --git a/OutOfSchool/OutOfSchool.BackgroundJobs/OutOfSchool.BackgroundJobs.csproj b/OutOfSchool/OutOfSchool.BackgroundJobs/OutOfSchool.BackgroundJobs.csproj index a314e0c21..0b4831d90 100644 --- a/OutOfSchool/OutOfSchool.BackgroundJobs/OutOfSchool.BackgroundJobs.csproj +++ b/OutOfSchool/OutOfSchool.BackgroundJobs/OutOfSchool.BackgroundJobs.csproj @@ -5,6 +5,11 @@ enable + + net8.0 + direct + + diff --git a/OutOfSchool/OutOfSchool.BusinessLogic/OutOfSchool.BusinessLogic.csproj b/OutOfSchool/OutOfSchool.BusinessLogic/OutOfSchool.BusinessLogic.csproj index c6d4bd53c..32fee2d55 100644 --- a/OutOfSchool/OutOfSchool.BusinessLogic/OutOfSchool.BusinessLogic.csproj +++ b/OutOfSchool/OutOfSchool.BusinessLogic/OutOfSchool.BusinessLogic.csproj @@ -14,9 +14,14 @@ latest - - - + + net8.0 + direct + + + + + diff --git a/OutOfSchool/OutOfSchool.Common/OutOfSchool.Common.csproj b/OutOfSchool/OutOfSchool.Common/OutOfSchool.Common.csproj index 9353e8e48..da3b57514 100644 --- a/OutOfSchool/OutOfSchool.Common/OutOfSchool.Common.csproj +++ b/OutOfSchool/OutOfSchool.Common/OutOfSchool.Common.csproj @@ -3,6 +3,10 @@ true $(NoWarn);1591 + + net8.0 + direct + diff --git a/OutOfSchool/OutOfSchool.DataAccess/OutOfSchool.DataAccess.csproj b/OutOfSchool/OutOfSchool.DataAccess/OutOfSchool.DataAccess.csproj index 321f2c4a6..4c6fdd146 100644 --- a/OutOfSchool/OutOfSchool.DataAccess/OutOfSchool.DataAccess.csproj +++ b/OutOfSchool/OutOfSchool.DataAccess/OutOfSchool.DataAccess.csproj @@ -9,6 +9,11 @@ $(NoWarn);1591 + + net8.0 + direct + + diff --git a/OutOfSchool/OutOfSchool.ElasticsearchData/OutOfSchool.ElasticsearchData.csproj b/OutOfSchool/OutOfSchool.ElasticsearchData/OutOfSchool.ElasticsearchData.csproj index 96d08a445..da3853845 100644 --- a/OutOfSchool/OutOfSchool.ElasticsearchData/OutOfSchool.ElasticsearchData.csproj +++ b/OutOfSchool/OutOfSchool.ElasticsearchData/OutOfSchool.ElasticsearchData.csproj @@ -4,6 +4,11 @@ $(NoWarn);1591 + + net8.0 + direct + + diff --git a/OutOfSchool/OutOfSchool.EmailSender/OutOfSchool.EmailSender.csproj b/OutOfSchool/OutOfSchool.EmailSender/OutOfSchool.EmailSender.csproj index fd80cb737..a2339bab6 100644 --- a/OutOfSchool/OutOfSchool.EmailSender/OutOfSchool.EmailSender.csproj +++ b/OutOfSchool/OutOfSchool.EmailSender/OutOfSchool.EmailSender.csproj @@ -1,5 +1,10 @@  + + net8.0 + direct + + diff --git a/OutOfSchool/OutOfSchool.Encryption/OutOfSchool.Encryption.csproj b/OutOfSchool/OutOfSchool.Encryption/OutOfSchool.Encryption.csproj index 8e4712899..47733b5d3 100644 --- a/OutOfSchool/OutOfSchool.Encryption/OutOfSchool.Encryption.csproj +++ b/OutOfSchool/OutOfSchool.Encryption/OutOfSchool.Encryption.csproj @@ -2,6 +2,7 @@ net8.0 + direct enable db5c1f0a-62fe-42c7-ba72-df07e26ee2d9 diff --git a/OutOfSchool/OutOfSchool.GRPC/OutOfSchool.GRPC.csproj b/OutOfSchool/OutOfSchool.GRPC/OutOfSchool.GRPC.csproj index 68b74516e..f523cb2f9 100644 --- a/OutOfSchool/OutOfSchool.GRPC/OutOfSchool.GRPC.csproj +++ b/OutOfSchool/OutOfSchool.GRPC/OutOfSchool.GRPC.csproj @@ -1,4 +1,10 @@  + + + net8.0 + direct + + diff --git a/OutOfSchool/OutOfSchool.Migrations/OutOfSchool.Migrations.csproj b/OutOfSchool/OutOfSchool.Migrations/OutOfSchool.Migrations.csproj index 79e160be3..071cc1a74 100644 --- a/OutOfSchool/OutOfSchool.Migrations/OutOfSchool.Migrations.csproj +++ b/OutOfSchool/OutOfSchool.Migrations/OutOfSchool.Migrations.csproj @@ -8,6 +8,11 @@ Linux + + net8.0 + direct + + diff --git a/OutOfSchool/OutOfSchool.RazorTemplatesData/OutOfSchool.RazorTemplatesData.csproj b/OutOfSchool/OutOfSchool.RazorTemplatesData/OutOfSchool.RazorTemplatesData.csproj index 173373dcf..ee4faa41e 100644 --- a/OutOfSchool/OutOfSchool.RazorTemplatesData/OutOfSchool.RazorTemplatesData.csproj +++ b/OutOfSchool/OutOfSchool.RazorTemplatesData/OutOfSchool.RazorTemplatesData.csproj @@ -1,5 +1,10 @@  + + net8.0 + direct + + email true diff --git a/OutOfSchool/OutOfSchool.WebApi/OutOfSchool.WebApi.csproj b/OutOfSchool/OutOfSchool.WebApi/OutOfSchool.WebApi.csproj index d5bbdf0c8..88dc2b7e7 100644 --- a/OutOfSchool/OutOfSchool.WebApi/OutOfSchool.WebApi.csproj +++ b/OutOfSchool/OutOfSchool.WebApi/OutOfSchool.WebApi.csproj @@ -2,6 +2,7 @@ net8.0 + direct 1ac220b0-4848-4d5c-b0d9-b64657bd3b04 From ad087ce0e1463fa220b2f1b9d7309096c1069ce8 Mon Sep 17 00:00:00 2001 From: HlibBondarev Date: Wed, 4 Dec 2024 17:09:04 +0200 Subject: [PATCH 14/28] Changed OutOfSchool.AdminInitializer file - added code lines: net8.0 direct --- .../OutOfSchool.AdminInitializer.csproj | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/OutOfSchool/OutOfSchool.AdminInitializer/OutOfSchool.AdminInitializer.csproj b/OutOfSchool/OutOfSchool.AdminInitializer/OutOfSchool.AdminInitializer.csproj index 07f1e1e1d..9ad4e6812 100644 --- a/OutOfSchool/OutOfSchool.AdminInitializer/OutOfSchool.AdminInitializer.csproj +++ b/OutOfSchool/OutOfSchool.AdminInitializer/OutOfSchool.AdminInitializer.csproj @@ -6,6 +6,10 @@ enable dd058b3f-d89a-4456-a6df-2f32568b56ea + + net8.0 + direct + From 1c86f714a66efc341543c27f962b92c717680874 Mon Sep 17 00:00:00 2001 From: HlibBondarev Date: Wed, 4 Dec 2024 17:15:02 +0200 Subject: [PATCH 15/28] Changed OutOfSchool.Migrations file - added code lines: net8.0 direct --- .../OutOfSchool.Migrations/OutOfSchool.Migrations.csproj | 3 --- 1 file changed, 3 deletions(-) diff --git a/OutOfSchool/OutOfSchool.Migrations/OutOfSchool.Migrations.csproj b/OutOfSchool/OutOfSchool.Migrations/OutOfSchool.Migrations.csproj index 071cc1a74..914290af3 100644 --- a/OutOfSchool/OutOfSchool.Migrations/OutOfSchool.Migrations.csproj +++ b/OutOfSchool/OutOfSchool.Migrations/OutOfSchool.Migrations.csproj @@ -6,9 +6,6 @@ enable f3a869f2-8ab9-41e0-9336-d88036505f44 Linux - - - net8.0 direct From 9b2d15f892ca6dc5a3700292750610828156ebe7 Mon Sep 17 00:00:00 2001 From: HlibBondarev Date: Wed, 4 Dec 2024 22:20:16 +0200 Subject: [PATCH 16/28] Changed project files - OutOfSchool.AuthServer.Tests, OutOfSchool.Tests.Common, OutOfSchool.WebApi.IntegrationTests, OutOfSchool.WebApi.Tests, added the following lines of code: net8.0 direct --- .../OutOfSchool.AuthServer.Tests.csproj | 1 + .../OutOfSchool.WebApi.Tests/OutOfSchool.WebApi.Tests.csproj | 5 +++++ .../OutOfSchool.Tests.Common/OutOfSchool.Tests.Common.csproj | 1 + .../OutOfSchool.WebApi.IntegrationTests.csproj | 1 + 4 files changed, 8 insertions(+) diff --git a/OutOfSchool/OutOfSchool.AuthServer.Tests/OutOfSchool.AuthServer.Tests.csproj b/OutOfSchool/OutOfSchool.AuthServer.Tests/OutOfSchool.AuthServer.Tests.csproj index 9c5e3b2ea..ac5f974ba 100644 --- a/OutOfSchool/OutOfSchool.AuthServer.Tests/OutOfSchool.AuthServer.Tests.csproj +++ b/OutOfSchool/OutOfSchool.AuthServer.Tests/OutOfSchool.AuthServer.Tests.csproj @@ -6,6 +6,7 @@ net8.0 + direct diff --git a/OutOfSchool/OutOfSchool.WebApi.Tests/OutOfSchool.WebApi.Tests.csproj b/OutOfSchool/OutOfSchool.WebApi.Tests/OutOfSchool.WebApi.Tests.csproj index 9de6fa337..618913054 100644 --- a/OutOfSchool/OutOfSchool.WebApi.Tests/OutOfSchool.WebApi.Tests.csproj +++ b/OutOfSchool/OutOfSchool.WebApi.Tests/OutOfSchool.WebApi.Tests.csproj @@ -12,6 +12,11 @@ net8.0 + + + net8.0 + direct + diff --git a/OutOfSchool/Tests/OutOfSchool.Tests.Common/OutOfSchool.Tests.Common.csproj b/OutOfSchool/Tests/OutOfSchool.Tests.Common/OutOfSchool.Tests.Common.csproj index e5b8fdab2..0e8c6f422 100644 --- a/OutOfSchool/Tests/OutOfSchool.Tests.Common/OutOfSchool.Tests.Common.csproj +++ b/OutOfSchool/Tests/OutOfSchool.Tests.Common/OutOfSchool.Tests.Common.csproj @@ -1,6 +1,7 @@  net8.0 + direct diff --git a/OutOfSchool/Tests/OutOfSchool.WebApi.IntegrationTests/OutOfSchool.WebApi.IntegrationTests.csproj b/OutOfSchool/Tests/OutOfSchool.WebApi.IntegrationTests/OutOfSchool.WebApi.IntegrationTests.csproj index fd5e7640e..2a3a3d04e 100644 --- a/OutOfSchool/Tests/OutOfSchool.WebApi.IntegrationTests/OutOfSchool.WebApi.IntegrationTests.csproj +++ b/OutOfSchool/Tests/OutOfSchool.WebApi.IntegrationTests/OutOfSchool.WebApi.IntegrationTests.csproj @@ -3,6 +3,7 @@ false net8.0 + direct From 6f41ba50ce8b9520b05b481a527b028e8157e693 Mon Sep 17 00:00:00 2001 From: HlibBondarev Date: Wed, 4 Dec 2024 22:28:08 +0200 Subject: [PATCH 17/28] Changed version of Microsoft.Extensions.Caching.StackExchangeRedis from 9.0.0 to 8.0.11. --- OutOfSchool/Directory.Packages.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OutOfSchool/Directory.Packages.props b/OutOfSchool/Directory.Packages.props index e6b06b3a8..228e6e92c 100644 --- a/OutOfSchool/Directory.Packages.props +++ b/OutOfSchool/Directory.Packages.props @@ -47,7 +47,7 @@ - + From 075316b3f5adbe81940edb272c5ec73dc712a8e2 Mon Sep 17 00:00:00 2001 From: HlibBondarev Date: Wed, 4 Dec 2024 22:43:08 +0200 Subject: [PATCH 18/28] Changed project files - added the following lines of code: net8.0 direct --- .../OutOfSchool.AdminInitializer.csproj | 6 ++---- .../OutOfSchool.AuthCommon.csproj | 11 ++++++----- .../OutOfSchool.AuthServer.Tests.csproj | 8 ++++---- .../OutOfSchool.AuthorizationServer.csproj | 6 +----- .../OutOfSchool.BackgroundJobs.csproj | 8 ++++---- .../OutOfSchool.BusinessLogic.csproj | 16 ++++++++-------- .../OutOfSchool.Common/OutOfSchool.Common.csproj | 9 +++++---- .../OutOfSchool.DataAccess.csproj | 10 +++++----- .../OutOfSchool.ElasticsearchData.csproj | 8 ++++---- .../OutOfSchool.WebApi.Tests.csproj | 14 +++++--------- .../OutOfSchool.WebApi.IntegrationTests.csproj | 2 +- 11 files changed, 45 insertions(+), 53 deletions(-) diff --git a/OutOfSchool/OutOfSchool.AdminInitializer/OutOfSchool.AdminInitializer.csproj b/OutOfSchool/OutOfSchool.AdminInitializer/OutOfSchool.AdminInitializer.csproj index 9ad4e6812..7676bb1c5 100644 --- a/OutOfSchool/OutOfSchool.AdminInitializer/OutOfSchool.AdminInitializer.csproj +++ b/OutOfSchool/OutOfSchool.AdminInitializer/OutOfSchool.AdminInitializer.csproj @@ -1,15 +1,13 @@ net8.0 + direct Exe enable enable dd058b3f-d89a-4456-a6df-2f32568b56ea - - net8.0 - direct - + diff --git a/OutOfSchool/OutOfSchool.AuthCommon/OutOfSchool.AuthCommon.csproj b/OutOfSchool/OutOfSchool.AuthCommon/OutOfSchool.AuthCommon.csproj index dda5c0831..2451c61c3 100644 --- a/OutOfSchool/OutOfSchool.AuthCommon/OutOfSchool.AuthCommon.csproj +++ b/OutOfSchool/OutOfSchool.AuthCommon/OutOfSchool.AuthCommon.csproj @@ -1,4 +1,10 @@ + + + net8.0 + direct + + auth enable @@ -6,11 +12,6 @@ true - - net8.0 - direct - - diff --git a/OutOfSchool/OutOfSchool.AuthServer.Tests/OutOfSchool.AuthServer.Tests.csproj b/OutOfSchool/OutOfSchool.AuthServer.Tests/OutOfSchool.AuthServer.Tests.csproj index ac5f974ba..6006964b7 100644 --- a/OutOfSchool/OutOfSchool.AuthServer.Tests/OutOfSchool.AuthServer.Tests.csproj +++ b/OutOfSchool/OutOfSchool.AuthServer.Tests/OutOfSchool.AuthServer.Tests.csproj @@ -1,13 +1,13 @@  - - false - - net8.0 direct + + + false + diff --git a/OutOfSchool/OutOfSchool.AuthorizationServer/OutOfSchool.AuthorizationServer.csproj b/OutOfSchool/OutOfSchool.AuthorizationServer/OutOfSchool.AuthorizationServer.csproj index 6f37852db..bbecb89fa 100644 --- a/OutOfSchool/OutOfSchool.AuthorizationServer/OutOfSchool.AuthorizationServer.csproj +++ b/OutOfSchool/OutOfSchool.AuthorizationServer/OutOfSchool.AuthorizationServer.csproj @@ -2,16 +2,12 @@ net8.0 + direct 23768b69-757e-4a20-894f-dcf7181971ca enable enable - - net8.0 - direct - - diff --git a/OutOfSchool/OutOfSchool.BackgroundJobs/OutOfSchool.BackgroundJobs.csproj b/OutOfSchool/OutOfSchool.BackgroundJobs/OutOfSchool.BackgroundJobs.csproj index 0b4831d90..ec89a85f3 100644 --- a/OutOfSchool/OutOfSchool.BackgroundJobs/OutOfSchool.BackgroundJobs.csproj +++ b/OutOfSchool/OutOfSchool.BackgroundJobs/OutOfSchool.BackgroundJobs.csproj @@ -1,13 +1,13 @@  - enable - enable + net8.0 + direct - net8.0 - direct + enable + enable diff --git a/OutOfSchool/OutOfSchool.BusinessLogic/OutOfSchool.BusinessLogic.csproj b/OutOfSchool/OutOfSchool.BusinessLogic/OutOfSchool.BusinessLogic.csproj index 32fee2d55..8102e8458 100644 --- a/OutOfSchool/OutOfSchool.BusinessLogic/OutOfSchool.BusinessLogic.csproj +++ b/OutOfSchool/OutOfSchool.BusinessLogic/OutOfSchool.BusinessLogic.csproj @@ -1,7 +1,12 @@  - - enable - + + + net8.0 + direct + + + enable + $(NoWarn);1591 @@ -14,11 +19,6 @@ latest - - net8.0 - direct - - diff --git a/OutOfSchool/OutOfSchool.Common/OutOfSchool.Common.csproj b/OutOfSchool/OutOfSchool.Common/OutOfSchool.Common.csproj index da3b57514..fe7db8f98 100644 --- a/OutOfSchool/OutOfSchool.Common/OutOfSchool.Common.csproj +++ b/OutOfSchool/OutOfSchool.Common/OutOfSchool.Common.csproj @@ -1,12 +1,13 @@  - - true - $(NoWarn);1591 - net8.0 direct + + true + $(NoWarn);1591 + + diff --git a/OutOfSchool/OutOfSchool.DataAccess/OutOfSchool.DataAccess.csproj b/OutOfSchool/OutOfSchool.DataAccess/OutOfSchool.DataAccess.csproj index 4c6fdd146..10848458c 100644 --- a/OutOfSchool/OutOfSchool.DataAccess/OutOfSchool.DataAccess.csproj +++ b/OutOfSchool/OutOfSchool.DataAccess/OutOfSchool.DataAccess.csproj @@ -1,5 +1,10 @@  + + net8.0 + direct + + OutOfSchool.Services @@ -9,11 +14,6 @@ $(NoWarn);1591 - - net8.0 - direct - - diff --git a/OutOfSchool/OutOfSchool.ElasticsearchData/OutOfSchool.ElasticsearchData.csproj b/OutOfSchool/OutOfSchool.ElasticsearchData/OutOfSchool.ElasticsearchData.csproj index da3853845..cc989f342 100644 --- a/OutOfSchool/OutOfSchool.ElasticsearchData/OutOfSchool.ElasticsearchData.csproj +++ b/OutOfSchool/OutOfSchool.ElasticsearchData/OutOfSchool.ElasticsearchData.csproj @@ -1,13 +1,13 @@  - - true - $(NoWarn);1591 - net8.0 direct + + true + $(NoWarn);1591 + diff --git a/OutOfSchool/OutOfSchool.WebApi.Tests/OutOfSchool.WebApi.Tests.csproj b/OutOfSchool/OutOfSchool.WebApi.Tests/OutOfSchool.WebApi.Tests.csproj index 618913054..28d534374 100644 --- a/OutOfSchool/OutOfSchool.WebApi.Tests/OutOfSchool.WebApi.Tests.csproj +++ b/OutOfSchool/OutOfSchool.WebApi.Tests/OutOfSchool.WebApi.Tests.csproj @@ -1,5 +1,10 @@  + + net8.0 + direct + + false @@ -9,15 +14,6 @@ $(NoWarn);1591 - - net8.0 - - - - net8.0 - direct - - diff --git a/OutOfSchool/Tests/OutOfSchool.WebApi.IntegrationTests/OutOfSchool.WebApi.IntegrationTests.csproj b/OutOfSchool/Tests/OutOfSchool.WebApi.IntegrationTests/OutOfSchool.WebApi.IntegrationTests.csproj index 2a3a3d04e..19405240a 100644 --- a/OutOfSchool/Tests/OutOfSchool.WebApi.IntegrationTests/OutOfSchool.WebApi.IntegrationTests.csproj +++ b/OutOfSchool/Tests/OutOfSchool.WebApi.IntegrationTests/OutOfSchool.WebApi.IntegrationTests.csproj @@ -1,9 +1,9 @@  - false net8.0 direct + false From 2e48d65753acb272ec8d3ea73964b65c485cddc6 Mon Sep 17 00:00:00 2001 From: HlibBondarev Date: Thu, 5 Dec 2024 11:40:36 +0200 Subject: [PATCH 19/28] Added tests to CacheServiceTests class. --- .../Redis/CacheServiceTests.cs | 101 ++++++++++++++---- 1 file changed, 80 insertions(+), 21 deletions(-) diff --git a/OutOfSchool/OutOfSchool.WebApi.Tests/Redis/CacheServiceTests.cs b/OutOfSchool/OutOfSchool.WebApi.Tests/Redis/CacheServiceTests.cs index 13098ad67..d6249a522 100644 --- a/OutOfSchool/OutOfSchool.WebApi.Tests/Redis/CacheServiceTests.cs +++ b/OutOfSchool/OutOfSchool.WebApi.Tests/Redis/CacheServiceTests.cs @@ -1,8 +1,8 @@ using System; using System.Collections.Generic; using System.Text; -using System.Threading; using System.Threading.Tasks; +using Bogus; using FluentAssertions; using Microsoft.Extensions.Caching.Distributed; using Microsoft.Extensions.Options; @@ -16,13 +16,20 @@ namespace OutOfSchool.WebApi.Tests.Redis; [TestFixture] public class CacheServiceTests { + private const int RANDOMSTRINGSIZE = 50; + + private string expectedValue; + private string expectedKey; private Mock distributedCacheMock; private Mock> redisConfigMock; private ICacheService cacheService; + private IReadWriteCacheService readWriteCacheService; [SetUp] public void SetUp() { + expectedValue = new string(new Faker().Random.Chars(min: (char)0, max: (char)127, count: RANDOMSTRINGSIZE)); + expectedKey = new string(new Faker().Random.Chars(min: (char)0, max: (char)127, count: RANDOMSTRINGSIZE)); distributedCacheMock = new Mock(); redisConfigMock = new Mock>(); redisConfigMock.Setup(c => c.Value).Returns(new RedisConfig @@ -32,6 +39,7 @@ public void SetUp() SlidingExpirationInterval = TimeSpan.FromMinutes(1), }); cacheService = new CacheService(distributedCacheMock.Object, redisConfigMock.Object); + readWriteCacheService = new CacheService(distributedCacheMock.Object, redisConfigMock.Object); } [Test] @@ -43,7 +51,14 @@ public async Task GetOrAddAsync_WhenDataExistsAndNotExpired_ShouldReturnData() {"ExpectedKey", "ExpectedValue"}, }; distributedCacheMock.Setup(c => c.Get(It.IsAny())) - .Returns(Encoding.UTF8.GetBytes(JsonSerializerHelper.Serialize(expected))); + .Returns(Encoding.UTF8.GetBytes(JsonSerializerHelper.Serialize(expected))) + .Verifiable(Times.Once); + + distributedCacheMock.Setup(c => c.Set( + It.IsAny(), + It.IsAny(), + It.IsAny())) + .Verifiable(Times.Never); // Act var result = await cacheService.GetOrAddAsync("Example", () => Task.FromResult(expected)); @@ -51,12 +66,7 @@ public async Task GetOrAddAsync_WhenDataExistsAndNotExpired_ShouldReturnData() // Assert result.Keys.Should().Contain("ExpectedKey"); result.Values.Should().Contain("ExpectedValue"); - distributedCacheMock.Verify( - c => c.Set( - It.IsAny(), - It.IsAny(), - It.IsAny()), - Times.Never); + distributedCacheMock.VerifyAll(); } [Test] @@ -68,7 +78,13 @@ public async Task GetOrAddAsync_WhenDataNotExistsOrExpired_ShouldSaveNewData() {"ExpectedKey", "ExpectedValue"}, }; distributedCacheMock.Setup(c => c.Get(It.IsAny())) - .Returns((byte[]) null); + .Returns((byte[])null) + .Verifiable(Times.Once); + distributedCacheMock.Setup(c => c.Set( + It.IsAny(), + It.IsAny(), + It.IsAny())) + .Verifiable(Times.Once); // Act var result = await cacheService.GetOrAddAsync("Example", () => Task.FromResult(expected)); @@ -76,23 +92,66 @@ public async Task GetOrAddAsync_WhenDataNotExistsOrExpired_ShouldSaveNewData() // Assert result.Keys.Should().Contain("ExpectedKey"); result.Values.Should().Contain("ExpectedValue"); - distributedCacheMock.Verify( - c => c.Set( - It.IsAny(), - It.IsAny(), - It.IsAny()), - Times.Once); + distributedCacheMock.VerifyAll(); + } + + [Test] + public async Task RemoveAsync_ShouldCallCacheRemoveOnce() + { + // Arrange + distributedCacheMock.Setup(c => c.Remove(expectedKey)) + .Verifiable(Times.Once); + + // Act + await cacheService.RemoveAsync(expectedKey); + + // Assert + distributedCacheMock.VerifyAll(); + } + + [Test] + public async Task ReadAsync_WhenDataExistsInCacheAndNotExpired_ShouldReturnData() + { + // Arrange + distributedCacheMock.Setup(c => c.Get(expectedKey)) + .Returns(Encoding.UTF8.GetBytes(expectedValue)) + .Verifiable(Times.Once); + + // Act + var result = await readWriteCacheService.ReadAsync(expectedKey); + + // Assert + result.Should().Be(expectedValue); + distributedCacheMock.VerifyAll(); + } + + [Test] + public async Task ReadAsync_WhenDataNotExistsOrExpired_ShouldReturnNull() + { + // Arrange + distributedCacheMock.Setup(c => c.Get(expectedKey)) + .Returns(Encoding.UTF8.GetBytes(string.Empty)) + .Verifiable(Times.Once); + + // Act + var result = await readWriteCacheService.ReadAsync(expectedKey); + + // Assert + result.Should().Be(string.Empty); + distributedCacheMock.VerifyAll(); } [Test] - public async Task RemoveAsync_ShouldCallCacheRemove() + public async Task WriteAsync_ShouldCallCacheSetOnce() { - // Arrange & Act - await cacheService.RemoveAsync("Example"); + // Arrange + distributedCacheMock.Setup(c => c.Set(expectedKey, Encoding.UTF8.GetBytes(expectedValue), It.IsAny())) + .Verifiable(Times.Once); + + // Act + await readWriteCacheService.WriteAsync(expectedKey, expectedValue); // Assert - distributedCacheMock.Verify( - c => c.RemoveAsync("Example", It.IsAny()), - Times.Once); + distributedCacheMock.VerifyAll(); } } \ No newline at end of file From 4782a85e0c4484c20b27c253f1e0e4d6c3664466 Mon Sep 17 00:00:00 2001 From: HlibBondarev Date: Thu, 5 Dec 2024 12:55:30 +0200 Subject: [PATCH 20/28] Added tests to CacheServiceTests class. --- .../DraftStorage/DraftStorageServiceTests.cs | 159 ++++++++++++++++++ 1 file changed, 159 insertions(+) create mode 100644 OutOfSchool/OutOfSchool.WebApi.Tests/Services/DraftStorage/DraftStorageServiceTests.cs diff --git a/OutOfSchool/OutOfSchool.WebApi.Tests/Services/DraftStorage/DraftStorageServiceTests.cs b/OutOfSchool/OutOfSchool.WebApi.Tests/Services/DraftStorage/DraftStorageServiceTests.cs new file mode 100644 index 000000000..a0ae7a79c --- /dev/null +++ b/OutOfSchool/OutOfSchool.WebApi.Tests/Services/DraftStorage/DraftStorageServiceTests.cs @@ -0,0 +1,159 @@ +using System; +using System.Threading.Tasks; +using AutoMapper; +using Bogus; +using FluentAssertions; +using Microsoft.Extensions.Logging; +using Moq; +using NUnit.Framework; +using OutOfSchool.BusinessLogic.Models.Workshops; +using OutOfSchool.BusinessLogic.Models.Workshops.Drafts; +using OutOfSchool.BusinessLogic.Services.DraftStorage; +using OutOfSchool.Common; +using OutOfSchool.Common.Enums; +using OutOfSchool.Redis; +using OutOfSchool.Tests.Common.TestDataGenerators; + +namespace OutOfSchool.WebApi.Tests.Services.DraftStorage; + +[TestFixture] +public class DraftStorageServiceTests +{ + private const int RANDOMSTRINGSIZE = 50; + + private string key; + private string cacheKey; + private Mock readWriteCacheServiceMock; + private Mock>> loggerMock; + private IDraftStorageService draftStorageService; + + [SetUp] + public void SetUp() + { + key = new string(new Faker().Random.Chars(min: (char)0, max: (char)127, count: RANDOMSTRINGSIZE)); + cacheKey = GetCacheKey(key, typeof(WorkshopMainRequiredPropertiesDto)); + loggerMock = new Mock>>(); + readWriteCacheServiceMock = new Mock(); + draftStorageService = new DraftStorageService(readWriteCacheServiceMock.Object, loggerMock.Object); + } + + [Test] + public async Task RestoreAsync_WhenDraftExistsInCache_ShouldRestoreAppropriatedEntity() + { + // Arrange + var workshopDraft = GetWorkshopFakeDraft(); + readWriteCacheServiceMock.Setup(c => c.ReadAsync(cacheKey)) + .Returns(() => Task.FromResult(JsonSerializerHelper.Serialize(workshopDraft))) + .Verifiable(Times.Once); + + // Act + var result = await draftStorageService.RestoreAsync(key).ConfigureAwait(false); + + // Assert + result.Should().BeOfType(); + result.Should().BeEquivalentTo(workshopDraft); + readWriteCacheServiceMock.VerifyAll(); + } + + [Test] + public async Task RestoreAsync_WhenDraftIsAbsentInCache_ShouldRestoreDefaultEntity() + { + // Arrange + var workshopDraft = default(WorkshopMainRequiredPropertiesDto); + readWriteCacheServiceMock.Setup(c => c.ReadAsync(cacheKey)) + .Returns(() => Task.FromResult(JsonSerializerHelper.Serialize(workshopDraft))) + .Verifiable(Times.Once); + + // Act + var result = await draftStorageService.RestoreAsync(key).ConfigureAwait(false); + + // Assert + result.Should().Be(workshopDraft); + readWriteCacheServiceMock.VerifyAll(); + } + + [Test] + public void CreateAsync_ShouldCallWriteAsyncOnce() + { + // Arrange + var workshopDraft = GetWorkshopFakeDraft(); + var workshopJsonString = JsonSerializerHelper.Serialize(workshopDraft); + readWriteCacheServiceMock.Setup(c => c.WriteAsync( + cacheKey, + workshopJsonString, + null, + null)) + .Verifiable(Times.Once); + + // Act + var result = draftStorageService.CreateAsync(key, workshopDraft).ConfigureAwait(false); + + // Assert + readWriteCacheServiceMock.VerifyAll(); + } + + [Test] + public async Task RemoveAsync_WhenDataExistsInCache_ShouldCallRemoveAsyncAndReadAsyncOnce() + { + // Arrange + var workshopJsonString = JsonSerializerHelper.Serialize(GetWorkshopFakeDraft()); + readWriteCacheServiceMock.Setup(c => c.ReadAsync(cacheKey)) + .Returns(() => Task.FromResult(workshopJsonString)) + .Verifiable(Times.Once); + readWriteCacheServiceMock.Setup(c => c.RemoveAsync(cacheKey)) + .Returns(() => Task.FromResult(workshopJsonString)) + .Verifiable(Times.Once); + + // Act + await draftStorageService.RemoveAsync(key).ConfigureAwait(false); + + // Assert + readWriteCacheServiceMock.VerifyAll(); + } + + [Test] + public async Task RemoveAsync_WhenDataIsAbsentInCache_ShouldNeverCallRemoveAsync() + { + // Arrange + readWriteCacheServiceMock.Setup(c => c.ReadAsync(cacheKey)) + .Returns(() => Task.FromResult(string.Empty)).Verifiable(Times.Once); + readWriteCacheServiceMock.Setup(c => c.RemoveAsync(cacheKey)) + .Returns(() => Task.FromResult(string.Empty)).Verifiable(Times.Never); + + // Act + await draftStorageService.RemoveAsync(key).ConfigureAwait(false); + + // Assert + readWriteCacheServiceMock.VerifyAll(); + } + + private static WorkshopMainRequiredPropertiesDto GetWorkshopFakeDraft() + { + var workshopFacker = new Faker() + .RuleFor(w => w.Id, f => f.Random.Guid()) + .RuleFor(w => w.Title, f => f.Name.FullName()) + .RuleFor(w => w.ShortTitle, f => f.Name.LastName()) + .RuleFor(w => w.Phone, f => f.Phone.PhoneNumber()) + .RuleFor(w => w.Email, f => f.Internet.Email()) + .RuleFor(w => w.MinAge, f => f.Random.Int(5, 9)) + .RuleFor(w => w.MaxAge, f => f.Random.Int(10, 13)) + .RuleFor(w => w.IsPaid, f => f.Random.Bool()) + .RuleFor(w => w.Price, f => f.Random.Decimal()) + .RuleFor(w => w.AvailableSeats, f => f.Random.UInt(0, 13)) + .RuleFor(w => w.CompetitiveSelection, f => true) + .RuleFor(w => w.CompetitiveSelectionDescription, f => f.Lorem.Paragraph()) + .RuleFor(w => w.ProviderId, f => f.Random.Guid()); + + var workshop = workshopFacker.Generate(); + workshop.FormOfLearning = FormOfLearning.Mixed; + workshop.PayRate = PayRateType.Classes; + workshop.DateTimeRanges = DateTimeRangeDtoGenerator.Generate(4); + + return workshopFacker.Generate(); + } + + private static string GetCacheKey(string key, Type type) + { + return $"{key}_{type.Name}"; + } +} \ No newline at end of file From 65bcffce064cc212d4a8e66400f029a70f932945 Mon Sep 17 00:00:00 2001 From: HlibBondarev Date: Thu, 5 Dec 2024 13:51:07 +0200 Subject: [PATCH 21/28] 1) Changed Startup class: - deleted redundant Using directive - 'using System.Text.Json;' - replaced the line of code: .AddJsonOptions(options => options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter(JsonNamingPolicy.CamelCase))); with: .AddJsonOptions(options => options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter())); 2) Changed RestoreAsync_WhenDraftIsAbsentInCache_ShouldRestoreDefaultEntity() method in DraftStorageServiceTests class. --- .../Services/DraftStorage/DraftStorageServiceTests.cs | 4 +--- OutOfSchool/OutOfSchool.WebApi/Startup.cs | 3 +-- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/OutOfSchool/OutOfSchool.WebApi.Tests/Services/DraftStorage/DraftStorageServiceTests.cs b/OutOfSchool/OutOfSchool.WebApi.Tests/Services/DraftStorage/DraftStorageServiceTests.cs index a0ae7a79c..ee5af811f 100644 --- a/OutOfSchool/OutOfSchool.WebApi.Tests/Services/DraftStorage/DraftStorageServiceTests.cs +++ b/OutOfSchool/OutOfSchool.WebApi.Tests/Services/DraftStorage/DraftStorageServiceTests.cs @@ -1,12 +1,10 @@ using System; using System.Threading.Tasks; -using AutoMapper; using Bogus; using FluentAssertions; using Microsoft.Extensions.Logging; using Moq; using NUnit.Framework; -using OutOfSchool.BusinessLogic.Models.Workshops; using OutOfSchool.BusinessLogic.Models.Workshops.Drafts; using OutOfSchool.BusinessLogic.Services.DraftStorage; using OutOfSchool.Common; @@ -61,7 +59,7 @@ public async Task RestoreAsync_WhenDraftIsAbsentInCache_ShouldRestoreDefaultEnti // Arrange var workshopDraft = default(WorkshopMainRequiredPropertiesDto); readWriteCacheServiceMock.Setup(c => c.ReadAsync(cacheKey)) - .Returns(() => Task.FromResult(JsonSerializerHelper.Serialize(workshopDraft))) + .Returns(() => Task.FromResult(string.Empty)) .Verifiable(Times.Once); // Act diff --git a/OutOfSchool/OutOfSchool.WebApi/Startup.cs b/OutOfSchool/OutOfSchool.WebApi/Startup.cs index 6b5d3c0c1..74c4a762c 100644 --- a/OutOfSchool/OutOfSchool.WebApi/Startup.cs +++ b/OutOfSchool/OutOfSchool.WebApi/Startup.cs @@ -1,4 +1,3 @@ -using System.Text.Json; using System.Text.Json.Serialization; using Asp.Versioning.ApiExplorer; using AutoMapper; @@ -207,7 +206,7 @@ public static async Task AddApplicationServices(this WebApplicationBuilder build }) .AddJsonOptions(options => - options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter(JsonNamingPolicy.CamelCase))); + options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter())); services.AddHttpClient(configuration["Communication:ClientName"]) .AddHttpMessageHandler(handler => From 8d4d3cad50beb64059bb55870662965b45f89edb Mon Sep 17 00:00:00 2001 From: HlibBondarev Date: Thu, 5 Dec 2024 15:15:58 +0200 Subject: [PATCH 22/28] 1) Created the WorkshopMainRequiredPropertiesDtoGenerator class. 2) Added tests to the WorkshopDraftStorageControllerTests class. 3) Modified the DraftStorageServiceTests class to use the WorkshopMainRequiredPropertiesDtoGenerator class. 4) Changed the return value of the StoreDraft() method in the DraftStorageController() class. --- .../WorkshopMainRequiredPropertiesDto.cs | 2 +- .../WorkshopDraftStorageControllerTests.cs | 132 ++++++++++++++++++ .../DraftStorage/DraftStorageServiceTests.cs | 27 +--- .../Controllers/V1/DraftStorageController.cs | 2 +- ...kshopMainRequiredPropertiesDtoGenerator.cs | 38 +++++ 5 files changed, 174 insertions(+), 27 deletions(-) create mode 100644 OutOfSchool/OutOfSchool.WebApi.Tests/Controllers/WorkshopDraftStorageControllerTests.cs create mode 100644 OutOfSchool/Tests/OutOfSchool.Tests.Common/TestDataGenerators/WorkshopMainRequiredPropertiesDtoGenerator.cs diff --git a/OutOfSchool/OutOfSchool.BusinessLogic/Models/Workshops/Drafts/WorkshopMainRequiredPropertiesDto.cs b/OutOfSchool/OutOfSchool.BusinessLogic/Models/Workshops/Drafts/WorkshopMainRequiredPropertiesDto.cs index 7c017cdac..c18ffb9b5 100644 --- a/OutOfSchool/OutOfSchool.BusinessLogic/Models/Workshops/Drafts/WorkshopMainRequiredPropertiesDto.cs +++ b/OutOfSchool/OutOfSchool.BusinessLogic/Models/Workshops/Drafts/WorkshopMainRequiredPropertiesDto.cs @@ -15,7 +15,7 @@ namespace OutOfSchool.BusinessLogic.Models.Workshops.Drafts; [JsonDerivedType(typeof(WorkshopDescriptionDto), typeDiscriminator: "withDescription")] [JsonDerivedType(typeof(WorkshopContactsDto), typeDiscriminator: "withContacts")] [JsonDerivedType(typeof(WorkshopStaffDto), typeDiscriminator: "withStaff")] -public class WorkshopMainRequiredPropertiesDto : IValidatableObject //16 + 12 + 12 + 5 + 3 = 48 +public class WorkshopMainRequiredPropertiesDto : IValidatableObject { public Guid Id { get; set; } diff --git a/OutOfSchool/OutOfSchool.WebApi.Tests/Controllers/WorkshopDraftStorageControllerTests.cs b/OutOfSchool/OutOfSchool.WebApi.Tests/Controllers/WorkshopDraftStorageControllerTests.cs new file mode 100644 index 000000000..f833c9437 --- /dev/null +++ b/OutOfSchool/OutOfSchool.WebApi.Tests/Controllers/WorkshopDraftStorageControllerTests.cs @@ -0,0 +1,132 @@ +using FluentAssertions; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Moq; +using NUnit.Framework; +using OutOfSchool.BusinessLogic.Common; +using OutOfSchool.BusinessLogic.Models.Workshops; +using OutOfSchool.BusinessLogic.Models.Workshops.Drafts; +using OutOfSchool.BusinessLogic.Services.DraftStorage; +using OutOfSchool.Tests.Common.TestDataGenerators; +using OutOfSchool.WebApi.Controllers.V1; +using System.Security.Claims; +using System.Threading.Tasks; + +namespace OutOfSchool.WebApi.Tests.Controllers; + +[TestFixture] +public class WorkshopDraftStorageControllerTests +{ + private const int RANDOMSTRINGSIZE = 50; + + private string key; + private Mock> draftStorageService; + private WorkshopDraftStorageController controller; + private ClaimsPrincipal user; + private WorkshopMainRequiredPropertiesDto draft; + + [SetUp] + public void Setup() + { + draft = GetWorkshopFakeDraft(); + draftStorageService = new Mock>(); + controller = new WorkshopDraftStorageController(draftStorageService.Object); + user = new ClaimsPrincipal(new ClaimsIdentity()); + key = GettingUserProperties.GetUserId(user); + controller.ControllerContext.HttpContext = new DefaultHttpContext { User = user }; + } + + [Test] + public async Task StoreDraft_WhenModelIsValid_ReturnsOkObjectResult() + { + // Arrange + draftStorageService.Setup(ds => ds.CreateAsync(key, draft)) + .Verifiable(Times.Once); + var resultValue = $"{draft.GetType().Name} is stored"; + + // Act + var result = await controller.StoreDraft(draft).ConfigureAwait(false); + + // Assert + result.Should() + .BeOfType() + .Which.StatusCode + .Should() + .Be(StatusCodes.Status200OK); + result.Should() + .BeOfType() + .Which.Value.Should().Be(resultValue); + draftStorageService.VerifyAll(); + } + + [Test] + public async Task StoreDraft_WhenModelIsInvalid_ReturnsBadRequestObjectResult() + { + // Arrange + var errorKey = "DraftStorage"; + var errorMessage = "Invalid model state"; + controller.ModelState.AddModelError(errorKey, errorMessage); + draftStorageService.Setup(ds => ds.CreateAsync(key, draft)) + .Verifiable(Times.Never); + + // Act + var result = await controller.StoreDraft(draft).ConfigureAwait(false); + + // Assert + result.Should() + .BeOfType() + .Which.StatusCode + .Should() + .Be(StatusCodes.Status400BadRequest); + draftStorageService.VerifyAll(); + } + + [Test] + public async Task RestoreDraft_WhenDraftExistsInCache_ReturnsDraftAtActionResult() + { + // Arrange + draftStorageService.Setup(ds => ds.RestoreAsync(key)) + .ReturnsAsync(draft) + .Verifiable(Times.Once); + + // Act + var result = await controller.RestoreDraft().ConfigureAwait(false); + + // Assert + result.Should() + .BeOfType() + .Which.StatusCode + .Should() + .Be(StatusCodes.Status200OK); + result.Should() + .BeOfType() + .Which.Value.Should().Be(draft); + draftStorageService.VerifyAll(); + } + + [Test] + public async Task RestoreDraft_WhenDraftIsAbsentInCache_ReturnsDefaultDraftAtActionResult() + { + // Arrange + draftStorageService.Setup(ds => ds.RestoreAsync(key)) + .ReturnsAsync(default(WorkshopMainRequiredPropertiesDto)) + .Verifiable(Times.Once); + + // Act + var result = await controller.RestoreDraft().ConfigureAwait(false); + + // Assert + result.Should() + .BeOfType() + .Which.StatusCode + .Should() + .Be(StatusCodes.Status200OK); + result.Should() + .BeOfType() + .Which.Value.Should().Be(default(WorkshopBaseDto)); + draftStorageService.VerifyAll(); + } + + private static WorkshopMainRequiredPropertiesDto GetWorkshopFakeDraft() => + WorkshopMainRequiredPropertiesDtoGenerator.Generate(); +} \ No newline at end of file diff --git a/OutOfSchool/OutOfSchool.WebApi.Tests/Services/DraftStorage/DraftStorageServiceTests.cs b/OutOfSchool/OutOfSchool.WebApi.Tests/Services/DraftStorage/DraftStorageServiceTests.cs index ee5af811f..1790f8dcb 100644 --- a/OutOfSchool/OutOfSchool.WebApi.Tests/Services/DraftStorage/DraftStorageServiceTests.cs +++ b/OutOfSchool/OutOfSchool.WebApi.Tests/Services/DraftStorage/DraftStorageServiceTests.cs @@ -8,7 +8,6 @@ using OutOfSchool.BusinessLogic.Models.Workshops.Drafts; using OutOfSchool.BusinessLogic.Services.DraftStorage; using OutOfSchool.Common; -using OutOfSchool.Common.Enums; using OutOfSchool.Redis; using OutOfSchool.Tests.Common.TestDataGenerators; @@ -125,30 +124,8 @@ public async Task RemoveAsync_WhenDataIsAbsentInCache_ShouldNeverCallRemoveAsync readWriteCacheServiceMock.VerifyAll(); } - private static WorkshopMainRequiredPropertiesDto GetWorkshopFakeDraft() - { - var workshopFacker = new Faker() - .RuleFor(w => w.Id, f => f.Random.Guid()) - .RuleFor(w => w.Title, f => f.Name.FullName()) - .RuleFor(w => w.ShortTitle, f => f.Name.LastName()) - .RuleFor(w => w.Phone, f => f.Phone.PhoneNumber()) - .RuleFor(w => w.Email, f => f.Internet.Email()) - .RuleFor(w => w.MinAge, f => f.Random.Int(5, 9)) - .RuleFor(w => w.MaxAge, f => f.Random.Int(10, 13)) - .RuleFor(w => w.IsPaid, f => f.Random.Bool()) - .RuleFor(w => w.Price, f => f.Random.Decimal()) - .RuleFor(w => w.AvailableSeats, f => f.Random.UInt(0, 13)) - .RuleFor(w => w.CompetitiveSelection, f => true) - .RuleFor(w => w.CompetitiveSelectionDescription, f => f.Lorem.Paragraph()) - .RuleFor(w => w.ProviderId, f => f.Random.Guid()); - - var workshop = workshopFacker.Generate(); - workshop.FormOfLearning = FormOfLearning.Mixed; - workshop.PayRate = PayRateType.Classes; - workshop.DateTimeRanges = DateTimeRangeDtoGenerator.Generate(4); - - return workshopFacker.Generate(); - } + private static WorkshopMainRequiredPropertiesDto GetWorkshopFakeDraft() => + WorkshopMainRequiredPropertiesDtoGenerator.Generate(); private static string GetCacheKey(string key, Type type) { diff --git a/OutOfSchool/OutOfSchool.WebApi/Controllers/V1/DraftStorageController.cs b/OutOfSchool/OutOfSchool.WebApi/Controllers/V1/DraftStorageController.cs index 64677fa05..610dc2498 100644 --- a/OutOfSchool/OutOfSchool.WebApi/Controllers/V1/DraftStorageController.cs +++ b/OutOfSchool/OutOfSchool.WebApi/Controllers/V1/DraftStorageController.cs @@ -33,7 +33,7 @@ public async Task StoreDraft([FromBody] T draftDto) await draftStorageService.CreateAsync(GettingUserProperties.GetUserId(User), draftDto).ConfigureAwait(false); - return Ok($"{typeof(T).Name} is stored ({JsonSerializerHelper.Serialize(draftDto)})"); + return Ok($"{typeof(T).Name} is stored"); } /// Restores the entity draft. diff --git a/OutOfSchool/Tests/OutOfSchool.Tests.Common/TestDataGenerators/WorkshopMainRequiredPropertiesDtoGenerator.cs b/OutOfSchool/Tests/OutOfSchool.Tests.Common/TestDataGenerators/WorkshopMainRequiredPropertiesDtoGenerator.cs new file mode 100644 index 000000000..cb565171b --- /dev/null +++ b/OutOfSchool/Tests/OutOfSchool.Tests.Common/TestDataGenerators/WorkshopMainRequiredPropertiesDtoGenerator.cs @@ -0,0 +1,38 @@ +using Bogus; +using System.Collections.Generic; +using OutOfSchool.BusinessLogic.Models.Workshops.Drafts; +using OutOfSchool.Common.Enums; +using System; + +namespace OutOfSchool.Tests.Common.TestDataGenerators; + +public static class WorkshopMainRequiredPropertiesDtoGenerator +{ + public static readonly Faker Faker = new Faker() + .RuleForType(typeof(int), f => f.Random.Int()) + .RuleForType(typeof(Guid), f => f.Random.Guid()) + .RuleForType(typeof(long), f => f.Random.Long(0, long.MaxValue)) + .RuleForType(typeof(string), f => f.Lorem.Word()) + .RuleFor(w => w.Id, f => f.Random.Guid()) + .RuleFor(w => w.Title, f => f.Name.FullName()) + .RuleFor(w => w.ShortTitle, f => f.Name.LastName()) + .RuleFor(w => w.Phone, f => f.Phone.PhoneNumber()) + .RuleFor(w => w.Email, f => f.Internet.Email()) + .RuleFor(w => w.MinAge, f => f.Random.Int(5, 9)) + .RuleFor(w => w.MaxAge, f => f.Random.Int(10, 13)) + .RuleFor(w => w.IsPaid, f => true) + .RuleFor(w => w.Price, f => f.Random.Decimal()) + .RuleFor(w => w.AvailableSeats, f => f.Random.UInt(0, 15)) + .RuleFor(w => w.CompetitiveSelection, f => true) + .RuleFor(w => w.CompetitiveSelectionDescription, f => f.Lorem.Paragraph()) + .RuleFor(w => w.ProviderId, f => f.Random.Guid()) + .RuleFor(w => w.DateTimeRanges, f => DateTimeRangeDtoGenerator.Generate(4)) + .RuleFor(w => w.FormOfLearning, f => f.PickRandom()) + .RuleFor(w => w.PayRate, f => f.PickRandom()); + + public static WorkshopMainRequiredPropertiesDto Generate() => Faker.Generate(); + + public static List Generate(int count) => Faker.Generate(count); + + public static void Populate(WorkshopMainRequiredPropertiesDto dto) => Faker.Populate(dto); +} \ No newline at end of file From 187d64de391236089aef722812848aa1219bc786 Mon Sep 17 00:00:00 2001 From: HlibBondarev Date: Thu, 5 Dec 2024 23:43:30 +0200 Subject: [PATCH 23/28] 1) Created the WorkshopRequiredPropertiesDtoGenerator class. 2) Deleted WorkshopStaffDto class. 3) Changed WorkshopCreateRequestDto class - moved properties from the deleted WorkshopStaffDto. 4) Changed WorkshopMainRequiredPropertiesDto - replaced [JsonDerivedType(typeof(WorkshopStaffDto), typeDiscriminator: 'withStaff')] attribute with [JsonDerivedType(typeof(WorkshopCreateRequestDto), typeDiscriminator: 'withStaff')] attribute. 5) Fixed tests in DraftStorageServiceTests class. 6) Changed WorkshopDraftStorageControllerTests class - added tests to it. --- .../WorkshopMainRequiredPropertiesDto.cs | 2 +- .../Workshops/Drafts/WorkshopStaffDto.cs | 15 -- .../Workshops/WorkshopCreateRequestDto.cs | 13 +- .../WorkshopDraftStorageControllerTests.cs | 149 +++++++++++++++--- .../DraftStorage/DraftStorageServiceTests.cs | 3 +- .../WorkshopRequiredPropertiesDtoGenerator.cs | 34 ++++ 6 files changed, 178 insertions(+), 38 deletions(-) delete mode 100644 OutOfSchool/OutOfSchool.BusinessLogic/Models/Workshops/Drafts/WorkshopStaffDto.cs create mode 100644 OutOfSchool/Tests/OutOfSchool.Tests.Common/TestDataGenerators/WorkshopRequiredPropertiesDtoGenerator.cs diff --git a/OutOfSchool/OutOfSchool.BusinessLogic/Models/Workshops/Drafts/WorkshopMainRequiredPropertiesDto.cs b/OutOfSchool/OutOfSchool.BusinessLogic/Models/Workshops/Drafts/WorkshopMainRequiredPropertiesDto.cs index c18ffb9b5..3f3d14705 100644 --- a/OutOfSchool/OutOfSchool.BusinessLogic/Models/Workshops/Drafts/WorkshopMainRequiredPropertiesDto.cs +++ b/OutOfSchool/OutOfSchool.BusinessLogic/Models/Workshops/Drafts/WorkshopMainRequiredPropertiesDto.cs @@ -14,7 +14,7 @@ namespace OutOfSchool.BusinessLogic.Models.Workshops.Drafts; [JsonDerivedType(typeof(WorkshopRequiredPropertiesDto), typeDiscriminator: "withOtherRequiredProperties")] [JsonDerivedType(typeof(WorkshopDescriptionDto), typeDiscriminator: "withDescription")] [JsonDerivedType(typeof(WorkshopContactsDto), typeDiscriminator: "withContacts")] -[JsonDerivedType(typeof(WorkshopStaffDto), typeDiscriminator: "withStaff")] +[JsonDerivedType(typeof(WorkshopCreateRequestDto), typeDiscriminator: "withStaff")] public class WorkshopMainRequiredPropertiesDto : IValidatableObject { public Guid Id { get; set; } diff --git a/OutOfSchool/OutOfSchool.BusinessLogic/Models/Workshops/Drafts/WorkshopStaffDto.cs b/OutOfSchool/OutOfSchool.BusinessLogic/Models/Workshops/Drafts/WorkshopStaffDto.cs deleted file mode 100644 index 890618b3d..000000000 --- a/OutOfSchool/OutOfSchool.BusinessLogic/Models/Workshops/Drafts/WorkshopStaffDto.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Microsoft.AspNetCore.Mvc; -using OutOfSchool.BusinessLogic.Util.JsonTools; - -namespace OutOfSchool.BusinessLogic.Models.Workshops.Drafts; - -public class WorkshopStaffDto : WorkshopContactsDto -{ - [ModelBinder(BinderType = typeof(JsonModelBinder))] - public TeacherDTO DefaultTeacher { get; set; } - - [ModelBinder(BinderType = typeof(JsonModelBinder))] - public List Teachers { get; set; } - - public Guid? DefaultTeacherId { get; set; } -} \ No newline at end of file diff --git a/OutOfSchool/OutOfSchool.BusinessLogic/Models/Workshops/WorkshopCreateRequestDto.cs b/OutOfSchool/OutOfSchool.BusinessLogic/Models/Workshops/WorkshopCreateRequestDto.cs index 802887b6c..28b1fdeea 100644 --- a/OutOfSchool/OutOfSchool.BusinessLogic/Models/Workshops/WorkshopCreateRequestDto.cs +++ b/OutOfSchool/OutOfSchool.BusinessLogic/Models/Workshops/WorkshopCreateRequestDto.cs @@ -1,7 +1,16 @@ -using OutOfSchool.BusinessLogic.Models.Workshops.Drafts; +using Microsoft.AspNetCore.Mvc; +using OutOfSchool.BusinessLogic.Models.Workshops.Drafts; +using OutOfSchool.BusinessLogic.Util.JsonTools; namespace OutOfSchool.BusinessLogic.Models.Workshops; -public class WorkshopCreateRequestDto : WorkshopStaffDto +public class WorkshopCreateRequestDto : WorkshopContactsDto { + [ModelBinder(BinderType = typeof(JsonModelBinder))] + public TeacherDTO DefaultTeacher { get; set; } + + [ModelBinder(BinderType = typeof(JsonModelBinder))] + public List Teachers { get; set; } + + public Guid? DefaultTeacherId { get; set; } } \ No newline at end of file diff --git a/OutOfSchool/OutOfSchool.WebApi.Tests/Controllers/WorkshopDraftStorageControllerTests.cs b/OutOfSchool/OutOfSchool.WebApi.Tests/Controllers/WorkshopDraftStorageControllerTests.cs index f833c9437..6c1ac0722 100644 --- a/OutOfSchool/OutOfSchool.WebApi.Tests/Controllers/WorkshopDraftStorageControllerTests.cs +++ b/OutOfSchool/OutOfSchool.WebApi.Tests/Controllers/WorkshopDraftStorageControllerTests.cs @@ -1,37 +1,37 @@ -using FluentAssertions; +using System.Security.Claims; +using System.Threading.Tasks; +using FluentAssertions; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Moq; using NUnit.Framework; using OutOfSchool.BusinessLogic.Common; -using OutOfSchool.BusinessLogic.Models.Workshops; using OutOfSchool.BusinessLogic.Models.Workshops.Drafts; using OutOfSchool.BusinessLogic.Services.DraftStorage; using OutOfSchool.Tests.Common.TestDataGenerators; using OutOfSchool.WebApi.Controllers.V1; -using System.Security.Claims; -using System.Threading.Tasks; namespace OutOfSchool.WebApi.Tests.Controllers; [TestFixture] public class WorkshopDraftStorageControllerTests { - private const int RANDOMSTRINGSIZE = 50; - + private readonly string userId = "someUserId"; private string key; private Mock> draftStorageService; private WorkshopDraftStorageController controller; private ClaimsPrincipal user; - private WorkshopMainRequiredPropertiesDto draft; + private WorkshopMainRequiredPropertiesDto baseDtoDraft; + private WorkshopRequiredPropertiesDto derivedDtoDraft; [SetUp] public void Setup() { - draft = GetWorkshopFakeDraft(); + user = new ClaimsPrincipal(new ClaimsIdentity([new Claim("sub", userId)])); + baseDtoDraft = GetBaseWorkshopDtoFakeDraft(); + derivedDtoDraft = GetDerivedWorkshopDtoFakeDraft(); draftStorageService = new Mock>(); controller = new WorkshopDraftStorageController(draftStorageService.Object); - user = new ClaimsPrincipal(new ClaimsIdentity()); key = GettingUserProperties.GetUserId(user); controller.ControllerContext.HttpContext = new DefaultHttpContext { User = user }; } @@ -40,12 +40,35 @@ public void Setup() public async Task StoreDraft_WhenModelIsValid_ReturnsOkObjectResult() { // Arrange - draftStorageService.Setup(ds => ds.CreateAsync(key, draft)) + draftStorageService.Setup(ds => ds.CreateAsync(key, baseDtoDraft)) + .Verifiable(Times.Once); + var resultValue = $"{baseDtoDraft.GetType().Name} is stored"; + + // Act + var result = await controller.StoreDraft(baseDtoDraft).ConfigureAwait(false); + + // Assert + result.Should() + .BeOfType() + .Which.StatusCode + .Should() + .Be(StatusCodes.Status200OK); + result.Should() + .BeOfType() + .Which.Value.Should().Be(resultValue); + draftStorageService.VerifyAll(); + } + + [Test] + public async Task StoreDerivedDraft_WhenModelIsValid_ReturnsOkObjectResult() + { + // Arrange + draftStorageService.Setup(ds => ds.CreateAsync(key, derivedDtoDraft)) .Verifiable(Times.Once); - var resultValue = $"{draft.GetType().Name} is stored"; + var resultValue = $"{baseDtoDraft.GetType().Name} is stored"; // Act - var result = await controller.StoreDraft(draft).ConfigureAwait(false); + var result = await controller.StoreDraft(derivedDtoDraft).ConfigureAwait(false); // Assert result.Should() @@ -66,11 +89,33 @@ public async Task StoreDraft_WhenModelIsInvalid_ReturnsBadRequestObjectResult() var errorKey = "DraftStorage"; var errorMessage = "Invalid model state"; controller.ModelState.AddModelError(errorKey, errorMessage); - draftStorageService.Setup(ds => ds.CreateAsync(key, draft)) + draftStorageService.Setup(ds => ds.CreateAsync(key, baseDtoDraft)) + .Verifiable(Times.Never); + + // Act + var result = await controller.StoreDraft(baseDtoDraft).ConfigureAwait(false); + + // Assert + result.Should() + .BeOfType() + .Which.StatusCode + .Should() + .Be(StatusCodes.Status400BadRequest); + draftStorageService.VerifyAll(); + } + + [Test] + public async Task StoreDerivedDraft_WhenModelIsInvalid_ReturnsBadRequestObjectResult() + { + // Arrange + var errorKey = "DraftStorage"; + var errorMessage = "Invalid model state"; + controller.ModelState.AddModelError(errorKey, errorMessage); + draftStorageService.Setup(ds => ds.CreateAsync(key, derivedDtoDraft)) .Verifiable(Times.Never); // Act - var result = await controller.StoreDraft(draft).ConfigureAwait(false); + var result = await controller.StoreDraft(derivedDtoDraft).ConfigureAwait(false); // Assert result.Should() @@ -86,7 +131,30 @@ public async Task RestoreDraft_WhenDraftExistsInCache_ReturnsDraftAtActionResult { // Arrange draftStorageService.Setup(ds => ds.RestoreAsync(key)) - .ReturnsAsync(draft) + .ReturnsAsync(baseDtoDraft) + .Verifiable(Times.Once); + + // Act + var result = await controller.RestoreDraft().ConfigureAwait(false); + + // Assert + result.Should() + .BeOfType() + .Which.StatusCode + .Should() + .Be(StatusCodes.Status200OK); + result.Should() + .BeOfType() + .Which.Value.Should().Be(baseDtoDraft); + draftStorageService.VerifyAll(); + } + + [Test] + public async Task RestoreDerivedDraft_WhenDraftExistsInCache_ReturnsDerivedDraftAtActionResult() + { + // Arrange + draftStorageService.Setup(ds => ds.RestoreAsync(key)) + .ReturnsAsync(derivedDtoDraft) .Verifiable(Times.Once); // Act @@ -100,7 +168,7 @@ public async Task RestoreDraft_WhenDraftExistsInCache_ReturnsDraftAtActionResult .Be(StatusCodes.Status200OK); result.Should() .BeOfType() - .Which.Value.Should().Be(draft); + .Which.Value.Should().Be(derivedDtoDraft); draftStorageService.VerifyAll(); } @@ -123,10 +191,55 @@ public async Task RestoreDraft_WhenDraftIsAbsentInCache_ReturnsDefaultDraftAtAct .Be(StatusCodes.Status200OK); result.Should() .BeOfType() - .Which.Value.Should().Be(default(WorkshopBaseDto)); + .Which.Value.Should().Be(default(WorkshopMainRequiredPropertiesDto)); + draftStorageService.VerifyAll(); + } + + [Test] + public async Task RestoreDrivedDraft_WhenDraftIsAbsentInCache_ReturnsDefaultDrivedDraftAtActionResult() + { + // Arrange + draftStorageService.Setup(ds => ds.RestoreAsync(key)) + .ReturnsAsync(default(WorkshopRequiredPropertiesDto)) + .Verifiable(Times.Once); + + // Act + var result = await controller.RestoreDraft().ConfigureAwait(false); + + // Assert + result.Should() + .BeOfType() + .Which.StatusCode + .Should() + .Be(StatusCodes.Status200OK); + result.Should() + .BeOfType() + .Which.Value.Should().Be(default(WorkshopRequiredPropertiesDto)); + draftStorageService.VerifyAll(); + } + + [Test] + public async Task RemoveDraft_ReturnsStatus204NoContent() + { + // Arrange + draftStorageService.Setup(ds => ds.RemoveAsync(key)) + .Verifiable(Times.Once); + + // Act + var result = await controller.RemoveDraft().ConfigureAwait(false); + + // Assert + result.Should() + .BeOfType() + .Which.StatusCode + .Should() + .Be(StatusCodes.Status204NoContent); draftStorageService.VerifyAll(); } - private static WorkshopMainRequiredPropertiesDto GetWorkshopFakeDraft() => + private static WorkshopMainRequiredPropertiesDto GetBaseWorkshopDtoFakeDraft() => WorkshopMainRequiredPropertiesDtoGenerator.Generate(); + + private static WorkshopRequiredPropertiesDto GetDerivedWorkshopDtoFakeDraft() => + WorkshopRequiredPropertiesDtoGenerator.Generate(); } \ No newline at end of file diff --git a/OutOfSchool/OutOfSchool.WebApi.Tests/Services/DraftStorage/DraftStorageServiceTests.cs b/OutOfSchool/OutOfSchool.WebApi.Tests/Services/DraftStorage/DraftStorageServiceTests.cs index 1790f8dcb..cffb0416d 100644 --- a/OutOfSchool/OutOfSchool.WebApi.Tests/Services/DraftStorage/DraftStorageServiceTests.cs +++ b/OutOfSchool/OutOfSchool.WebApi.Tests/Services/DraftStorage/DraftStorageServiceTests.cs @@ -98,7 +98,6 @@ public async Task RemoveAsync_WhenDataExistsInCache_ShouldCallRemoveAsyncAndRead .Returns(() => Task.FromResult(workshopJsonString)) .Verifiable(Times.Once); readWriteCacheServiceMock.Setup(c => c.RemoveAsync(cacheKey)) - .Returns(() => Task.FromResult(workshopJsonString)) .Verifiable(Times.Once); // Act @@ -115,7 +114,7 @@ public async Task RemoveAsync_WhenDataIsAbsentInCache_ShouldNeverCallRemoveAsync readWriteCacheServiceMock.Setup(c => c.ReadAsync(cacheKey)) .Returns(() => Task.FromResult(string.Empty)).Verifiable(Times.Once); readWriteCacheServiceMock.Setup(c => c.RemoveAsync(cacheKey)) - .Returns(() => Task.FromResult(string.Empty)).Verifiable(Times.Never); + .Verifiable(Times.Never); // Act await draftStorageService.RemoveAsync(key).ConfigureAwait(false); diff --git a/OutOfSchool/Tests/OutOfSchool.Tests.Common/TestDataGenerators/WorkshopRequiredPropertiesDtoGenerator.cs b/OutOfSchool/Tests/OutOfSchool.Tests.Common/TestDataGenerators/WorkshopRequiredPropertiesDtoGenerator.cs new file mode 100644 index 000000000..7173db65a --- /dev/null +++ b/OutOfSchool/Tests/OutOfSchool.Tests.Common/TestDataGenerators/WorkshopRequiredPropertiesDtoGenerator.cs @@ -0,0 +1,34 @@ +using Bogus; +using OutOfSchool.BusinessLogic.Models.Workshops.Drafts; +using System.Collections.Generic; + +namespace OutOfSchool.Tests.Common.TestDataGenerators; + +public static class WorkshopRequiredPropertiesDtoGenerator +{ + private static readonly Faker Faker = new Faker() + .RuleFor(w => w.ShortStay, f => f.Random.Bool()) + .RuleFor(w => w.IsSelfFinanced, f => f.Random.Bool()) + .RuleFor(w => w.IsSpecial, f => f.Random.Bool()) + .RuleFor(w => w.SpecialNeedsId, f => f.Random.UInt(0, uint.MaxValue)) + .RuleFor(w => w.IsInclusive, f => f.Random.Bool()) + .RuleFor(w => w.EducationalShiftId, f => f.Random.UInt(0, uint.MaxValue)) + .RuleFor(w => w.LanguageOfEducationId, f => f.Random.UInt(0, uint.MaxValue)) + .RuleFor(w => w.TypeOfAgeCompositionId, f => f.Random.UInt(0, uint.MaxValue)) + .RuleFor(w => w.EducationalDisciplines, f => f.Random.Guid()) + .RuleFor(w => w.CategoryId, f => f.Random.UInt(0, uint.MaxValue)) + .RuleFor(w => w.GropeTypeId, f => f.Random.UInt(0, uint.MaxValue)) + .RuleFor(w => w.MemberOfWorkshopId, f => null) + .CustomInstantiator(f => + { + var dto = new WorkshopRequiredPropertiesDto(); + WorkshopMainRequiredPropertiesDtoGenerator.Populate(dto); + return dto; + }); + + public static WorkshopRequiredPropertiesDto Generate() => Faker.Generate(); + + public static List Generate(int count) => Faker.Generate(count); + + public static void Populate(WorkshopRequiredPropertiesDto dto) => Faker.Populate(dto); +} \ No newline at end of file From 718d4132cd5d401d92a58414602b930619b2da4b Mon Sep 17 00:00:00 2001 From: HlibBondarev Date: Fri, 6 Dec 2024 10:35:32 +0200 Subject: [PATCH 24/28] 1) Added [JsonDerivedType(typeof(WorkshopV2CreateRequestDto), typeDiscriminator: 'withImages')] attribute to WorkshopMainRequiredPropertiesDto. 2) Added WorkshopDraftStorageController class for V2. 3) Deleted redundant comment in WorkshopV2CreateRequestDto. --- .../Drafts/WorkshopMainRequiredPropertiesDto.cs | 1 + .../Workshops/WorkshopV2CreateRequestDto.cs | 2 +- .../V2/WorkshopDraftStorageController.cs | 16 ++++++++++++++++ 3 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 OutOfSchool/OutOfSchool.WebApi/Controllers/V2/WorkshopDraftStorageController.cs diff --git a/OutOfSchool/OutOfSchool.BusinessLogic/Models/Workshops/Drafts/WorkshopMainRequiredPropertiesDto.cs b/OutOfSchool/OutOfSchool.BusinessLogic/Models/Workshops/Drafts/WorkshopMainRequiredPropertiesDto.cs index 3f3d14705..48a0005e7 100644 --- a/OutOfSchool/OutOfSchool.BusinessLogic/Models/Workshops/Drafts/WorkshopMainRequiredPropertiesDto.cs +++ b/OutOfSchool/OutOfSchool.BusinessLogic/Models/Workshops/Drafts/WorkshopMainRequiredPropertiesDto.cs @@ -15,6 +15,7 @@ namespace OutOfSchool.BusinessLogic.Models.Workshops.Drafts; [JsonDerivedType(typeof(WorkshopDescriptionDto), typeDiscriminator: "withDescription")] [JsonDerivedType(typeof(WorkshopContactsDto), typeDiscriminator: "withContacts")] [JsonDerivedType(typeof(WorkshopCreateRequestDto), typeDiscriminator: "withStaff")] +[JsonDerivedType(typeof(WorkshopV2CreateRequestDto), typeDiscriminator: "withImages")] public class WorkshopMainRequiredPropertiesDto : IValidatableObject { public Guid Id { get; set; } diff --git a/OutOfSchool/OutOfSchool.BusinessLogic/Models/Workshops/WorkshopV2CreateRequestDto.cs b/OutOfSchool/OutOfSchool.BusinessLogic/Models/Workshops/WorkshopV2CreateRequestDto.cs index a33e8325d..207e3e833 100644 --- a/OutOfSchool/OutOfSchool.BusinessLogic/Models/Workshops/WorkshopV2CreateRequestDto.cs +++ b/OutOfSchool/OutOfSchool.BusinessLogic/Models/Workshops/WorkshopV2CreateRequestDto.cs @@ -5,7 +5,7 @@ namespace OutOfSchool.BusinessLogic.Models.Workshops; -public class WorkshopV2CreateRequestDto : WorkshopCreateRequestDto/*, IHasRating*/ +public class WorkshopV2CreateRequestDto : WorkshopCreateRequestDto { [MaxLength(256)] public string CoverImageId { get; set; } = string.Empty; diff --git a/OutOfSchool/OutOfSchool.WebApi/Controllers/V2/WorkshopDraftStorageController.cs b/OutOfSchool/OutOfSchool.WebApi/Controllers/V2/WorkshopDraftStorageController.cs new file mode 100644 index 000000000..e0673e47b --- /dev/null +++ b/OutOfSchool/OutOfSchool.WebApi/Controllers/V2/WorkshopDraftStorageController.cs @@ -0,0 +1,16 @@ +using Asp.Versioning; +using Microsoft.AspNetCore.Mvc; +using OutOfSchool.BusinessLogic.Models.Workshops.Drafts; +using OutOfSchool.BusinessLogic.Services.DraftStorage; +using OutOfSchool.WebApi.Controllers.V1; + +namespace OutOfSchool.WebApi.Controllers.V2; + +/// Controller with operations for storing the Workshop draft in cache. +[ApiController] +[ApiVersion("2.0")] +[Route("api/v{version:apiVersion}/[controller]/[action]")] +public class WorkshopDraftStorageController(IDraftStorageService draftStorageService) + : DraftStorageController(draftStorageService) +{ +} \ No newline at end of file From 32bb98c74ea926c7ba7548eff934a8461c8e2c37 Mon Sep 17 00:00:00 2001 From: HlibBondarev Date: Fri, 6 Dec 2024 10:46:54 +0200 Subject: [PATCH 25/28] Fixed code alignment in project file - OutOfSchool.BusinessLogic. --- .../OutOfSchool.BusinessLogic.csproj | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/OutOfSchool/OutOfSchool.BusinessLogic/OutOfSchool.BusinessLogic.csproj b/OutOfSchool/OutOfSchool.BusinessLogic/OutOfSchool.BusinessLogic.csproj index 8102e8458..1638fb0e6 100644 --- a/OutOfSchool/OutOfSchool.BusinessLogic/OutOfSchool.BusinessLogic.csproj +++ b/OutOfSchool/OutOfSchool.BusinessLogic/OutOfSchool.BusinessLogic.csproj @@ -1,12 +1,12 @@  - - - net8.0 - direct - - - enable - + + + net8.0 + direct + + + enable + $(NoWarn);1591 From ae3f70af79e17116980676cfdf5a9b1a49baae91 Mon Sep 17 00:00:00 2001 From: HlibBondarev Date: Fri, 6 Dec 2024 11:13:53 +0200 Subject: [PATCH 26/28] Fixed code alignment after merging in project file - OutOfSchool.BusinessLogic. --- .../OutOfSchool.BusinessLogic.csproj | 198 +++++++++--------- 1 file changed, 99 insertions(+), 99 deletions(-) diff --git a/OutOfSchool/OutOfSchool.BusinessLogic/OutOfSchool.BusinessLogic.csproj b/OutOfSchool/OutOfSchool.BusinessLogic/OutOfSchool.BusinessLogic.csproj index 1638fb0e6..1d9b598e4 100644 --- a/OutOfSchool/OutOfSchool.BusinessLogic/OutOfSchool.BusinessLogic.csproj +++ b/OutOfSchool/OutOfSchool.BusinessLogic/OutOfSchool.BusinessLogic.csproj @@ -1,112 +1,112 @@  - - - net8.0 - direct - - - enable - - - $(NoWarn);1591 - + + net8.0 + direct + + + enable + - - true - - - latest - + + $(NoWarn);1591 + + + + true + + + latest + - - - - - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - + + + + + + + + + - - - + + + - - - True - True - ImageResource.resx - - + + + True + True + ImageResource.resx + + - - - ResXFileCodeGenerator - ImageResource.Designer.cs - - + + + ResXFileCodeGenerator + ImageResource.Designer.cs + + \ No newline at end of file From 9ca565f33f3c60fbd4a74ef6f4857a0efd954c32 Mon Sep 17 00:00:00 2001 From: HlibBondarev Date: Fri, 6 Dec 2024 23:06:42 +0200 Subject: [PATCH 27/28] Changed the return value of StoreDraft() method in DraftStorageController class. --- .../Controllers/V1/DraftStorageController.cs | 7 +++---- .../V2/WorkshopDraftStorageController.cs | 16 ---------------- 2 files changed, 3 insertions(+), 20 deletions(-) delete mode 100644 OutOfSchool/OutOfSchool.WebApi/Controllers/V2/WorkshopDraftStorageController.cs diff --git a/OutOfSchool/OutOfSchool.WebApi/Controllers/V1/DraftStorageController.cs b/OutOfSchool/OutOfSchool.WebApi/Controllers/V1/DraftStorageController.cs index 610dc2498..52062fc8d 100644 --- a/OutOfSchool/OutOfSchool.WebApi/Controllers/V1/DraftStorageController.cs +++ b/OutOfSchool/OutOfSchool.WebApi/Controllers/V1/DraftStorageController.cs @@ -20,7 +20,7 @@ protected DraftStorageController(IDraftStorageService draftStorageService) /// Stores the entity draft. /// The entity draft dto for type T. /// - /// Information about the stored entity of type T in the cache. + /// Information about the result of storing an entity of type T in the cache. /// [HttpPost] [Authorize(Roles = "provider, ministryadmin, areaadmin, regionadmin, techadmin")] @@ -33,7 +33,7 @@ public async Task StoreDraft([FromBody] T draftDto) await draftStorageService.CreateAsync(GettingUserProperties.GetUserId(User), draftDto).ConfigureAwait(false); - return Ok($"{typeof(T).Name} is stored"); + return Ok($"{draftDto.GetType().Name} is stored"); } /// Restores the entity draft. @@ -58,5 +58,4 @@ public async Task RemoveDraft() return NoContent(); } -} - +} \ No newline at end of file diff --git a/OutOfSchool/OutOfSchool.WebApi/Controllers/V2/WorkshopDraftStorageController.cs b/OutOfSchool/OutOfSchool.WebApi/Controllers/V2/WorkshopDraftStorageController.cs deleted file mode 100644 index e0673e47b..000000000 --- a/OutOfSchool/OutOfSchool.WebApi/Controllers/V2/WorkshopDraftStorageController.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Asp.Versioning; -using Microsoft.AspNetCore.Mvc; -using OutOfSchool.BusinessLogic.Models.Workshops.Drafts; -using OutOfSchool.BusinessLogic.Services.DraftStorage; -using OutOfSchool.WebApi.Controllers.V1; - -namespace OutOfSchool.WebApi.Controllers.V2; - -/// Controller with operations for storing the Workshop draft in cache. -[ApiController] -[ApiVersion("2.0")] -[Route("api/v{version:apiVersion}/[controller]/[action]")] -public class WorkshopDraftStorageController(IDraftStorageService draftStorageService) - : DraftStorageController(draftStorageService) -{ -} \ No newline at end of file From 731ea6c37f368687277c681e72d7e4bec319bedb Mon Sep 17 00:00:00 2001 From: HlibBondarev Date: Fri, 6 Dec 2024 23:18:08 +0200 Subject: [PATCH 28/28] Fixed StoreDerivedDraft_WhenModelIsValid_ReturnsOkObjectResult() method in WorkshopDraftStorageControllerTests class. --- .../Controllers/WorkshopDraftStorageControllerTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OutOfSchool/OutOfSchool.WebApi.Tests/Controllers/WorkshopDraftStorageControllerTests.cs b/OutOfSchool/OutOfSchool.WebApi.Tests/Controllers/WorkshopDraftStorageControllerTests.cs index 6c1ac0722..668991470 100644 --- a/OutOfSchool/OutOfSchool.WebApi.Tests/Controllers/WorkshopDraftStorageControllerTests.cs +++ b/OutOfSchool/OutOfSchool.WebApi.Tests/Controllers/WorkshopDraftStorageControllerTests.cs @@ -65,7 +65,7 @@ public async Task StoreDerivedDraft_WhenModelIsValid_ReturnsOkObjectResult() // Arrange draftStorageService.Setup(ds => ds.CreateAsync(key, derivedDtoDraft)) .Verifiable(Times.Once); - var resultValue = $"{baseDtoDraft.GetType().Name} is stored"; + var resultValue = $"{derivedDtoDraft.GetType().Name} is stored"; // Act var result = await controller.StoreDraft(derivedDtoDraft).ConfigureAwait(false);