From f99b956fc9060ef43fa63675ae3b9567ebd67558 Mon Sep 17 00:00:00 2001 From: Danylo-Hrom Date: Mon, 1 Sep 2025 02:45:23 +0300 Subject: [PATCH 1/4] added tests for text and video --- .../DTO/Media/Video/CreateVideoDTO.cs | 3 + .../DTO/Media/Video/VideoDTO.cs | 13 +- .../DTO/TextContent/TermTextCreateDTO.cs | 10 + .../Mapping/TextContent/TermTextProfile.cs | 14 ++ .../Media/Video/Create/CreateVideoCommand.cs | 7 + .../Media/Video/Create/CreateVideoHandler.cs | 80 ++++++ .../Term/Create/CreateTextTermCommand.cs | 7 + .../Term/Create/CreateTextTermHandler.cs | 49 ++++ .../Streetcode/Term/GetById/.editorconfig | 232 ++++++++++++++++++ .../Video/CreateVideoCommandValidator.cs | 13 + .../TextContent/CreateTextTermValidator.cs | 15 ++ .../Streetcode.DAL/Entities/Media/Video.cs | 2 +- .../Entities/Streetcode/TextContent/Term.cs | 3 + .../Controllers/Media/VideoController.cs | 11 +- .../Streetcode.WebApi.csproj | 1 + .../Media/Video/CreateVideoHandlerTests.cs | 178 ++++++++++++++ .../Streetcode.XUnitTest.csproj | 2 + 17 files changed, 630 insertions(+), 10 deletions(-) create mode 100644 Streetcode/Streetcode.BLL/DTO/Media/Video/CreateVideoDTO.cs create mode 100644 Streetcode/Streetcode.BLL/DTO/TextContent/TermTextCreateDTO.cs create mode 100644 Streetcode/Streetcode.BLL/Mapping/TextContent/TermTextProfile.cs create mode 100644 Streetcode/Streetcode.BLL/MediatR/Media/Video/Create/CreateVideoCommand.cs create mode 100644 Streetcode/Streetcode.BLL/MediatR/Media/Video/Create/CreateVideoHandler.cs create mode 100644 Streetcode/Streetcode.BLL/MediatR/Streetcode/Term/Create/CreateTextTermCommand.cs create mode 100644 Streetcode/Streetcode.BLL/MediatR/Streetcode/Term/Create/CreateTextTermHandler.cs create mode 100644 Streetcode/Streetcode.BLL/MediatR/Streetcode/Term/GetById/.editorconfig create mode 100644 Streetcode/Streetcode.BLL/Validators/Media/Video/CreateVideoCommandValidator.cs create mode 100644 Streetcode/Streetcode.BLL/Validators/TextContent/CreateTextTermValidator.cs create mode 100644 Streetcode/Streetcode.XUnitTest/BLL/MediatR/Media/Video/CreateVideoHandlerTests.cs diff --git a/Streetcode/Streetcode.BLL/DTO/Media/Video/CreateVideoDTO.cs b/Streetcode/Streetcode.BLL/DTO/Media/Video/CreateVideoDTO.cs new file mode 100644 index 000000000..29a55e128 --- /dev/null +++ b/Streetcode/Streetcode.BLL/DTO/Media/Video/CreateVideoDTO.cs @@ -0,0 +1,3 @@ +namespace Streetcode.BLL.DTO.Media.Video; + +public class CreateVideoDTO : VideoDTO { } \ No newline at end of file diff --git a/Streetcode/Streetcode.BLL/DTO/Media/Video/VideoDTO.cs b/Streetcode/Streetcode.BLL/DTO/Media/Video/VideoDTO.cs index 28900e1d4..b49d6c42d 100644 --- a/Streetcode/Streetcode.BLL/DTO/Media/Video/VideoDTO.cs +++ b/Streetcode/Streetcode.BLL/DTO/Media/Video/VideoDTO.cs @@ -1,11 +1,10 @@ -using Streetcode.BLL.DTO.AdditionalContent; - namespace Streetcode.BLL.DTO.Media.Video; public class VideoDTO { - public int Id { get; set; } - public string? Description { get; set; } - public string? Url { get; set; } - public int StreetcodeId { get; set; } -} + public int Id { get; set; } + public string? Title { get; set; } + public string? Description { get; set; } + public string? Url { get; set; } + public int StreetcodeId { get; set; } +} \ No newline at end of file diff --git a/Streetcode/Streetcode.BLL/DTO/TextContent/TermTextCreateDTO.cs b/Streetcode/Streetcode.BLL/DTO/TextContent/TermTextCreateDTO.cs new file mode 100644 index 000000000..8b1c35e00 --- /dev/null +++ b/Streetcode/Streetcode.BLL/DTO/TextContent/TermTextCreateDTO.cs @@ -0,0 +1,10 @@ +namespace Streetcode.BLL.DTO.Streetcode.TextContent; + +public class TermCreateDTO +{ + public string Title { get; set; } + + public string Description { get; set; } + + public int StreetcodeId { get; set; } +} \ No newline at end of file diff --git a/Streetcode/Streetcode.BLL/Mapping/TextContent/TermTextProfile.cs b/Streetcode/Streetcode.BLL/Mapping/TextContent/TermTextProfile.cs new file mode 100644 index 000000000..c08280a7d --- /dev/null +++ b/Streetcode/Streetcode.BLL/Mapping/TextContent/TermTextProfile.cs @@ -0,0 +1,14 @@ +using AutoMapper; +using Streetcode.BLL.DTO.Streetcode.TextContent; +using Streetcode.DAL.Entities.Streetcode.TextContent; + +namespace Streetcode.BLL.Mapping.Streetcode.TextContent; + +public class TermProfile : Profile +{ + public TermProfile() + { + CreateMap().ReverseMap(); + CreateMap(); + } +} \ No newline at end of file diff --git a/Streetcode/Streetcode.BLL/MediatR/Media/Video/Create/CreateVideoCommand.cs b/Streetcode/Streetcode.BLL/MediatR/Media/Video/Create/CreateVideoCommand.cs new file mode 100644 index 000000000..eb461b9b5 --- /dev/null +++ b/Streetcode/Streetcode.BLL/MediatR/Media/Video/Create/CreateVideoCommand.cs @@ -0,0 +1,7 @@ +using FluentResults; +using MediatR; +using Streetcode.BLL.DTO.Media.Video; + +namespace Streetcode.BLL.MediatR.Media.Video.Create; + +public record CreateVideoCommand(CreateVideoDTO CreateVideoRequest) : IRequest>; \ No newline at end of file diff --git a/Streetcode/Streetcode.BLL/MediatR/Media/Video/Create/CreateVideoHandler.cs b/Streetcode/Streetcode.BLL/MediatR/Media/Video/Create/CreateVideoHandler.cs new file mode 100644 index 000000000..e538ac0a3 --- /dev/null +++ b/Streetcode/Streetcode.BLL/MediatR/Media/Video/Create/CreateVideoHandler.cs @@ -0,0 +1,80 @@ +using AutoMapper; +using FluentResults; +using MediatR; +using Microsoft.IdentityModel.Tokens; +using Streetcode.BLL.DTO.Media.Video; +using Streetcode.BLL.Interfaces.Logging; +using Streetcode.DAL.Repositories.Interfaces.Base; + +using Entity = Streetcode.DAL.Entities.Media.Video; + +namespace Streetcode.BLL.MediatR.Media.Video.Create; + +public class CreateVideoHandler : IRequestHandler> +{ + private readonly IMapper _mapper; + private readonly IRepositoryWrapper _repositoryWrapper; + private readonly ILoggerService _logger; + + public CreateVideoHandler( + IRepositoryWrapper repositoryWrapper, + IMapper mapper, + ILoggerService logger) + { + _repositoryWrapper = repositoryWrapper; + _mapper = mapper; + _logger = logger; + } + + public async Task> Handle(CreateVideoCommand request, CancellationToken cancellationToken) + { + var newVideo = _mapper.Map(request.CreateVideoRequest); + + var validation = VideoValidation(request, newVideo); + if (validation != null) + { + return validation; + } + + var entity = await _repositoryWrapper.VideoRepository.CreateAsync(newVideo); + var resultIsSuccess = await _repositoryWrapper.SaveChangesAsync() > 0; + if (resultIsSuccess) + { + return Result.Ok(_mapper.Map(entity)); + } + else + { + const string errorMsg = "Failed to create a Video."; + return LogAndFail(request, errorMsg); + } + } + + private Result? VideoValidation(CreateVideoCommand request, Entity? newVideo) + { + if (newVideo == null) + { + const string errorMsg = "Cannot convert null to Video."; + return LogAndFail(request, errorMsg); + } + + if (newVideo.Title.Length > 100) + { + const string errorMsg = "Заголовок відео не може бути більше 100 символів."; + return LogAndFail(request, errorMsg); + } + + if (newVideo.Url.IsNullOrEmpty()) + { + const string errorMsg = "Посилання на відео є обов'язковим."; + return LogAndFail(request, errorMsg); + } + + return null; + } + + private Result LogAndFail(CreateVideoCommand request, string errorMsg) + { + _logger.LogError(request, errorMsg); + return Result.Fail(errorMsg); + } +} \ No newline at end of file diff --git a/Streetcode/Streetcode.BLL/MediatR/Streetcode/Term/Create/CreateTextTermCommand.cs b/Streetcode/Streetcode.BLL/MediatR/Streetcode/Term/Create/CreateTextTermCommand.cs new file mode 100644 index 000000000..82a2c1569 --- /dev/null +++ b/Streetcode/Streetcode.BLL/MediatR/Streetcode/Term/Create/CreateTextTermCommand.cs @@ -0,0 +1,7 @@ +using FluentResults; +using MediatR; +using Streetcode.BLL.DTO.Streetcode.TextContent; + +namespace Streetcode.BLL.MediatR.Streetcode.Term.Create; + +public record CreateTermCommand(TermCreateDTO Term) : IRequest>; \ No newline at end of file diff --git a/Streetcode/Streetcode.BLL/MediatR/Streetcode/Term/Create/CreateTextTermHandler.cs b/Streetcode/Streetcode.BLL/MediatR/Streetcode/Term/Create/CreateTextTermHandler.cs new file mode 100644 index 000000000..d56c2eb10 --- /dev/null +++ b/Streetcode/Streetcode.BLL/MediatR/Streetcode/Term/Create/CreateTextTermHandler.cs @@ -0,0 +1,49 @@ +using AutoMapper; +using FluentResults; +using MediatR; +using Streetcode.BLL.DTO.Streetcode.TextContent; +using Streetcode.BLL.Interfaces.Logging; +using Streetcode.DAL.Repositories.Interfaces.Base; + +using TermEntity = Streetcode.DAL.Entities.Streetcode.TextContent.Term; + +namespace Streetcode.BLL.MediatR.Streetcode.Term.Create; + +public class CreateTermHandler : IRequestHandler> +{ + private readonly IRepositoryWrapper _repository; + private readonly IMapper _mapper; + private readonly ILoggerService _logger; + + public CreateTermHandler(IRepositoryWrapper repository, IMapper mapper, ILoggerService logger) + { + _repository = repository; + _mapper = mapper; + _logger = logger; + } + + public async Task> Handle(CreateTermCommand request, CancellationToken cancellationToken) + { + var entity = _mapper.Map(request.Term); + + if (entity is null) + { + const string errorMsg = "Cannot map CreateTermRequest to entity."; + _logger.LogError(request, errorMsg); + return Result.Fail(errorMsg); + } + + await _repository.TermRepository.CreateAsync(entity); + var result = await _repository.SaveChangesAsync(); + + if (result > 0) + { + var dto = _mapper.Map(entity); + return Result.Ok(dto); + } + + const string failMsg = "Failed to save new Term."; + _logger.LogError(request, failMsg); + return Result.Fail(failMsg); + } +} \ No newline at end of file diff --git a/Streetcode/Streetcode.BLL/MediatR/Streetcode/Term/GetById/.editorconfig b/Streetcode/Streetcode.BLL/MediatR/Streetcode/Term/GetById/.editorconfig new file mode 100644 index 000000000..875356ee6 --- /dev/null +++ b/Streetcode/Streetcode.BLL/MediatR/Streetcode/Term/GetById/.editorconfig @@ -0,0 +1,232 @@ +# Remove the line below if you want to inherit .editorconfig settings from higher directories +root = true + +# C# files +[*.cs] + +#### Core EditorConfig Options #### + +# Indentation and spacing +indent_size = 4 +indent_style = space +tab_width = 4 + +# New line preferences +end_of_line = crlf +insert_final_newline = false + +#### .NET Coding Conventions #### + +# Organize usings +dotnet_separate_import_directive_groups = false +dotnet_sort_system_directives_first = false +file_header_template = unset + +# this. and Me. preferences +dotnet_style_qualification_for_event = false +dotnet_style_qualification_for_field = false +dotnet_style_qualification_for_method = false +dotnet_style_qualification_for_property = false + +# Language keywords vs BCL types preferences +dotnet_style_predefined_type_for_locals_parameters_members = true +dotnet_style_predefined_type_for_member_access = true + +# Parentheses preferences +dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity +dotnet_style_parentheses_in_other_binary_operators = always_for_clarity +dotnet_style_parentheses_in_other_operators = never_if_unnecessary +dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity + +# Modifier preferences +dotnet_style_require_accessibility_modifiers = for_non_interface_members + +# Expression-level preferences +dotnet_style_coalesce_expression = true +dotnet_style_collection_initializer = true +dotnet_style_explicit_tuple_names = true +dotnet_style_namespace_match_folder = true +dotnet_style_null_propagation = true +dotnet_style_object_initializer = true +dotnet_style_operator_placement_when_wrapping = beginning_of_line +dotnet_style_prefer_auto_properties = true +dotnet_style_prefer_collection_expression = when_types_loosely_match +dotnet_style_prefer_compound_assignment = true +dotnet_style_prefer_conditional_expression_over_assignment = true +dotnet_style_prefer_conditional_expression_over_return = true +dotnet_style_prefer_foreach_explicit_cast_in_source = when_strongly_typed +dotnet_style_prefer_inferred_anonymous_type_member_names = true +dotnet_style_prefer_inferred_tuple_names = true +dotnet_style_prefer_is_null_check_over_reference_equality_method = true +dotnet_style_prefer_simplified_boolean_expressions = true +dotnet_style_prefer_simplified_interpolation = true + +# Field preferences +dotnet_style_readonly_field = true + +# Parameter preferences +dotnet_code_quality_unused_parameters = all + +# Suppression preferences +dotnet_remove_unnecessary_suppression_exclusions = none + +# New line preferences +dotnet_style_allow_multiple_blank_lines_experimental = true +dotnet_style_allow_statement_immediately_after_block_experimental = true + +#### C# Coding Conventions #### + +# var preferences +csharp_style_var_elsewhere = false +csharp_style_var_for_built_in_types = false +csharp_style_var_when_type_is_apparent = false + +# Expression-bodied members +csharp_style_expression_bodied_accessors = true +csharp_style_expression_bodied_constructors = false +csharp_style_expression_bodied_indexers = true +csharp_style_expression_bodied_lambdas = true +csharp_style_expression_bodied_local_functions = false +csharp_style_expression_bodied_methods = false +csharp_style_expression_bodied_operators = false +csharp_style_expression_bodied_properties = true + +# Pattern matching preferences +csharp_style_pattern_matching_over_as_with_null_check = true +csharp_style_pattern_matching_over_is_with_cast_check = true +csharp_style_prefer_extended_property_pattern = true +csharp_style_prefer_not_pattern = true +csharp_style_prefer_pattern_matching = true +csharp_style_prefer_switch_expression = true + +# Null-checking preferences +csharp_style_conditional_delegate_call = true + +# Modifier preferences +csharp_prefer_static_anonymous_function = true +csharp_prefer_static_local_function = true +csharp_preferred_modifier_order = public,private,protected,internal,file,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,required,volatile,async +csharp_style_prefer_readonly_struct = true +csharp_style_prefer_readonly_struct_member = true + +# Code-block preferences +csharp_prefer_braces = true +csharp_prefer_simple_using_statement = true +csharp_style_namespace_declarations = block_scoped +csharp_style_prefer_method_group_conversion = true +csharp_style_prefer_primary_constructors = true +csharp_style_prefer_top_level_statements = true + +# Expression-level preferences +csharp_prefer_simple_default_expression = true +csharp_style_deconstructed_variable_declaration = true +csharp_style_implicit_object_creation_when_type_is_apparent = true +csharp_style_inlined_variable_declaration = true +csharp_style_prefer_index_operator = true +csharp_style_prefer_local_over_anonymous_function = true +csharp_style_prefer_null_check_over_type_check = true +csharp_style_prefer_range_operator = true +csharp_style_prefer_tuple_swap = true +csharp_style_prefer_utf8_string_literals = true +csharp_style_throw_expression = true +csharp_style_unused_value_assignment_preference = discard_variable +csharp_style_unused_value_expression_statement_preference = discard_variable + +# 'using' directive preferences +csharp_using_directive_placement = outside_namespace + +# New line preferences +csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true +csharp_style_allow_blank_line_after_token_in_arrow_expression_clause_experimental = true +csharp_style_allow_blank_line_after_token_in_conditional_expression_experimental = true +csharp_style_allow_blank_lines_between_consecutive_braces_experimental = true +csharp_style_allow_embedded_statements_on_same_line_experimental = true + +#### C# Formatting Rules #### + +# New line preferences +csharp_new_line_before_catch = true +csharp_new_line_before_else = true +csharp_new_line_before_finally = true +csharp_new_line_before_members_in_anonymous_types = true +csharp_new_line_before_members_in_object_initializers = true +csharp_new_line_before_open_brace = all +csharp_new_line_between_query_expression_clauses = true + +# Indentation preferences +csharp_indent_block_contents = true +csharp_indent_braces = false +csharp_indent_case_contents = true +csharp_indent_case_contents_when_block = true +csharp_indent_labels = one_less_than_current +csharp_indent_switch_labels = true + +# Space preferences +csharp_space_after_cast = false +csharp_space_after_colon_in_inheritance_clause = true +csharp_space_after_comma = true +csharp_space_after_dot = false +csharp_space_after_keywords_in_control_flow_statements = true +csharp_space_after_semicolon_in_for_statement = true +csharp_space_around_binary_operators = before_and_after +csharp_space_around_declaration_statements = false +csharp_space_before_colon_in_inheritance_clause = true +csharp_space_before_comma = false +csharp_space_before_dot = false +csharp_space_before_open_square_brackets = false +csharp_space_before_semicolon_in_for_statement = false +csharp_space_between_empty_square_brackets = false +csharp_space_between_method_call_empty_parameter_list_parentheses = false +csharp_space_between_method_call_name_and_opening_parenthesis = false +csharp_space_between_method_call_parameter_list_parentheses = false +csharp_space_between_method_declaration_empty_parameter_list_parentheses = false +csharp_space_between_method_declaration_name_and_open_parenthesis = false +csharp_space_between_method_declaration_parameter_list_parentheses = false +csharp_space_between_parentheses = false +csharp_space_between_square_brackets = false + +# Wrapping preferences +csharp_preserve_single_line_blocks = true +csharp_preserve_single_line_statements = true + +#### Naming styles #### + +# Naming rules + +dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion +dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface +dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i + +dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.types_should_be_pascal_case.symbols = types +dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case + +dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members +dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case + +# Symbol specifications + +dotnet_naming_symbols.interface.applicable_kinds = interface +dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.interface.required_modifiers = + +dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum +dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.types.required_modifiers = + +dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method +dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.non_field_members.required_modifiers = + +# Naming styles + +dotnet_naming_style.pascal_case.required_prefix = +dotnet_naming_style.pascal_case.required_suffix = +dotnet_naming_style.pascal_case.word_separator = +dotnet_naming_style.pascal_case.capitalization = pascal_case + +dotnet_naming_style.begins_with_i.required_prefix = I +dotnet_naming_style.begins_with_i.required_suffix = +dotnet_naming_style.begins_with_i.word_separator = +dotnet_naming_style.begins_with_i.capitalization = pascal_case diff --git a/Streetcode/Streetcode.BLL/Validators/Media/Video/CreateVideoCommandValidator.cs b/Streetcode/Streetcode.BLL/Validators/Media/Video/CreateVideoCommandValidator.cs new file mode 100644 index 000000000..899a5f997 --- /dev/null +++ b/Streetcode/Streetcode.BLL/Validators/Media/Video/CreateVideoCommandValidator.cs @@ -0,0 +1,13 @@ +using FluentValidation; +using Streetcode.BLL.MediatR.Media.Video.Create; + +namespace Streetcode.BLL.Validator.Media.Video; + +public sealed class CreateVideoCommandValidator : AbstractValidator +{ + public CreateVideoCommandValidator() + { + RuleFor(c => c.CreateVideoRequest.Title).ValidTitle().MaximumLength(100); + RuleFor(c => c.CreateVideoRequest.Url).ValidUrl(); + } +} \ No newline at end of file diff --git a/Streetcode/Streetcode.BLL/Validators/TextContent/CreateTextTermValidator.cs b/Streetcode/Streetcode.BLL/Validators/TextContent/CreateTextTermValidator.cs new file mode 100644 index 000000000..265e22890 --- /dev/null +++ b/Streetcode/Streetcode.BLL/Validators/TextContent/CreateTextTermValidator.cs @@ -0,0 +1,15 @@ +using FluentValidation; +using Streetcode.BLL.MediatR.Streetcode.Term.Create; + +namespace Streetcode.BLL.Validator.Streetcode.Term.Create; + +public sealed class CreateTermValidator : AbstractValidator + +{ + public CreateTermValidator() + { + RuleFor(cmd => cmd.Term.Title).ValidTitle(); + RuleFor(cmd => cmd.Term.Description).ValidText(); + RuleFor(cmd => cmd.Term.StreetcodeId).ValidId(); + } +} \ No newline at end of file diff --git a/Streetcode/Streetcode.DAL/Entities/Media/Video.cs b/Streetcode/Streetcode.DAL/Entities/Media/Video.cs index 73cb953de..2ab84c509 100644 --- a/Streetcode/Streetcode.DAL/Entities/Media/Video.cs +++ b/Streetcode/Streetcode.DAL/Entities/Media/Video.cs @@ -16,7 +16,7 @@ public class Video public string? Description { get; set; } [Required] - public string? Url { get; set; } + public string Url { get; set; } [Required] public int StreetcodeId { get; set; } diff --git a/Streetcode/Streetcode.DAL/Entities/Streetcode/TextContent/Term.cs b/Streetcode/Streetcode.DAL/Entities/Streetcode/TextContent/Term.cs index cd24b9e2b..5d3447562 100644 --- a/Streetcode/Streetcode.DAL/Entities/Streetcode/TextContent/Term.cs +++ b/Streetcode/Streetcode.DAL/Entities/Streetcode/TextContent/Term.cs @@ -18,5 +18,8 @@ public class Term [MaxLength(500)] public string? Description { get; set; } + [ForeignKey(nameof(Streetcode))] + public int StreetcodeId { get; set; } + public List RelatedTerms { get; set; } = new(); } \ No newline at end of file diff --git a/Streetcode/Streetcode.WebApi/Controllers/Media/VideoController.cs b/Streetcode/Streetcode.WebApi/Controllers/Media/VideoController.cs index 451bc0051..a78dff636 100644 --- a/Streetcode/Streetcode.WebApi/Controllers/Media/VideoController.cs +++ b/Streetcode/Streetcode.WebApi/Controllers/Media/VideoController.cs @@ -1,5 +1,6 @@ п»їusing Microsoft.AspNetCore.Mvc; -using Streetcode.BLL.DTO.Media; +using Streetcode.BLL.DTO.Media.Video; +using Streetcode.BLL.MediatR.Media.Video.Create; using Streetcode.BLL.MediatR.Media.Video.GetAll; using Streetcode.BLL.MediatR.Media.Video.GetById; using Streetcode.BLL.MediatR.Media.Video.GetByStreetcodeId; @@ -25,4 +26,10 @@ public async Task GetById([FromRoute] int id) { return HandleResult(await Mediator.Send(new GetVideoByIdQuery(id))); } -} + + [HttpPost] + public async Task Create([FromBody] CreateVideoDTO video) + { + return HandleResult(await Mediator.Send(new CreateVideoCommand(video))); + } +} \ No newline at end of file diff --git a/Streetcode/Streetcode.WebApi/Streetcode.WebApi.csproj b/Streetcode/Streetcode.WebApi/Streetcode.WebApi.csproj index 02cef2ad6..a995fa379 100644 --- a/Streetcode/Streetcode.WebApi/Streetcode.WebApi.csproj +++ b/Streetcode/Streetcode.WebApi/Streetcode.WebApi.csproj @@ -14,6 +14,7 @@ + diff --git a/Streetcode/Streetcode.XUnitTest/BLL/MediatR/Media/Video/CreateVideoHandlerTests.cs b/Streetcode/Streetcode.XUnitTest/BLL/MediatR/Media/Video/CreateVideoHandlerTests.cs new file mode 100644 index 000000000..0753bf824 --- /dev/null +++ b/Streetcode/Streetcode.XUnitTest/BLL/MediatR/Media/Video/CreateVideoHandlerTests.cs @@ -0,0 +1,178 @@ +using System.Linq.Expressions; +using AutoMapper; +using FluentAssertions; +using Moq; +using Streetcode.BLL.DTO.Media.Video; +using Streetcode.BLL.Interfaces.Logging; +using Streetcode.BLL.MediatR.Media.Video.Create; +using Streetcode.DAL.Repositories.Interfaces.Base; +using Xunit; + +using Entity = Streetcode.DAL.Entities.Media.Video; + +namespace Streetcode.XUnitTest.BLL.MediatRTests.Media.Video.Create; + +public class CreateVideoHandlerTests +{ + private readonly Mock _repositoryWrapperMock; + private readonly Mock _mapperMock; + private readonly Mock _loggerMock; + private readonly CreateVideoHandler _handler; + + public CreateVideoHandlerTests() + { + _repositoryWrapperMock = new Mock(); + _mapperMock = new Mock(); + _loggerMock = new Mock(); + + _handler = new CreateVideoHandler( + _repositoryWrapperMock.Object, + _mapperMock.Object, + _loggerMock.Object + ); + } + + [Fact] + public async Task Handle_OnVideoCreation_ReturnsSuccess() + { + // Arrange + var requestDto = new CreateVideoDTO + { + Title = "Title", + Description = "Description", + Url = "https://google.com", + StreetcodeId = 1, + }; + var mappedEntity = new Entity + { + Title = "Title", + Description = "Description", + Url = "https://google.com", + StreetcodeId = 1, + }; + _mapperMock + .Setup(m => m.Map(requestDto)) + .Returns(mappedEntity); + _repositoryWrapperMock + .Setup(r => r.VideoRepository.GetFirstOrDefaultAsync( + It.IsAny>>(), default)) + .ReturnsAsync((Entity)null); + _repositoryWrapperMock + .Setup(r => r.VideoRepository.CreateAsync(mappedEntity)) + .ReturnsAsync(mappedEntity); + _repositoryWrapperMock + .Setup(r => r.SaveChangesAsync()) + .ReturnsAsync(1); + + // Act + var result = await _handler.Handle(new CreateVideoCommand(requestDto), CancellationToken.None); + + // Assert + _repositoryWrapperMock.Verify(x => x.VideoRepository.CreateAsync(mappedEntity), Times.Once); + _repositoryWrapperMock.Verify(x => x.SaveChangesAsync(), Times.Once); + result.IsSuccess.Should().BeTrue(); + } + + [Fact] + public async Task Handle_EmptyVideo_ReturnsError() + { + CreateVideoDTO requestDto = null; + Entity mappedEntity = null; + _mapperMock + .Setup(m => m.Map(null)) + .Returns(mappedEntity); + _repositoryWrapperMock + .Setup(r => r.VideoRepository.GetFirstOrDefaultAsync( + It.IsAny>>(), default)) + .ReturnsAsync((Entity)null); + _repositoryWrapperMock + .Setup(r => r.VideoRepository.CreateAsync(mappedEntity)) + .ReturnsAsync(mappedEntity); + _repositoryWrapperMock + .Setup(r => r.SaveChangesAsync()) + .ReturnsAsync(1); + + // Act + var result = await _handler.Handle(new CreateVideoCommand(requestDto), CancellationToken.None); + + // Assert + result.IsFailed.Should().BeTrue(); + result.Errors.First().Message.Should().Contain("Cannot convert null to Video."); + } + + [Fact] + public async Task Handle_TitleMaxLength_ReturnsError() + { + var requestDto = new CreateVideoDTO + { + Title = new string('*', 101), + Description = "Description", + Url = "https://google.com", + StreetcodeId = 1, + }; + var mappedEntity = new Entity + { + Title = new string('*', 101), + Description = "Description", + Url = "https://google.com", + StreetcodeId = 1, + }; + _mapperMock + .Setup(m => m.Map(requestDto)) + .Returns(mappedEntity); + _repositoryWrapperMock + .Setup(r => r.VideoRepository.GetFirstOrDefaultAsync( + It.IsAny>>(), default)) + .ReturnsAsync((Entity)null); + _repositoryWrapperMock + .Setup(r => r.VideoRepository.CreateAsync(mappedEntity)) + .ReturnsAsync(mappedEntity); + _repositoryWrapperMock + .Setup(r => r.SaveChangesAsync()) + .ReturnsAsync(1); + + // Act + var result = await _handler.Handle(new CreateVideoCommand(requestDto), CancellationToken.None); + + // Assert + result.IsFailed.Should().BeTrue(); + result.Errors.First().Message.Should().Contain("Заголовок відео не може бути більше 100 символів."); + } + + [Fact] + public async Task Handle_EmptyUrl_ReturnsError() + { + var requestDto = new CreateVideoDTO + { + Title = "Title", + Description = "Description", + StreetcodeId = 1, + }; + var mappedEntity = new Entity + { + Title = "Title", + Description = "Description", + StreetcodeId = 1, + }; + _mapperMock + .Setup(m => m.Map(requestDto)) + .Returns(mappedEntity); + _repositoryWrapperMock + .Setup(r => r.VideoRepository.GetFirstOrDefaultAsync( + It.IsAny>>(), default)) + .ReturnsAsync((Entity)null); + _repositoryWrapperMock + .Setup(r => r.VideoRepository.CreateAsync(mappedEntity)) + .ReturnsAsync(mappedEntity); + _repositoryWrapperMock + .Setup(r => r.SaveChangesAsync()) + .ReturnsAsync(1); + + // Act + var result = await _handler.Handle(new CreateVideoCommand(requestDto), CancellationToken.None); + + // Assert + result.IsFailed.Should().BeTrue(); + result.Errors.First().Message.Should().Contain("Посилання на відео є обов'язковим."); + } +} \ No newline at end of file diff --git a/Streetcode/Streetcode.XUnitTest/Streetcode.XUnitTest.csproj b/Streetcode/Streetcode.XUnitTest/Streetcode.XUnitTest.csproj index d1559e265..135d0f2f6 100644 --- a/Streetcode/Streetcode.XUnitTest/Streetcode.XUnitTest.csproj +++ b/Streetcode/Streetcode.XUnitTest/Streetcode.XUnitTest.csproj @@ -8,6 +8,8 @@ + + From 1c13a93d2bad90e4546b25da5d9895b64bd64faf Mon Sep 17 00:00:00 2001 From: Danylo-Hrom Date: Mon, 1 Sep 2025 03:16:09 +0300 Subject: [PATCH 2/4] Deleted disinterested config --- .../Streetcode/Term/GetById/.editorconfig | 232 ------------------ 1 file changed, 232 deletions(-) delete mode 100644 Streetcode/Streetcode.BLL/MediatR/Streetcode/Term/GetById/.editorconfig diff --git a/Streetcode/Streetcode.BLL/MediatR/Streetcode/Term/GetById/.editorconfig b/Streetcode/Streetcode.BLL/MediatR/Streetcode/Term/GetById/.editorconfig deleted file mode 100644 index 875356ee6..000000000 --- a/Streetcode/Streetcode.BLL/MediatR/Streetcode/Term/GetById/.editorconfig +++ /dev/null @@ -1,232 +0,0 @@ -# Remove the line below if you want to inherit .editorconfig settings from higher directories -root = true - -# C# files -[*.cs] - -#### Core EditorConfig Options #### - -# Indentation and spacing -indent_size = 4 -indent_style = space -tab_width = 4 - -# New line preferences -end_of_line = crlf -insert_final_newline = false - -#### .NET Coding Conventions #### - -# Organize usings -dotnet_separate_import_directive_groups = false -dotnet_sort_system_directives_first = false -file_header_template = unset - -# this. and Me. preferences -dotnet_style_qualification_for_event = false -dotnet_style_qualification_for_field = false -dotnet_style_qualification_for_method = false -dotnet_style_qualification_for_property = false - -# Language keywords vs BCL types preferences -dotnet_style_predefined_type_for_locals_parameters_members = true -dotnet_style_predefined_type_for_member_access = true - -# Parentheses preferences -dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity -dotnet_style_parentheses_in_other_binary_operators = always_for_clarity -dotnet_style_parentheses_in_other_operators = never_if_unnecessary -dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity - -# Modifier preferences -dotnet_style_require_accessibility_modifiers = for_non_interface_members - -# Expression-level preferences -dotnet_style_coalesce_expression = true -dotnet_style_collection_initializer = true -dotnet_style_explicit_tuple_names = true -dotnet_style_namespace_match_folder = true -dotnet_style_null_propagation = true -dotnet_style_object_initializer = true -dotnet_style_operator_placement_when_wrapping = beginning_of_line -dotnet_style_prefer_auto_properties = true -dotnet_style_prefer_collection_expression = when_types_loosely_match -dotnet_style_prefer_compound_assignment = true -dotnet_style_prefer_conditional_expression_over_assignment = true -dotnet_style_prefer_conditional_expression_over_return = true -dotnet_style_prefer_foreach_explicit_cast_in_source = when_strongly_typed -dotnet_style_prefer_inferred_anonymous_type_member_names = true -dotnet_style_prefer_inferred_tuple_names = true -dotnet_style_prefer_is_null_check_over_reference_equality_method = true -dotnet_style_prefer_simplified_boolean_expressions = true -dotnet_style_prefer_simplified_interpolation = true - -# Field preferences -dotnet_style_readonly_field = true - -# Parameter preferences -dotnet_code_quality_unused_parameters = all - -# Suppression preferences -dotnet_remove_unnecessary_suppression_exclusions = none - -# New line preferences -dotnet_style_allow_multiple_blank_lines_experimental = true -dotnet_style_allow_statement_immediately_after_block_experimental = true - -#### C# Coding Conventions #### - -# var preferences -csharp_style_var_elsewhere = false -csharp_style_var_for_built_in_types = false -csharp_style_var_when_type_is_apparent = false - -# Expression-bodied members -csharp_style_expression_bodied_accessors = true -csharp_style_expression_bodied_constructors = false -csharp_style_expression_bodied_indexers = true -csharp_style_expression_bodied_lambdas = true -csharp_style_expression_bodied_local_functions = false -csharp_style_expression_bodied_methods = false -csharp_style_expression_bodied_operators = false -csharp_style_expression_bodied_properties = true - -# Pattern matching preferences -csharp_style_pattern_matching_over_as_with_null_check = true -csharp_style_pattern_matching_over_is_with_cast_check = true -csharp_style_prefer_extended_property_pattern = true -csharp_style_prefer_not_pattern = true -csharp_style_prefer_pattern_matching = true -csharp_style_prefer_switch_expression = true - -# Null-checking preferences -csharp_style_conditional_delegate_call = true - -# Modifier preferences -csharp_prefer_static_anonymous_function = true -csharp_prefer_static_local_function = true -csharp_preferred_modifier_order = public,private,protected,internal,file,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,required,volatile,async -csharp_style_prefer_readonly_struct = true -csharp_style_prefer_readonly_struct_member = true - -# Code-block preferences -csharp_prefer_braces = true -csharp_prefer_simple_using_statement = true -csharp_style_namespace_declarations = block_scoped -csharp_style_prefer_method_group_conversion = true -csharp_style_prefer_primary_constructors = true -csharp_style_prefer_top_level_statements = true - -# Expression-level preferences -csharp_prefer_simple_default_expression = true -csharp_style_deconstructed_variable_declaration = true -csharp_style_implicit_object_creation_when_type_is_apparent = true -csharp_style_inlined_variable_declaration = true -csharp_style_prefer_index_operator = true -csharp_style_prefer_local_over_anonymous_function = true -csharp_style_prefer_null_check_over_type_check = true -csharp_style_prefer_range_operator = true -csharp_style_prefer_tuple_swap = true -csharp_style_prefer_utf8_string_literals = true -csharp_style_throw_expression = true -csharp_style_unused_value_assignment_preference = discard_variable -csharp_style_unused_value_expression_statement_preference = discard_variable - -# 'using' directive preferences -csharp_using_directive_placement = outside_namespace - -# New line preferences -csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true -csharp_style_allow_blank_line_after_token_in_arrow_expression_clause_experimental = true -csharp_style_allow_blank_line_after_token_in_conditional_expression_experimental = true -csharp_style_allow_blank_lines_between_consecutive_braces_experimental = true -csharp_style_allow_embedded_statements_on_same_line_experimental = true - -#### C# Formatting Rules #### - -# New line preferences -csharp_new_line_before_catch = true -csharp_new_line_before_else = true -csharp_new_line_before_finally = true -csharp_new_line_before_members_in_anonymous_types = true -csharp_new_line_before_members_in_object_initializers = true -csharp_new_line_before_open_brace = all -csharp_new_line_between_query_expression_clauses = true - -# Indentation preferences -csharp_indent_block_contents = true -csharp_indent_braces = false -csharp_indent_case_contents = true -csharp_indent_case_contents_when_block = true -csharp_indent_labels = one_less_than_current -csharp_indent_switch_labels = true - -# Space preferences -csharp_space_after_cast = false -csharp_space_after_colon_in_inheritance_clause = true -csharp_space_after_comma = true -csharp_space_after_dot = false -csharp_space_after_keywords_in_control_flow_statements = true -csharp_space_after_semicolon_in_for_statement = true -csharp_space_around_binary_operators = before_and_after -csharp_space_around_declaration_statements = false -csharp_space_before_colon_in_inheritance_clause = true -csharp_space_before_comma = false -csharp_space_before_dot = false -csharp_space_before_open_square_brackets = false -csharp_space_before_semicolon_in_for_statement = false -csharp_space_between_empty_square_brackets = false -csharp_space_between_method_call_empty_parameter_list_parentheses = false -csharp_space_between_method_call_name_and_opening_parenthesis = false -csharp_space_between_method_call_parameter_list_parentheses = false -csharp_space_between_method_declaration_empty_parameter_list_parentheses = false -csharp_space_between_method_declaration_name_and_open_parenthesis = false -csharp_space_between_method_declaration_parameter_list_parentheses = false -csharp_space_between_parentheses = false -csharp_space_between_square_brackets = false - -# Wrapping preferences -csharp_preserve_single_line_blocks = true -csharp_preserve_single_line_statements = true - -#### Naming styles #### - -# Naming rules - -dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion -dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface -dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i - -dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion -dotnet_naming_rule.types_should_be_pascal_case.symbols = types -dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case - -dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion -dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members -dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case - -# Symbol specifications - -dotnet_naming_symbols.interface.applicable_kinds = interface -dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected -dotnet_naming_symbols.interface.required_modifiers = - -dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum -dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected -dotnet_naming_symbols.types.required_modifiers = - -dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method -dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected -dotnet_naming_symbols.non_field_members.required_modifiers = - -# Naming styles - -dotnet_naming_style.pascal_case.required_prefix = -dotnet_naming_style.pascal_case.required_suffix = -dotnet_naming_style.pascal_case.word_separator = -dotnet_naming_style.pascal_case.capitalization = pascal_case - -dotnet_naming_style.begins_with_i.required_prefix = I -dotnet_naming_style.begins_with_i.required_suffix = -dotnet_naming_style.begins_with_i.word_separator = -dotnet_naming_style.begins_with_i.capitalization = pascal_case From b8d4fbaf4436667db5bab4b73869167b4eb455f8 Mon Sep 17 00:00:00 2001 From: Danylo-Hrom Date: Tue, 9 Sep 2025 00:50:47 +0300 Subject: [PATCH 3/4] fixed some build errors --- .../{TextCreateDTO.cs => CreateTextDTO.cs} | 0 ...{TermTextCreateDTO.cs => TermCreateDTO.cs} | 0 .../Streetcode/TextContent/TermProfile.cs | 13 ++++++- .../Mapping/TextContent/TermTextProfile.cs | 14 ------- .../Create/CreateTermCommand.cs} | 0 .../Create/CreateTermHandler.cs} | 0 ...llTextsHandler.cs => GetAllTextHandler.cs} | 0 ...GetAllTextsQuery.cs => GetAllTextQuery.cs} | 0 .../Video/CreateVideoCommandValidator.cs | 11 ++++-- ...ermValidator.cs => CreateTermValidator.cs} | 15 ++++--- .../Streetcode/TextContent/RelatedTerm.cs | 4 +- .../Entities/Streetcode/TextContent/Term.cs | 39 ++++++++++--------- 12 files changed, 53 insertions(+), 43 deletions(-) rename Streetcode/Streetcode.BLL/DTO/Streetcode/TextContent/Text/{TextCreateDTO.cs => CreateTextDTO.cs} (100%) rename Streetcode/Streetcode.BLL/DTO/TextContent/{TermTextCreateDTO.cs => TermCreateDTO.cs} (100%) delete mode 100644 Streetcode/Streetcode.BLL/Mapping/TextContent/TermTextProfile.cs rename Streetcode/Streetcode.BLL/MediatR/Streetcode/{Term/Create/CreateTextTermCommand.cs => Text/Create/CreateTermCommand.cs} (100%) rename Streetcode/Streetcode.BLL/MediatR/Streetcode/{Term/Create/CreateTextTermHandler.cs => Text/Create/CreateTermHandler.cs} (100%) rename Streetcode/Streetcode.BLL/MediatR/Streetcode/Text/GetAll/{GetAllTextsHandler.cs => GetAllTextHandler.cs} (100%) rename Streetcode/Streetcode.BLL/MediatR/Streetcode/Text/GetAll/{GetAllTextsQuery.cs => GetAllTextQuery.cs} (100%) rename Streetcode/Streetcode.BLL/Validators/TextContent/{CreateTextTermValidator.cs => CreateTermValidator.cs} (52%) diff --git a/Streetcode/Streetcode.BLL/DTO/Streetcode/TextContent/Text/TextCreateDTO.cs b/Streetcode/Streetcode.BLL/DTO/Streetcode/TextContent/Text/CreateTextDTO.cs similarity index 100% rename from Streetcode/Streetcode.BLL/DTO/Streetcode/TextContent/Text/TextCreateDTO.cs rename to Streetcode/Streetcode.BLL/DTO/Streetcode/TextContent/Text/CreateTextDTO.cs diff --git a/Streetcode/Streetcode.BLL/DTO/TextContent/TermTextCreateDTO.cs b/Streetcode/Streetcode.BLL/DTO/TextContent/TermCreateDTO.cs similarity index 100% rename from Streetcode/Streetcode.BLL/DTO/TextContent/TermTextCreateDTO.cs rename to Streetcode/Streetcode.BLL/DTO/TextContent/TermCreateDTO.cs diff --git a/Streetcode/Streetcode.BLL/Mapping/Streetcode/TextContent/TermProfile.cs b/Streetcode/Streetcode.BLL/Mapping/Streetcode/TextContent/TermProfile.cs index f3707b07a..d4f83f1a5 100644 --- a/Streetcode/Streetcode.BLL/Mapping/Streetcode/TextContent/TermProfile.cs +++ b/Streetcode/Streetcode.BLL/Mapping/Streetcode/TextContent/TermProfile.cs @@ -8,6 +8,17 @@ public class TermProfile : Profile { public TermProfile() { - CreateMap().ReverseMap(); + CreateMap() + .ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.Id)) + .ForMember(dest => dest.Title, opt => opt.MapFrom(src => src.Title)) + .ForMember(dest => dest.Description, opt => opt.MapFrom(src => src.Description)) + .ForAllOtherMembers(opt => opt.Ignore()); + + CreateMap() + .ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.Id)) + .ForMember(dest => dest.Title, opt => opt.MapFrom(src => src.Title)) + .ForMember(dest => dest.Description, opt => opt.MapFrom(src => src.Description)) + .ForMember(dest => dest.StreetcodeId, opt => opt.Ignore()) + .ForMember(dest => dest.RelatedTerms, opt => opt.Ignore()); } } \ No newline at end of file diff --git a/Streetcode/Streetcode.BLL/Mapping/TextContent/TermTextProfile.cs b/Streetcode/Streetcode.BLL/Mapping/TextContent/TermTextProfile.cs deleted file mode 100644 index c08280a7d..000000000 --- a/Streetcode/Streetcode.BLL/Mapping/TextContent/TermTextProfile.cs +++ /dev/null @@ -1,14 +0,0 @@ -using AutoMapper; -using Streetcode.BLL.DTO.Streetcode.TextContent; -using Streetcode.DAL.Entities.Streetcode.TextContent; - -namespace Streetcode.BLL.Mapping.Streetcode.TextContent; - -public class TermProfile : Profile -{ - public TermProfile() - { - CreateMap().ReverseMap(); - CreateMap(); - } -} \ No newline at end of file diff --git a/Streetcode/Streetcode.BLL/MediatR/Streetcode/Term/Create/CreateTextTermCommand.cs b/Streetcode/Streetcode.BLL/MediatR/Streetcode/Text/Create/CreateTermCommand.cs similarity index 100% rename from Streetcode/Streetcode.BLL/MediatR/Streetcode/Term/Create/CreateTextTermCommand.cs rename to Streetcode/Streetcode.BLL/MediatR/Streetcode/Text/Create/CreateTermCommand.cs diff --git a/Streetcode/Streetcode.BLL/MediatR/Streetcode/Term/Create/CreateTextTermHandler.cs b/Streetcode/Streetcode.BLL/MediatR/Streetcode/Text/Create/CreateTermHandler.cs similarity index 100% rename from Streetcode/Streetcode.BLL/MediatR/Streetcode/Term/Create/CreateTextTermHandler.cs rename to Streetcode/Streetcode.BLL/MediatR/Streetcode/Text/Create/CreateTermHandler.cs diff --git a/Streetcode/Streetcode.BLL/MediatR/Streetcode/Text/GetAll/GetAllTextsHandler.cs b/Streetcode/Streetcode.BLL/MediatR/Streetcode/Text/GetAll/GetAllTextHandler.cs similarity index 100% rename from Streetcode/Streetcode.BLL/MediatR/Streetcode/Text/GetAll/GetAllTextsHandler.cs rename to Streetcode/Streetcode.BLL/MediatR/Streetcode/Text/GetAll/GetAllTextHandler.cs diff --git a/Streetcode/Streetcode.BLL/MediatR/Streetcode/Text/GetAll/GetAllTextsQuery.cs b/Streetcode/Streetcode.BLL/MediatR/Streetcode/Text/GetAll/GetAllTextQuery.cs similarity index 100% rename from Streetcode/Streetcode.BLL/MediatR/Streetcode/Text/GetAll/GetAllTextsQuery.cs rename to Streetcode/Streetcode.BLL/MediatR/Streetcode/Text/GetAll/GetAllTextQuery.cs diff --git a/Streetcode/Streetcode.BLL/Validators/Media/Video/CreateVideoCommandValidator.cs b/Streetcode/Streetcode.BLL/Validators/Media/Video/CreateVideoCommandValidator.cs index 899a5f997..11ef079e5 100644 --- a/Streetcode/Streetcode.BLL/Validators/Media/Video/CreateVideoCommandValidator.cs +++ b/Streetcode/Streetcode.BLL/Validators/Media/Video/CreateVideoCommandValidator.cs @@ -7,7 +7,12 @@ public sealed class CreateVideoCommandValidator : AbstractValidator c.CreateVideoRequest.Title).ValidTitle().MaximumLength(100); - RuleFor(c => c.CreateVideoRequest.Url).ValidUrl(); + RuleFor(c => c.CreateVideoRequest.Title) + .NotEmpty() + .MaximumLength(100); + + RuleFor(c => c.CreateVideoRequest.Url) + .NotEmpty() + .Must(url => Uri.IsWellFormedUriString(url, UriKind.Absolute)); } -} \ No newline at end of file +} diff --git a/Streetcode/Streetcode.BLL/Validators/TextContent/CreateTextTermValidator.cs b/Streetcode/Streetcode.BLL/Validators/TextContent/CreateTermValidator.cs similarity index 52% rename from Streetcode/Streetcode.BLL/Validators/TextContent/CreateTextTermValidator.cs rename to Streetcode/Streetcode.BLL/Validators/TextContent/CreateTermValidator.cs index 265e22890..cbb2af623 100644 --- a/Streetcode/Streetcode.BLL/Validators/TextContent/CreateTextTermValidator.cs +++ b/Streetcode/Streetcode.BLL/Validators/TextContent/CreateTermValidator.cs @@ -4,12 +4,17 @@ namespace Streetcode.BLL.Validator.Streetcode.Term.Create; public sealed class CreateTermValidator : AbstractValidator - { public CreateTermValidator() { - RuleFor(cmd => cmd.Term.Title).ValidTitle(); - RuleFor(cmd => cmd.Term.Description).ValidText(); - RuleFor(cmd => cmd.Term.StreetcodeId).ValidId(); + RuleFor(cmd => cmd.Term.Title) + .NotEmpty() + .MaximumLength(200); + + RuleFor(cmd => cmd.Term.Description) + .NotEmpty(); + + RuleFor(cmd => cmd.Term.StreetcodeId) + .GreaterThan(0); } -} \ No newline at end of file +} diff --git a/Streetcode/Streetcode.DAL/Entities/Streetcode/TextContent/RelatedTerm.cs b/Streetcode/Streetcode.DAL/Entities/Streetcode/TextContent/RelatedTerm.cs index ce1cf42d3..b9b7fe716 100644 --- a/Streetcode/Streetcode.DAL/Entities/Streetcode/TextContent/RelatedTerm.cs +++ b/Streetcode/Streetcode.DAL/Entities/Streetcode/TextContent/RelatedTerm.cs @@ -1,4 +1,4 @@ -п»їusing System.ComponentModel.DataAnnotations.Schema; +using System.ComponentModel.DataAnnotations.Schema; using System.ComponentModel.DataAnnotations; namespace Streetcode.DAL.Entities.Streetcode.TextContent @@ -17,4 +17,4 @@ public class RelatedTerm public int TermId { get; set; } public Term? Term { get; set; } } -} +} \ No newline at end of file diff --git a/Streetcode/Streetcode.DAL/Entities/Streetcode/TextContent/Term.cs b/Streetcode/Streetcode.DAL/Entities/Streetcode/TextContent/Term.cs index 5d3447562..3276bce6f 100644 --- a/Streetcode/Streetcode.DAL/Entities/Streetcode/TextContent/Term.cs +++ b/Streetcode/Streetcode.DAL/Entities/Streetcode/TextContent/Term.cs @@ -1,25 +1,28 @@ -п»їusing System.ComponentModel.DataAnnotations; +п»їusing System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; -namespace Streetcode.DAL.Entities.Streetcode.TextContent; - -[Table("terms", Schema = "streetcode")] -public class Term +namespace Streetcode.DAL.Entities.Streetcode.TextContent { - [Key] - [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - public int Id { get; set; } + [Table("terms", Schema = "streetcode")] + public class Term + { + [Key] + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public int Id { get; set; } - [Required] - [MaxLength(50)] - public string? Title { get; set; } + [Required] + [MaxLength(50)] + public string Title { get; set; } = string.Empty; - [Required] - [MaxLength(500)] - public string? Description { get; set; } + [Required] + [MaxLength(500)] + public string Description { get; set; } = string.Empty; - [ForeignKey(nameof(Streetcode))] - public int StreetcodeId { get; set; } + [ForeignKey(nameof(Streetcode))] + public int StreetcodeId { get; set; } - public List RelatedTerms { get; set; } = new(); -} \ No newline at end of file + public ICollection RelatedTerms { get; set; } = new List(); + } +} From c99304de6662bcaefa64c8ab0823a1d109266d32 Mon Sep 17 00:00:00 2001 From: Danylo-Hrom Date: Tue, 9 Sep 2025 01:26:33 +0300 Subject: [PATCH 4/4] fixed all build errors --- .../DTO/Media/Video/CreateVideoDTO.cs | 9 +- .../Streetcode.BLL/DTO/Media/VideoDTO.cs | 11 --- .../{CreateTextDTO.cs => TextCreateDTO.cs} | 0 .../Streetcode/TextContent/TermProfile.cs | 14 +-- .../Media/Video/CreateVideoHandlerTests.cs | 98 +++++++------------ 5 files changed, 46 insertions(+), 86 deletions(-) delete mode 100644 Streetcode/Streetcode.BLL/DTO/Media/VideoDTO.cs rename Streetcode/Streetcode.BLL/DTO/Streetcode/TextContent/Text/{CreateTextDTO.cs => TextCreateDTO.cs} (100%) diff --git a/Streetcode/Streetcode.BLL/DTO/Media/Video/CreateVideoDTO.cs b/Streetcode/Streetcode.BLL/DTO/Media/Video/CreateVideoDTO.cs index 29a55e128..d4d903d37 100644 --- a/Streetcode/Streetcode.BLL/DTO/Media/Video/CreateVideoDTO.cs +++ b/Streetcode/Streetcode.BLL/DTO/Media/Video/CreateVideoDTO.cs @@ -1,3 +1,6 @@ -namespace Streetcode.BLL.DTO.Media.Video; - -public class CreateVideoDTO : VideoDTO { } \ No newline at end of file +namespace Streetcode.BLL.DTO.Media.Video +{ + public class CreateVideoDTO : VideoDTO + { + } +} diff --git a/Streetcode/Streetcode.BLL/DTO/Media/VideoDTO.cs b/Streetcode/Streetcode.BLL/DTO/Media/VideoDTO.cs deleted file mode 100644 index 7d53055a4..000000000 --- a/Streetcode/Streetcode.BLL/DTO/Media/VideoDTO.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Streetcode.BLL.DTO.AdditionalContent; - -namespace Streetcode.BLL.DTO.Media; - -public class VideoDTO -{ - public int Id { get; set; } - public string? Description { get; set; } - public string? Url { get; set; } - public int StreetcodeId { get; set; } -} \ No newline at end of file diff --git a/Streetcode/Streetcode.BLL/DTO/Streetcode/TextContent/Text/CreateTextDTO.cs b/Streetcode/Streetcode.BLL/DTO/Streetcode/TextContent/Text/TextCreateDTO.cs similarity index 100% rename from Streetcode/Streetcode.BLL/DTO/Streetcode/TextContent/Text/CreateTextDTO.cs rename to Streetcode/Streetcode.BLL/DTO/Streetcode/TextContent/Text/TextCreateDTO.cs diff --git a/Streetcode/Streetcode.BLL/Mapping/Streetcode/TextContent/TermProfile.cs b/Streetcode/Streetcode.BLL/Mapping/Streetcode/TextContent/TermProfile.cs index d4f83f1a5..50902d385 100644 --- a/Streetcode/Streetcode.BLL/Mapping/Streetcode/TextContent/TermProfile.cs +++ b/Streetcode/Streetcode.BLL/Mapping/Streetcode/TextContent/TermProfile.cs @@ -8,17 +8,7 @@ public class TermProfile : Profile { public TermProfile() { - CreateMap() - .ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.Id)) - .ForMember(dest => dest.Title, opt => opt.MapFrom(src => src.Title)) - .ForMember(dest => dest.Description, opt => opt.MapFrom(src => src.Description)) - .ForAllOtherMembers(opt => opt.Ignore()); - - CreateMap() - .ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.Id)) - .ForMember(dest => dest.Title, opt => opt.MapFrom(src => src.Title)) - .ForMember(dest => dest.Description, opt => opt.MapFrom(src => src.Description)) - .ForMember(dest => dest.StreetcodeId, opt => opt.Ignore()) - .ForMember(dest => dest.RelatedTerms, opt => opt.Ignore()); + CreateMap().ReverseMap(); + CreateMap(); } } \ No newline at end of file diff --git a/Streetcode/Streetcode.XUnitTest/BLL/MediatR/Media/Video/CreateVideoHandlerTests.cs b/Streetcode/Streetcode.XUnitTest/BLL/MediatR/Media/Video/CreateVideoHandlerTests.cs index 0753bf824..ddff736aa 100644 --- a/Streetcode/Streetcode.XUnitTest/BLL/MediatR/Media/Video/CreateVideoHandlerTests.cs +++ b/Streetcode/Streetcode.XUnitTest/BLL/MediatR/Media/Video/CreateVideoHandlerTests.cs @@ -28,46 +28,40 @@ public CreateVideoHandlerTests() _handler = new CreateVideoHandler( _repositoryWrapperMock.Object, _mapperMock.Object, - _loggerMock.Object - ); + _loggerMock.Object); } [Fact] public async Task Handle_OnVideoCreation_ReturnsSuccess() { - // Arrange var requestDto = new CreateVideoDTO { Title = "Title", Description = "Description", Url = "https://google.com", - StreetcodeId = 1, + StreetcodeId = 1 }; + var mappedEntity = new Entity { Title = "Title", Description = "Description", Url = "https://google.com", - StreetcodeId = 1, + StreetcodeId = 1 }; - _mapperMock - .Setup(m => m.Map(requestDto)) - .Returns(mappedEntity); + + _mapperMock.Setup(m => m.Map(requestDto)).Returns(mappedEntity); + _repositoryWrapperMock .Setup(r => r.VideoRepository.GetFirstOrDefaultAsync( It.IsAny>>(), default)) .ReturnsAsync((Entity)null); - _repositoryWrapperMock - .Setup(r => r.VideoRepository.CreateAsync(mappedEntity)) - .ReturnsAsync(mappedEntity); - _repositoryWrapperMock - .Setup(r => r.SaveChangesAsync()) - .ReturnsAsync(1); - // Act + _repositoryWrapperMock.Setup(r => r.VideoRepository.CreateAsync(mappedEntity)).ReturnsAsync(mappedEntity); + _repositoryWrapperMock.Setup(r => r.SaveChangesAsync()).ReturnsAsync(1); + var result = await _handler.Handle(new CreateVideoCommand(requestDto), CancellationToken.None); - // Assert _repositoryWrapperMock.Verify(x => x.VideoRepository.CreateAsync(mappedEntity), Times.Once); _repositoryWrapperMock.Verify(x => x.SaveChangesAsync(), Times.Once); result.IsSuccess.Should().BeTrue(); @@ -78,24 +72,18 @@ public async Task Handle_EmptyVideo_ReturnsError() { CreateVideoDTO requestDto = null; Entity mappedEntity = null; - _mapperMock - .Setup(m => m.Map(null)) - .Returns(mappedEntity); + + _mapperMock.Setup(m => m.Map(null)).Returns(mappedEntity); + _repositoryWrapperMock - .Setup(r => r.VideoRepository.GetFirstOrDefaultAsync( - It.IsAny>>(), default)) + .Setup(r => r.VideoRepository.GetFirstOrDefaultAsync(It.IsAny>>(), default)) .ReturnsAsync((Entity)null); - _repositoryWrapperMock - .Setup(r => r.VideoRepository.CreateAsync(mappedEntity)) - .ReturnsAsync(mappedEntity); - _repositoryWrapperMock - .Setup(r => r.SaveChangesAsync()) - .ReturnsAsync(1); - // Act + _repositoryWrapperMock.Setup(r => r.VideoRepository.CreateAsync(mappedEntity)).ReturnsAsync(mappedEntity); + _repositoryWrapperMock.Setup(r => r.SaveChangesAsync()).ReturnsAsync(1); + var result = await _handler.Handle(new CreateVideoCommand(requestDto), CancellationToken.None); - // Assert result.IsFailed.Should().BeTrue(); result.Errors.First().Message.Should().Contain("Cannot convert null to Video."); } @@ -108,33 +96,28 @@ public async Task Handle_TitleMaxLength_ReturnsError() Title = new string('*', 101), Description = "Description", Url = "https://google.com", - StreetcodeId = 1, + StreetcodeId = 1 }; + var mappedEntity = new Entity { Title = new string('*', 101), Description = "Description", Url = "https://google.com", - StreetcodeId = 1, + StreetcodeId = 1 }; - _mapperMock - .Setup(m => m.Map(requestDto)) - .Returns(mappedEntity); + + _mapperMock.Setup(m => m.Map(requestDto)).Returns(mappedEntity); + _repositoryWrapperMock - .Setup(r => r.VideoRepository.GetFirstOrDefaultAsync( - It.IsAny>>(), default)) + .Setup(r => r.VideoRepository.GetFirstOrDefaultAsync(It.IsAny>>(), default)) .ReturnsAsync((Entity)null); - _repositoryWrapperMock - .Setup(r => r.VideoRepository.CreateAsync(mappedEntity)) - .ReturnsAsync(mappedEntity); - _repositoryWrapperMock - .Setup(r => r.SaveChangesAsync()) - .ReturnsAsync(1); - // Act + _repositoryWrapperMock.Setup(r => r.VideoRepository.CreateAsync(mappedEntity)).ReturnsAsync(mappedEntity); + _repositoryWrapperMock.Setup(r => r.SaveChangesAsync()).ReturnsAsync(1); + var result = await _handler.Handle(new CreateVideoCommand(requestDto), CancellationToken.None); - // Assert result.IsFailed.Should().BeTrue(); result.Errors.First().Message.Should().Contain("Заголовок відео не може бути більше 100 символів."); } @@ -146,33 +129,28 @@ public async Task Handle_EmptyUrl_ReturnsError() { Title = "Title", Description = "Description", - StreetcodeId = 1, + StreetcodeId = 1 }; + var mappedEntity = new Entity { Title = "Title", Description = "Description", - StreetcodeId = 1, + StreetcodeId = 1 }; - _mapperMock - .Setup(m => m.Map(requestDto)) - .Returns(mappedEntity); + + _mapperMock.Setup(m => m.Map(requestDto)).Returns(mappedEntity); + _repositoryWrapperMock - .Setup(r => r.VideoRepository.GetFirstOrDefaultAsync( - It.IsAny>>(), default)) + .Setup(r => r.VideoRepository.GetFirstOrDefaultAsync(It.IsAny>>(), default)) .ReturnsAsync((Entity)null); - _repositoryWrapperMock - .Setup(r => r.VideoRepository.CreateAsync(mappedEntity)) - .ReturnsAsync(mappedEntity); - _repositoryWrapperMock - .Setup(r => r.SaveChangesAsync()) - .ReturnsAsync(1); - // Act + _repositoryWrapperMock.Setup(r => r.VideoRepository.CreateAsync(mappedEntity)).ReturnsAsync(mappedEntity); + _repositoryWrapperMock.Setup(r => r.SaveChangesAsync()).ReturnsAsync(1); + var result = await _handler.Handle(new CreateVideoCommand(requestDto), CancellationToken.None); - // Assert result.IsFailed.Should().BeTrue(); result.Errors.First().Message.Should().Contain("Посилання на відео є обов'язковим."); } -} \ No newline at end of file +}