From 3d8026c668cb5b3d8ee95f35fc6b451a999d0900 Mon Sep 17 00:00:00 2001 From: Giovanni Previatti Date: Fri, 3 Apr 2026 10:38:23 -0300 Subject: [PATCH 1/4] feat(66): standardize classification casing in templates --- templates/Bff/.template.config/template.json | 8 +++++--- templates/Full/.template.config/template.json | 7 ++++--- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/templates/Bff/.template.config/template.json b/templates/Bff/.template.config/template.json index 04e6002..daaf475 100644 --- a/templates/Bff/.template.config/template.json +++ b/templates/Bff/.template.config/template.json @@ -7,9 +7,11 @@ "shortName": "hexagonal-solution-bff", "sourceName": "Hexagonal.Solution.Template.Bff", "classifications": [ - "common", - "template", - "hexagonal-architecture" + "Common", + "Template", + "Hexagonal-Architecture", + "Backend-For-Frontend", + "BFF" ], "tags": { "language": "C#", diff --git a/templates/Full/.template.config/template.json b/templates/Full/.template.config/template.json index b9791c5..0ebd8e6 100644 --- a/templates/Full/.template.config/template.json +++ b/templates/Full/.template.config/template.json @@ -7,9 +7,10 @@ "shortName": "hexagonal-solution-full", "sourceName": "Hexagonal.Solution.Template.Full", "classifications": [ - "common", - "template", - "hexagonal-architecture" + "Common", + "Template", + "Hexagonal-Architecture", + "Full" ], "tags": { "language": "C#", From b958b14ed294ce67b646bf1fe37b2cb2b4a6967f Mon Sep 17 00:00:00 2001 From: Giovanni Previatti Date: Fri, 3 Apr 2026 11:10:52 -0300 Subject: [PATCH 2/4] feat(66): add contracts template --- .github/workflows/publish.yml | 14 +- .github/workflows/validate.yml | 12 +- GPreviatti.Template.Hexagonal.Solution.csproj | 13 +- Readme.md | 21 ++ templates/Contracts/.editorconfig | 197 ++++++++++++ .../Contracts/.template.config/template.json | 20 ++ templates/Contracts/Directory.Build.props | 12 + templates/Contracts/Directory.Packages.props | 14 + ...Hexagonal.Solution.Template.Contracts.slnx | 12 + templates/Contracts/Readme.md | 31 ++ .../src/Contracts/Common/BaseRequest.cs | 25 ++ .../src/Contracts/Common/BaseResponse.cs | 99 ++++++ .../Contracts/src/Contracts/Contracts.csproj | 11 + .../Contracts/Orders/CreateOrderRequest.cs | 23 ++ .../src/Contracts/Orders/OrderDto.cs | 55 ++++ .../src/Contracts/Protos/payment.proto | 33 ++ .../src/Contracts/packages.lock.json | 190 ++++++++++++ .../UnitTests/Common/BaseRequestTests.cs | 57 ++++ .../UnitTests/Common/BaseResponseTests.cs | 71 +++++ .../Contracts/tests/UnitTests/GlobalUsings.cs | 1 + .../Orders/CreateOrderRequestTests.cs | 33 ++ .../tests/UnitTests/Orders/OrderDtoTests.cs | 51 +++ .../tests/UnitTests/UnitTests.csproj | 25 ++ .../tests/UnitTests/packages.lock.json | 293 ++++++++++++++++++ .../tests/UnitTests/stryker-config.json | 15 + 25 files changed, 1317 insertions(+), 11 deletions(-) create mode 100644 templates/Contracts/.editorconfig create mode 100644 templates/Contracts/.template.config/template.json create mode 100644 templates/Contracts/Directory.Build.props create mode 100644 templates/Contracts/Directory.Packages.props create mode 100644 templates/Contracts/Hexagonal.Solution.Template.Contracts.slnx create mode 100644 templates/Contracts/Readme.md create mode 100644 templates/Contracts/src/Contracts/Common/BaseRequest.cs create mode 100644 templates/Contracts/src/Contracts/Common/BaseResponse.cs create mode 100644 templates/Contracts/src/Contracts/Contracts.csproj create mode 100644 templates/Contracts/src/Contracts/Orders/CreateOrderRequest.cs create mode 100644 templates/Contracts/src/Contracts/Orders/OrderDto.cs create mode 100644 templates/Contracts/src/Contracts/Protos/payment.proto create mode 100644 templates/Contracts/src/Contracts/packages.lock.json create mode 100644 templates/Contracts/tests/UnitTests/Common/BaseRequestTests.cs create mode 100644 templates/Contracts/tests/UnitTests/Common/BaseResponseTests.cs create mode 100644 templates/Contracts/tests/UnitTests/GlobalUsings.cs create mode 100644 templates/Contracts/tests/UnitTests/Orders/CreateOrderRequestTests.cs create mode 100644 templates/Contracts/tests/UnitTests/Orders/OrderDtoTests.cs create mode 100644 templates/Contracts/tests/UnitTests/UnitTests.csproj create mode 100644 templates/Contracts/tests/UnitTests/packages.lock.json create mode 100644 templates/Contracts/tests/UnitTests/stryker-config.json diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index add242c..758e21b 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -31,12 +31,22 @@ jobs: secrets: sonar_token: ${{ secrets.SONAR_TOKEN }} + validate-contracts-template: + name: Validate Contracts Template + uses: gpreviatti/github-actions-templates/.github/workflows/dotnet-validate.yml@v1 + with: + build_path: "./templates/Contracts" + dotnet_version: "${{ vars.PROJECT_DOTNET_VERSION }}" + unit_test_project_path: "./templates/Contracts/tests/UnitTests/" + default_stryker_config_path: "./templates/Contracts/tests/UnitTests/stryker-config.json" + sonar_validation: false + pack-and-publish: name: Pack and Publish Template - needs: [validate-template-full, validate-template-bff] + needs: [validate-template-full, validate-template-bff, validate-contracts-template] uses: gpreviatti/github-actions-templates/.github/workflows/dotnet-pack.yml@v1 with: dotnet_version: "${{ vars.PROJECT_DOTNET_VERSION }}" - package_version: "10.1.0" + package_version: "10.2.0" secrets: nuget_api_key: ${{ secrets.NUGET_API_KEY }} diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 5b0d789..171f73e 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -28,4 +28,14 @@ jobs: build_path: "./templates/Bff" dotnet_version: "${{ vars.PROJECT_DOTNET_VERSION }}" integration_test_project_path: "./templates/Bff/tests/IntegrationTests" - docker_compose_file_path: "./templates/Bff/docker-compose.yml" \ No newline at end of file + docker_compose_file_path: "./templates/Bff/docker-compose.yml" + + validate-contracts-template: + name: Validate Contracts Template + uses: gpreviatti/github-actions-templates/.github/workflows/dotnet-validate.yml@v1 + with: + build_path: "./templates/Contracts" + dotnet_version: "${{ vars.PROJECT_DOTNET_VERSION }}" + unit_test_project_path: "./templates/Contracts/tests/UnitTests/" + default_stryker_config_path: "./templates/Contracts/tests/UnitTests/stryker-config.json" + sonar_validation: false \ No newline at end of file diff --git a/GPreviatti.Template.Hexagonal.Solution.csproj b/GPreviatti.Template.Hexagonal.Solution.csproj index 844afca..98a407e 100644 --- a/GPreviatti.Template.Hexagonal.Solution.csproj +++ b/GPreviatti.Template.Hexagonal.Solution.csproj @@ -18,14 +18,11 @@ git://github.com/gpreviatti/hexagonal-solution-template true - - Add new new template with BFF architecture - - Remove Repository pattern since it's not needed with modern EF Core and adds unnecessary complexity - - Update dependencies to latest versions - - Update GitHub Actions workflows to test and analyze both templates - - Improve OpenTelemetry configuration and add more metrics and traces - - Remove Aspire Dashboard - - Add Grafana, Prometheus, Tempo and Loki configuration with Docker Compose for easier local development and testing of observability features - - Change DB to PostgreSQL since it's more widely used in production than SQL Server and works better with Docker + - Add new Contracts-only template (`hexagonal-solution-contracts`) with shared request/response contracts, DTOs and gRPC protobuf definitions + - Add unit tests in the Contracts template to validate contract instantiation and default values + - Add dedicated Contracts template solution file and template-level README + - Update root README with usage/help commands and guidance for the new Contracts template + - Optimize Contracts template package management by removing unused package versions diff --git a/Readme.md b/Readme.md index ab25827..f110e26 100644 --- a/Readme.md +++ b/Readme.md @@ -6,6 +6,12 @@ This is a dotnet solution template from projects based on hexagonal architecture and best practices +## Available templates + +- `hexagonal-solution-full`: Full hexagonal architecture template (Domain, Application, Infrastructure, WebApp, tests and load tests). +- `hexagonal-solution-bff`: Backend-for-Frontend focused template with HTTP/gRPC integration patterns and integration/load tests. +- `hexagonal-solution-contracts`: Contracts-only template for shared request/response models, DTOs, gRPC protobuf definitions and unit tests. + ## Used Technologies - FluentValidation @@ -15,6 +21,7 @@ This is a dotnet solution template from projects based on hexagonal architecture - Xunit - Moq - AutoFixture +- gRPC / Protobuf - Docker and Docker Compose - K6 - Stryker @@ -46,6 +53,14 @@ To create only the BFF template you can use the following command dotnet new hexagonal-solution-bff -n HexagonalSolution ``` +To create only the Contracts template you can use the following command + +```bash +dotnet new hexagonal-solution-contracts -n HexagonalSolution +``` + +The Contracts template is recommended when you want a lightweight shared library for API and messaging contracts that can be reused across services. + If you had any doubts about the existing parameters you can also use -h to get more information ```bash @@ -58,6 +73,12 @@ or dotnet new hexagonal-solution-bff -h ``` +or + +```bash +dotnet new hexagonal-solution-contracts -h +``` + If you want to update the template to the latest version just execute the following command ```bash diff --git a/templates/Contracts/.editorconfig b/templates/Contracts/.editorconfig new file mode 100644 index 0000000..c4f02cf --- /dev/null +++ b/templates/Contracts/.editorconfig @@ -0,0 +1,197 @@ +# editorconfig.org + +# top-most EditorConfig file +root = true + +# Default settings: +# A newline ending every file +# Use 4 spaces as indentation +[*] +insert_final_newline = true +indent_style = space +indent_size = 4 +trim_trailing_whitespace = true + +# Generated code +[*{_AssemblyInfo.cs,.notsupported.cs,AsmOffsets.cs}] +generated_code = true + +# C# files +[*.cs] +# New line preferences +csharp_new_line_before_open_brace = all +csharp_new_line_before_else = true +csharp_new_line_before_catch = true +csharp_new_line_before_finally = true +csharp_new_line_before_members_in_object_initializers = true +csharp_new_line_before_members_in_anonymous_types = true +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_switch_labels = true +csharp_indent_labels = one_less_than_current + +# Modifier preferences +csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:suggestion + +# avoid this. unless absolutely necessary +dotnet_style_qualification_for_field = false:suggestion +dotnet_style_qualification_for_property = false:suggestion +dotnet_style_qualification_for_method = false:suggestion +dotnet_style_qualification_for_event = false:suggestion + +# Types: use keywords instead of BCL types, and permit var only when the type is clear +csharp_style_var_for_built_in_types = false:suggestion +csharp_style_var_when_type_is_apparent = false:none +csharp_style_var_elsewhere = true:suggestion +dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion +dotnet_style_predefined_type_for_member_access = true:suggestion + +# name all constant fields using PascalCase +dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields +dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style +dotnet_naming_symbols.constant_fields.applicable_kinds = field +dotnet_naming_symbols.constant_fields.required_modifiers = const +dotnet_naming_style.pascal_case_style.capitalization = pascal_case + +# static fields should have s_ prefix +dotnet_naming_rule.static_fields_should_have_prefix.severity = suggestion +dotnet_naming_rule.static_fields_should_have_prefix.symbols = static_fields +dotnet_naming_rule.static_fields_should_have_prefix.style = static_prefix_style +dotnet_naming_symbols.static_fields.applicable_kinds = field +dotnet_naming_symbols.static_fields.required_modifiers = static +dotnet_naming_symbols.static_fields.applicable_accessibilities = private, internal, private_protected +dotnet_naming_style.static_prefix_style.required_prefix = s_ +dotnet_naming_style.static_prefix_style.capitalization = camel_case + +# internal and private fields should be _camelCase +dotnet_naming_rule.camel_case_for_private_internal_fields.severity = suggestion +dotnet_naming_rule.camel_case_for_private_internal_fields.symbols = private_internal_fields +dotnet_naming_rule.camel_case_for_private_internal_fields.style = camel_case_underscore_style +dotnet_naming_symbols.private_internal_fields.applicable_kinds = field +dotnet_naming_symbols.private_internal_fields.applicable_accessibilities = private, internal +dotnet_naming_style.camel_case_underscore_style.required_prefix = _ +dotnet_naming_style.camel_case_underscore_style.capitalization = camel_case + +# Code style defaults +csharp_using_directive_placement = outside_namespace:suggestion +dotnet_sort_system_directives_first = true +csharp_prefer_braces = true:silent +csharp_preserve_single_line_blocks = true:none +csharp_preserve_single_line_statements = false:none +csharp_prefer_static_local_function = true:suggestion +csharp_prefer_simple_using_statement = false:none +csharp_style_prefer_switch_expression = true:suggestion +csharp_style_namespace_declarations = file_scoped:warning +dotnet_style_readonly_field = true:suggestion + +# Expression-level preferences +dotnet_style_object_initializer = true:suggestion +dotnet_style_collection_initializer = true:suggestion +dotnet_style_explicit_tuple_names = true:suggestion +dotnet_style_coalesce_expression = true:suggestion +dotnet_style_null_propagation = true:suggestion +dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion +dotnet_style_prefer_inferred_tuple_names = true:suggestion +dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion +dotnet_style_prefer_auto_properties = true:suggestion +dotnet_style_prefer_conditional_expression_over_assignment = true:silent +dotnet_style_prefer_conditional_expression_over_return = true:silent +csharp_prefer_simple_default_expression = true:suggestion + +# Expression-bodied members +csharp_style_expression_bodied_methods = true:suggestion +csharp_style_expression_bodied_constructors = true:silent +csharp_style_expression_bodied_operators = true:silent +csharp_style_expression_bodied_properties = true:silent +csharp_style_expression_bodied_indexers = true:silent +csharp_style_expression_bodied_accessors = true:silent +csharp_style_expression_bodied_lambdas = true:silent +csharp_style_expression_bodied_local_functions = true:silent + +# Pattern matching +csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion +csharp_style_pattern_matching_over_as_with_null_check = true:suggestion +csharp_style_inlined_variable_declaration = true:suggestion + +# Null checking preferences +csharp_style_throw_expression = true:suggestion +csharp_style_conditional_delegate_call = true:suggestion + +# Other features +csharp_style_prefer_index_operator = false:none +csharp_style_prefer_range_operator = false:none +csharp_style_pattern_local_over_anonymous_function = false:none + +# Space preferences +csharp_space_after_cast = true +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 = do_not_ignore +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 + +# S3216: "ConfigureAwait(false)" should be used +dotnet_diagnostic.S3216.severity = none + +# S2360: Optional parameters should not be used +dotnet_diagnostic.S2360.severity = none + +# CS8618: Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable. +dotnet_diagnostic.CS8618.severity = none + +# CA1873: Avoid potentially expensive logging +dotnet_diagnostic.CA1873.severity = none + +# Xml project files +[*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,nativeproj,locproj}] +indent_size = 2 + +[*.{csproj,vbproj,proj,nativeproj,locproj}] +charset = utf-8 + +# Xml build files +[*.builds] +indent_size = 2 + +# Xml files +[*.{xml,stylecop,resx,ruleset}] +indent_size = 2 + +# Xml config files +[*.{props,targets,config,nuspec}] +indent_size = 2 + +# YAML config files +[*.{yml,yaml}] +indent_size = 2 + +# Shell scripts +[*.sh] +end_of_line = lf +[*.{cmd,bat}] +end_of_line = crlf + +[*.{sql,json}] +insert_final_newline = false diff --git a/templates/Contracts/.template.config/template.json b/templates/Contracts/.template.config/template.json new file mode 100644 index 0000000..addb30e --- /dev/null +++ b/templates/Contracts/.template.config/template.json @@ -0,0 +1,20 @@ +{ + "$schema": "http://json.schemastore.org/template", + "author": "Giovanni Brunno Previatti", + "identity": "Hexagonal.Solution.Template.Contracts", + "name": "Hexagonal solution template Contracts", + "description": "Solution template focused on shared contracts for hexagonal architecture", + "shortName": "hexagonal-solution-contracts", + "sourceName": "Hexagonal.Solution.Template.Contracts", + "classifications": [ + "Common", + "Template", + "Hexagonal-Architecture", + "Contracts" + ], + "tags": { + "language": "C#", + "type": "project" + }, + "preferNameDirectory": true +} \ No newline at end of file diff --git a/templates/Contracts/Directory.Build.props b/templates/Contracts/Directory.Build.props new file mode 100644 index 0000000..5053742 --- /dev/null +++ b/templates/Contracts/Directory.Build.props @@ -0,0 +1,12 @@ + + + net10.0 + enable + enable + true + latest + Recommended + true + false + + \ No newline at end of file diff --git a/templates/Contracts/Directory.Packages.props b/templates/Contracts/Directory.Packages.props new file mode 100644 index 0000000..ce856b1 --- /dev/null +++ b/templates/Contracts/Directory.Packages.props @@ -0,0 +1,14 @@ + + + true + true + $(NoWarn);NU1507 + + + + + + + + + \ No newline at end of file diff --git a/templates/Contracts/Hexagonal.Solution.Template.Contracts.slnx b/templates/Contracts/Hexagonal.Solution.Template.Contracts.slnx new file mode 100644 index 0000000..0d1901e --- /dev/null +++ b/templates/Contracts/Hexagonal.Solution.Template.Contracts.slnx @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/templates/Contracts/Readme.md b/templates/Contracts/Readme.md new file mode 100644 index 0000000..fb9333f --- /dev/null +++ b/templates/Contracts/Readme.md @@ -0,0 +1,31 @@ +# Hexagonal Architecture Contracts Template + +This repository provides a focused contracts template for applications using Hexagonal Architecture. The template includes shared request/response contracts, DTOs, and gRPC contract definitions with unit tests to validate contract instantiation. + +## Project Structure + +- `src/`: Contains shared contracts. + - `Contracts/`: Common base contracts, order contracts, and proto files. + +- `tests/`: Contains tests for contract correctness. + - `UnitTests/`: Instantiation tests for all contract records and DTOs. + +## Helper Commands + +### Restore dependencies + +```bash +dotnet restore +``` + +### Build contracts project + +```bash +dotnet build src/Contracts +``` + +### Run unit tests + +```bash +dotnet test tests/UnitTests +``` diff --git a/templates/Contracts/src/Contracts/Common/BaseRequest.cs b/templates/Contracts/src/Contracts/Common/BaseRequest.cs new file mode 100644 index 0000000..80440f4 --- /dev/null +++ b/templates/Contracts/src/Contracts/Common/BaseRequest.cs @@ -0,0 +1,25 @@ +namespace Contracts.Common; + +/// +/// Base request structure +/// +/// The unique identifier for correlating requests +public record BaseRequest(Guid CorrelationId); + +/// +/// Base paginated request structure +/// +/// The unique identifier for correlating requests +/// The page number to retrieve +/// The number of items per page +/// The field to sort by +/// Indicates whether the sorting is in descending order +/// A dictionary of search criteria +public record BasePaginatedRequest( + Guid CorrelationId, + int Page = 1, + int PageSize = 10, + string? SortBy = null, + bool SortDescending = false, + Dictionary? SearchByValues = null +) : BaseRequest(CorrelationId); \ No newline at end of file diff --git a/templates/Contracts/src/Contracts/Common/BaseResponse.cs b/templates/Contracts/src/Contracts/Common/BaseResponse.cs new file mode 100644 index 0000000..440f33c --- /dev/null +++ b/templates/Contracts/src/Contracts/Common/BaseResponse.cs @@ -0,0 +1,99 @@ +namespace Contracts.Common; + +/// +/// Base response structure +/// +public record BaseResponse +{ + /// + /// Base response structure + /// + public BaseResponse() { } + + /// + /// Base response structure + /// + /// Indicates whether the response is successful + /// Optional message providing additional information + public BaseResponse(bool success, string? message = null) + { + Success = success; + Message = message; + } + + /// + /// Indicates whether the response is successful + /// + public bool Success { get; set; } + + /// + /// Optional message providing additional information + /// + public string? Message { get; set; } +} + +/// +/// Generic base response structure with data +/// +/// The type of the data included in the response +public record BaseResponse : BaseResponse where TData : class +{ + /// + /// Generic base response structure with data + /// + public BaseResponse() { } + + /// + /// Generic base response structure with data + /// + /// Indicates whether the response is successful + /// The data included in the response + /// Optional message providing additional information + public BaseResponse(bool success, TData? data = null, string? message = null) : base(success, message) => Data = data; + + /// + /// The data included in the response + /// + public TData? Data { get; set; } + +} + +/// +/// Generic base paginated response structure with data +/// +/// +public sealed record BasePaginatedResponse : BaseResponse> +{ + /// + /// Generic base paginated response structure with data + /// + public BasePaginatedResponse() { } + + /// + /// Generic base paginated response structure with data + /// + /// Indicates whether the response is successful + /// The total number of pages available + /// The total number of records available + /// The data included in the response + /// Optional message providing additional information + public BasePaginatedResponse( + bool success, int totalPages, int totalRecords, + IEnumerable? data = null, string? message = null + ) : base(success, data, message) + { + TotalPages = totalPages; + TotalRecords = totalRecords; + } + + /// + /// The total number of pages available + /// + public int TotalPages { get; set; } + + /// + /// The total number of records available + /// + public int TotalRecords { get; set; } + +} diff --git a/templates/Contracts/src/Contracts/Contracts.csproj b/templates/Contracts/src/Contracts/Contracts.csproj new file mode 100644 index 0000000..be735ab --- /dev/null +++ b/templates/Contracts/src/Contracts/Contracts.csproj @@ -0,0 +1,11 @@ + + + true + + + + + + + + \ No newline at end of file diff --git a/templates/Contracts/src/Contracts/Orders/CreateOrderRequest.cs b/templates/Contracts/src/Contracts/Orders/CreateOrderRequest.cs new file mode 100644 index 0000000..3a67a0d --- /dev/null +++ b/templates/Contracts/src/Contracts/Orders/CreateOrderRequest.cs @@ -0,0 +1,23 @@ +using Contracts.Common; + +namespace Contracts.Orders; + +/// +/// Request to create a new order +/// +/// The unique identifier for the request +/// A description of the order +/// The items included in the order +public sealed record CreateOrderRequest( + Guid CorrelationId, + string Description, + CreateOrderItemRequest[] Items +) : BaseRequest(CorrelationId); + +/// +/// An item included in the order +/// +/// The name of the item +/// A description of the item +/// The value of the item +public sealed record CreateOrderItemRequest(string Name, string Description, decimal Value); \ No newline at end of file diff --git a/templates/Contracts/src/Contracts/Orders/OrderDto.cs b/templates/Contracts/src/Contracts/Orders/OrderDto.cs new file mode 100644 index 0000000..591c252 --- /dev/null +++ b/templates/Contracts/src/Contracts/Orders/OrderDto.cs @@ -0,0 +1,55 @@ +namespace Contracts.Orders; + +/// +/// Data transfer object representing an order +/// +public sealed record OrderDto +{ + /// + /// The unique identifier of the order + /// + public int Id { get; set; } + + /// + /// A description of the order + /// + public string Description { get; set; } + + /// + /// The total value of the order + /// + /// 99.99 + public decimal Total { get; set; } + + /// + /// The items included in the order + /// + public IReadOnlyCollection? Items { get; set; } +}; + +/// +/// Data transfer object representing an item in an order +/// +public sealed record ItemDto +{ + /// + /// The unique identifier of the item + /// + public int Id { get; set; } + + /// + /// The name of the item + /// + public string Name { get; set; } + + /// + /// A description of the item + /// + public string Description { get; set; } + + /// + /// The value of the item + /// + /// 99.99 + public decimal Value { get; set; } +}; diff --git a/templates/Contracts/src/Contracts/Protos/payment.proto b/templates/Contracts/src/Contracts/Protos/payment.proto new file mode 100644 index 0000000..17d6454 --- /dev/null +++ b/templates/Contracts/src/Contracts/Protos/payment.proto @@ -0,0 +1,33 @@ +syntax = "proto3"; + +option csharp_namespace = "GrpcPayment"; + +package Protos; + +// Service for managing payments +service PaymentService { + // Creates a payment request for an order + rpc Create (CreatePaymentRequest) returns (PaymentReply); +} + +// Request message for creating a payment +message CreatePaymentRequest { + // Unique identifier for the order + int32 order_id = 1; + // Amount to be paid + double amount = 2; + // Correlation ID for tracking requests + string correlation_id = 3; + // Currency of the payment + string currency = 4; + // Method of payment + string payment_method = 5; +} + +// Reply message for payment processing +message PaymentReply { + // Indicates if the payment was successful + bool success = 1; + // Message providing additional information + string message = 2; +} \ No newline at end of file diff --git a/templates/Contracts/src/Contracts/packages.lock.json b/templates/Contracts/src/Contracts/packages.lock.json new file mode 100644 index 0000000..31f901c --- /dev/null +++ b/templates/Contracts/src/Contracts/packages.lock.json @@ -0,0 +1,190 @@ +{ + "version": 2, + "dependencies": { + "net10.0": { + "Grpc.AspNetCore": { + "type": "Direct", + "requested": "[2.76.0, )", + "resolved": "2.76.0", + "contentHash": "LyXMmpN2Ba0TE35SOLSKbGqIYtJuhc1UgiaGfoW1X8KJERV70QI5KGW+ckEY7MrXoFWN/uWo4B70siVhbDmCgQ==", + "dependencies": { + "Google.Protobuf": "3.31.1", + "Grpc.AspNetCore.Server.ClientFactory": "2.76.0", + "Grpc.Tools": "2.76.0" + } + }, + "Google.Protobuf": { + "type": "Transitive", + "resolved": "3.31.1", + "contentHash": "gSnJbUmGiOTdWddPhqzrEscHq9Ls6sqRDPB9WptckyjTUyx70JOOAaDLkFff8gManZNN3hllQ4aQInnQyq/Z/A==" + }, + "Grpc.AspNetCore.Server": { + "type": "Transitive", + "resolved": "2.76.0", + "contentHash": "diSC/ZeNdSdxHdYSOpYwuSBBDYpuNVtJQFJfiBB0WrYOQ4lVMmdxuUZJcViahQyo8pCvS3Mueo5lqFxwwMF/iw==", + "dependencies": { + "Grpc.Net.Common": "2.76.0" + } + }, + "Grpc.AspNetCore.Server.ClientFactory": { + "type": "Transitive", + "resolved": "2.76.0", + "contentHash": "y5KGO1GO0N2L/hCCMR05mmoK8j+v8rKvZ+9nothAxKx2Tf2CwV8f4TM5K0GkKfDsp4vrc4lm90MU6E+DeN7YIw==", + "dependencies": { + "Grpc.AspNetCore.Server": "2.76.0", + "Grpc.Net.ClientFactory": "2.76.0" + } + }, + "Grpc.Core.Api": { + "type": "Transitive", + "resolved": "2.76.0", + "contentHash": "cSxC2tdnFdXXuBgIn1pjc4YBx7LXTCp4M0qn+SMBS35VWZY+cEQYLWTBDDhdBH1HzU7BV+ncVZlniGQHMpRJKQ==" + }, + "Grpc.Net.Client": { + "type": "Transitive", + "resolved": "2.76.0", + "contentHash": "K1oldmqw2+Gn69nGRzZLhqSiUZwelX1GrBu/cUl9wNf1C0uB61vFS6JcxUUv9P8VoUJhFsmV44JA6lI2EUt4xw==", + "dependencies": { + "Grpc.Net.Common": "2.76.0", + "Microsoft.Extensions.Logging.Abstractions": "8.0.0" + } + }, + "Grpc.Net.ClientFactory": { + "type": "Transitive", + "resolved": "2.76.0", + "contentHash": "XI+kO69L9AV8B9N0UQOmH911r6MOEp9huHiavEsY56DJYuzJ9KAxNGy37dpV6CLbgCaN2uKmpOsZ9Pao6bmpVQ==", + "dependencies": { + "Grpc.Net.Client": "2.76.0", + "Microsoft.Extensions.Http": "8.0.0" + } + }, + "Grpc.Net.Common": { + "type": "Transitive", + "resolved": "2.76.0", + "contentHash": "bZpiMVYgvpB44/wBh1RotrkqC7bg2FOasLri2GhR3hMKyzsiTxCoDE49YjPrJeFc4RW0wS8u+EInI09sjxVFRA==", + "dependencies": { + "Grpc.Core.Api": "2.76.0" + } + }, + "Grpc.Tools": { + "type": "Transitive", + "resolved": "2.76.0", + "contentHash": "goRzYZVMgQtyLvkWdgTnPycg49hlNxnMkqGGgR3l7nCOm0bUh0YeAneiJ9JFk3XLgF4suQUdETYkl2Mg/TBr0w==" + }, + "Microsoft.Extensions.Configuration": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "0J/9YNXTMWSZP2p2+nvl8p71zpSwokZXZuJW+VjdErkegAnFdO1XlqtA62SJtgVYHdKu3uPxJHcMR/r35HwFBA==", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "8.0.0", + "Microsoft.Extensions.Primitives": "8.0.0" + } + }, + "Microsoft.Extensions.Configuration.Abstractions": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "3lE/iLSutpgX1CC0NOW70FJoGARRHbyKmG7dc0klnUZ9Dd9hS6N/POPWhKhMLCEuNN5nXEY5agmlFtH562vqhQ==", + "dependencies": { + "Microsoft.Extensions.Primitives": "8.0.0" + } + }, + "Microsoft.Extensions.Configuration.Binder": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "mBMoXLsr5s1y2zOHWmKsE9veDcx8h1x/c3rz4baEdQKTeDcmQAPNbB54Pi/lhFO3K431eEq6PFbMgLaa6PHFfA==", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "8.0.0" + } + }, + "Microsoft.Extensions.DependencyInjection": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "V8S3bsm50ig6JSyrbcJJ8bW2b9QLGouz+G1miK3UTaOWmMtFwNNNzUf4AleyDWUmTrWMLNnFSLEQtxmxgNQnNQ==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0" + } + }, + "Microsoft.Extensions.DependencyInjection.Abstractions": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "cjWrLkJXK0rs4zofsK4bSdg+jhDLTaxrkXu4gS6Y7MAlCvRyNNgwY/lJi5RDlQOnSZweHqoyvgvbdvQsRIW+hg==" + }, + "Microsoft.Extensions.Diagnostics": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "3PZp/YSkIXrF7QK7PfC1bkyRYwqOHpWFad8Qx+4wkuumAeXo1NHaxpS9LboNA9OvNSAu+QOVlXbMyoY+pHSqcw==", + "dependencies": { + "Microsoft.Extensions.Configuration": "8.0.0", + "Microsoft.Extensions.Diagnostics.Abstractions": "8.0.0", + "Microsoft.Extensions.Options.ConfigurationExtensions": "8.0.0" + } + }, + "Microsoft.Extensions.Diagnostics.Abstractions": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "JHYCQG7HmugNYUhOl368g+NMxYE/N/AiclCYRNlgCY9eVyiBkOHMwK4x60RYMxv9EL3+rmj1mqHvdCiPpC+D4Q==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0", + "Microsoft.Extensions.Options": "8.0.0" + } + }, + "Microsoft.Extensions.Http": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "cWz4caHwvx0emoYe7NkHPxII/KkTI8R/LC9qdqJqnKv2poTJ4e2qqPGQqvRoQ5kaSA4FU5IV3qFAuLuOhoqULQ==", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "8.0.0", + "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0", + "Microsoft.Extensions.Diagnostics": "8.0.0", + "Microsoft.Extensions.Logging": "8.0.0", + "Microsoft.Extensions.Logging.Abstractions": "8.0.0", + "Microsoft.Extensions.Options": "8.0.0" + } + }, + "Microsoft.Extensions.Logging": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "tvRkov9tAJ3xP51LCv3FJ2zINmv1P8Hi8lhhtcKGqM+ImiTCC84uOPEI4z8Cdq2C3o9e+Aa0Gw0rmrsJD77W+w==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection": "8.0.0", + "Microsoft.Extensions.Logging.Abstractions": "8.0.0", + "Microsoft.Extensions.Options": "8.0.0" + } + }, + "Microsoft.Extensions.Logging.Abstractions": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "arDBqTgFCyS0EvRV7O3MZturChstm50OJ0y9bDJvAcmEPJm0FFpFyjU/JLYyStNGGey081DvnQYlncNX5SJJGA==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0" + } + }, + "Microsoft.Extensions.Options": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "JOVOfqpnqlVLUzINQ2fox8evY2SKLYJ3BV8QDe/Jyp21u1T7r45x/R/5QdteURMR5r01GxeJSBBUOCOyaNXA3g==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0", + "Microsoft.Extensions.Primitives": "8.0.0" + } + }, + "Microsoft.Extensions.Options.ConfigurationExtensions": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "0f4DMRqEd50zQh+UyJc+/HiBsZ3vhAQALgdkcQEalSH1L2isdC7Yj54M3cyo5e+BeO5fcBQ7Dxly8XiBBcvRgw==", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "8.0.0", + "Microsoft.Extensions.Configuration.Binder": "8.0.0", + "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0", + "Microsoft.Extensions.Options": "8.0.0", + "Microsoft.Extensions.Primitives": "8.0.0" + } + }, + "Microsoft.Extensions.Primitives": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "bXJEZrW9ny8vjMF1JV253WeLhpEVzFo1lyaZu1vQ4ZxWUlVvknZ/+ftFgVheLubb4eZPSwwxBeqS1JkCOjxd8g==" + } + } + } +} \ No newline at end of file diff --git a/templates/Contracts/tests/UnitTests/Common/BaseRequestTests.cs b/templates/Contracts/tests/UnitTests/Common/BaseRequestTests.cs new file mode 100644 index 0000000..4e722ab --- /dev/null +++ b/templates/Contracts/tests/UnitTests/Common/BaseRequestTests.cs @@ -0,0 +1,57 @@ +using Contracts.Common; + +namespace UnitTests.Common; + +public sealed class BaseRequestTests +{ + [Fact(DisplayName = nameof(GivenABaseRequestWhenInstantiatedThenShouldAssignCorrelationId))] + public void GivenABaseRequestWhenInstantiatedThenShouldAssignCorrelationId() + { + var correlationId = Guid.NewGuid(); + + var request = new BaseRequest(correlationId); + + Assert.Equal(correlationId, request.CorrelationId); + } + + [Fact(DisplayName = nameof(GivenABasePaginatedRequestWhenUsingDefaultValuesThenShouldAssignExpectedDefaults))] + public void GivenABasePaginatedRequestWhenUsingDefaultValuesThenShouldAssignExpectedDefaults() + { + var correlationId = Guid.NewGuid(); + + var request = new BasePaginatedRequest(correlationId); + + Assert.Equal(correlationId, request.CorrelationId); + Assert.Equal(1, request.Page); + Assert.Equal(10, request.PageSize); + Assert.Null(request.SortBy); + Assert.False(request.SortDescending); + Assert.Null(request.SearchByValues); + } + + [Fact(DisplayName = nameof(GivenABasePaginatedRequestWhenInstantiatedThenShouldAssignCustomValues))] + public void GivenABasePaginatedRequestWhenInstantiatedThenShouldAssignCustomValues() + { + var correlationId = Guid.NewGuid(); + var searchByValues = new Dictionary + { + ["status"] = "active" + }; + + var request = new BasePaginatedRequest( + correlationId, + Page: 2, + PageSize: 25, + SortBy: "CreatedAt", + SortDescending: true, + SearchByValues: searchByValues + ); + + Assert.Equal(correlationId, request.CorrelationId); + Assert.Equal(2, request.Page); + Assert.Equal(25, request.PageSize); + Assert.Equal("CreatedAt", request.SortBy); + Assert.True(request.SortDescending); + Assert.Equal(searchByValues, request.SearchByValues); + } +} diff --git a/templates/Contracts/tests/UnitTests/Common/BaseResponseTests.cs b/templates/Contracts/tests/UnitTests/Common/BaseResponseTests.cs new file mode 100644 index 0000000..eb5c738 --- /dev/null +++ b/templates/Contracts/tests/UnitTests/Common/BaseResponseTests.cs @@ -0,0 +1,71 @@ +using Contracts.Common; +using Contracts.Orders; + +namespace UnitTests.Common; + +public sealed class BaseResponseTests +{ + [Fact(DisplayName = nameof(GivenABaseResponseWhenInstantiatedWithDefaultsThenShouldKeepDefaultValues))] + public void GivenABaseResponseWhenInstantiatedWithDefaultsThenShouldKeepDefaultValues() + { + var response = new BaseResponse(); + + Assert.False(response.Success); + Assert.Null(response.Message); + } + + [Fact(DisplayName = nameof(GivenABaseResponseWhenInstantiatedThenShouldAssignSuccessAndMessage))] + public void GivenABaseResponseWhenInstantiatedThenShouldAssignSuccessAndMessage() + { + var response = new BaseResponse(true, "ok"); + + Assert.True(response.Success); + Assert.Equal("ok", response.Message); + } + + [Fact(DisplayName = nameof(GivenABaseResponseOfTDataWhenInstantiatedThenShouldAssignSuccessMessageAndData))] + public void GivenABaseResponseOfTDataWhenInstantiatedThenShouldAssignSuccessMessageAndData() + { + var data = new OrderDto + { + Id = 1, + Description = "Order 1", + Total = 99.9m + }; + + var response = new BaseResponse(true, data, "created"); + + Assert.True(response.Success); + Assert.Equal("created", response.Message); + Assert.Equal(data, response.Data); + } + + [Fact(DisplayName = nameof(GivenABasePaginatedResponseWhenInstantiatedThenShouldAssignPaginationAndData))] + public void GivenABasePaginatedResponseWhenInstantiatedThenShouldAssignPaginationAndData() + { + ItemDto[] data = + [ + new() + { + Id = 1, + Name = "Item 1", + Description = "Desc 1", + Value = 10m + } + ]; + + var response = new BasePaginatedResponse( + success: true, + totalPages: 4, + totalRecords: 30, + data, + message: "ok" + ); + + Assert.True(response.Success); + Assert.Equal("ok", response.Message); + Assert.Equal(4, response.TotalPages); + Assert.Equal(30, response.TotalRecords); + Assert.Equal(data, response.Data); + } +} diff --git a/templates/Contracts/tests/UnitTests/GlobalUsings.cs b/templates/Contracts/tests/UnitTests/GlobalUsings.cs new file mode 100644 index 0000000..c802f44 --- /dev/null +++ b/templates/Contracts/tests/UnitTests/GlobalUsings.cs @@ -0,0 +1 @@ +global using Xunit; diff --git a/templates/Contracts/tests/UnitTests/Orders/CreateOrderRequestTests.cs b/templates/Contracts/tests/UnitTests/Orders/CreateOrderRequestTests.cs new file mode 100644 index 0000000..277dab9 --- /dev/null +++ b/templates/Contracts/tests/UnitTests/Orders/CreateOrderRequestTests.cs @@ -0,0 +1,33 @@ +using Contracts.Orders; + +namespace UnitTests.Orders; + +public sealed class CreateOrderRequestTests +{ + [Fact(DisplayName = nameof(GivenACreateOrderItemRequestWhenInstantiatedThenShouldAssignProperties))] + public void GivenACreateOrderItemRequestWhenInstantiatedThenShouldAssignProperties() + { + var request = new CreateOrderItemRequest("Item 1", "Description 1", 120.5m); + + Assert.Equal("Item 1", request.Name); + Assert.Equal("Description 1", request.Description); + Assert.Equal(120.5m, request.Value); + } + + [Fact(DisplayName = nameof(GivenACreateOrderRequestWhenInstantiatedThenShouldAssignProperties))] + public void GivenACreateOrderRequestWhenInstantiatedThenShouldAssignProperties() + { + var correlationId = Guid.NewGuid(); + CreateOrderItemRequest[] items = + [ + new("Item 1", "Description 1", 100m), + new("Item 2", "Description 2", 200m) + ]; + + var request = new CreateOrderRequest(correlationId, "Order description", items); + + Assert.Equal(correlationId, request.CorrelationId); + Assert.Equal("Order description", request.Description); + Assert.Equal(items, request.Items); + } +} diff --git a/templates/Contracts/tests/UnitTests/Orders/OrderDtoTests.cs b/templates/Contracts/tests/UnitTests/Orders/OrderDtoTests.cs new file mode 100644 index 0000000..6939944 --- /dev/null +++ b/templates/Contracts/tests/UnitTests/Orders/OrderDtoTests.cs @@ -0,0 +1,51 @@ +using Contracts.Orders; + +namespace UnitTests.Orders; + +public sealed class OrderDtoTests +{ + [Fact(DisplayName = nameof(GivenAnOrderDtoWhenPropertiesAreAssignedThenShouldExposeExpectedValues))] + public void GivenAnOrderDtoWhenPropertiesAreAssignedThenShouldExposeExpectedValues() + { + ItemDto[] items = + [ + new() + { + Id = 5, + Name = "Item A", + Description = "Item A description", + Value = 45.7m + } + ]; + + var orderDto = new OrderDto + { + Id = 10, + Description = "Order A", + Total = 45.7m, + Items = items + }; + + Assert.Equal(10, orderDto.Id); + Assert.Equal("Order A", orderDto.Description); + Assert.Equal(45.7m, orderDto.Total); + Assert.Equal(items, orderDto.Items); + } + + [Fact(DisplayName = nameof(GivenAnItemDtoWhenPropertiesAreAssignedThenShouldExposeExpectedValues))] + public void GivenAnItemDtoWhenPropertiesAreAssignedThenShouldExposeExpectedValues() + { + var itemDto = new ItemDto + { + Id = 2, + Name = "Item B", + Description = "Item B description", + Value = 99.99m + }; + + Assert.Equal(2, itemDto.Id); + Assert.Equal("Item B", itemDto.Name); + Assert.Equal("Item B description", itemDto.Description); + Assert.Equal(99.99m, itemDto.Value); + } +} diff --git a/templates/Contracts/tests/UnitTests/UnitTests.csproj b/templates/Contracts/tests/UnitTests/UnitTests.csproj new file mode 100644 index 0000000..cd8a842 --- /dev/null +++ b/templates/Contracts/tests/UnitTests/UnitTests.csproj @@ -0,0 +1,25 @@ + + + false + true + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + \ No newline at end of file diff --git a/templates/Contracts/tests/UnitTests/packages.lock.json b/templates/Contracts/tests/UnitTests/packages.lock.json new file mode 100644 index 0000000..5bede80 --- /dev/null +++ b/templates/Contracts/tests/UnitTests/packages.lock.json @@ -0,0 +1,293 @@ +{ + "version": 2, + "dependencies": { + "net10.0": { + "coverlet.collector": { + "type": "Direct", + "requested": "[8.0.1, )", + "resolved": "8.0.1", + "contentHash": "heVQl5tKYnnIDYlR1QMVGueYH6iriZTcZB6AjDczQNwZzxkjDIt9C84Pt4cCiZYrbo7jkZOYGWbs6Lo9wAtVLg==" + }, + "Microsoft.NET.Test.Sdk": { + "type": "Direct", + "requested": "[18.3.0, )", + "resolved": "18.3.0", + "contentHash": "xW3kXuWRQtgoxJp4J+gdhHSQyK+6Wb/AZDSd7lMvuMRYlZ1tnpkojyfZlWilB5G4dmZ0Y0ZxU/M23TlubndNkw==", + "dependencies": { + "Microsoft.CodeCoverage": "18.3.0", + "Microsoft.TestPlatform.TestHost": "18.3.0" + } + }, + "xunit": { + "type": "Direct", + "requested": "[2.9.3, )", + "resolved": "2.9.3", + "contentHash": "TlXQBinK35LpOPKHAqbLY4xlEen9TBafjs0V5KnA4wZsoQLQJiirCR4CbIXvOH8NzkW4YeJKP5P/Bnrodm0h9Q==", + "dependencies": { + "xunit.analyzers": "1.18.0", + "xunit.assert": "2.9.3", + "xunit.core": "[2.9.3]" + } + }, + "xunit.runner.visualstudio": { + "type": "Direct", + "requested": "[3.1.5, )", + "resolved": "3.1.5", + "contentHash": "tKi7dSTwP4m5m9eXPM2Ime4Kn7xNf4x4zT9sdLO/G4hZVnQCRiMTWoSZqI/pYTVeI27oPPqHBKYI/DjJ9GsYgA==" + }, + "Google.Protobuf": { + "type": "Transitive", + "resolved": "3.31.1", + "contentHash": "gSnJbUmGiOTdWddPhqzrEscHq9Ls6sqRDPB9WptckyjTUyx70JOOAaDLkFff8gManZNN3hllQ4aQInnQyq/Z/A==" + }, + "Grpc.AspNetCore.Server": { + "type": "Transitive", + "resolved": "2.76.0", + "contentHash": "diSC/ZeNdSdxHdYSOpYwuSBBDYpuNVtJQFJfiBB0WrYOQ4lVMmdxuUZJcViahQyo8pCvS3Mueo5lqFxwwMF/iw==", + "dependencies": { + "Grpc.Net.Common": "2.76.0" + } + }, + "Grpc.AspNetCore.Server.ClientFactory": { + "type": "Transitive", + "resolved": "2.76.0", + "contentHash": "y5KGO1GO0N2L/hCCMR05mmoK8j+v8rKvZ+9nothAxKx2Tf2CwV8f4TM5K0GkKfDsp4vrc4lm90MU6E+DeN7YIw==", + "dependencies": { + "Grpc.AspNetCore.Server": "2.76.0", + "Grpc.Net.ClientFactory": "2.76.0" + } + }, + "Grpc.Core.Api": { + "type": "Transitive", + "resolved": "2.76.0", + "contentHash": "cSxC2tdnFdXXuBgIn1pjc4YBx7LXTCp4M0qn+SMBS35VWZY+cEQYLWTBDDhdBH1HzU7BV+ncVZlniGQHMpRJKQ==" + }, + "Grpc.Net.Client": { + "type": "Transitive", + "resolved": "2.76.0", + "contentHash": "K1oldmqw2+Gn69nGRzZLhqSiUZwelX1GrBu/cUl9wNf1C0uB61vFS6JcxUUv9P8VoUJhFsmV44JA6lI2EUt4xw==", + "dependencies": { + "Grpc.Net.Common": "2.76.0", + "Microsoft.Extensions.Logging.Abstractions": "8.0.0" + } + }, + "Grpc.Net.ClientFactory": { + "type": "Transitive", + "resolved": "2.76.0", + "contentHash": "XI+kO69L9AV8B9N0UQOmH911r6MOEp9huHiavEsY56DJYuzJ9KAxNGy37dpV6CLbgCaN2uKmpOsZ9Pao6bmpVQ==", + "dependencies": { + "Grpc.Net.Client": "2.76.0", + "Microsoft.Extensions.Http": "8.0.0" + } + }, + "Grpc.Net.Common": { + "type": "Transitive", + "resolved": "2.76.0", + "contentHash": "bZpiMVYgvpB44/wBh1RotrkqC7bg2FOasLri2GhR3hMKyzsiTxCoDE49YjPrJeFc4RW0wS8u+EInI09sjxVFRA==", + "dependencies": { + "Grpc.Core.Api": "2.76.0" + } + }, + "Grpc.Tools": { + "type": "Transitive", + "resolved": "2.76.0", + "contentHash": "goRzYZVMgQtyLvkWdgTnPycg49hlNxnMkqGGgR3l7nCOm0bUh0YeAneiJ9JFk3XLgF4suQUdETYkl2Mg/TBr0w==" + }, + "Microsoft.CodeCoverage": { + "type": "Transitive", + "resolved": "18.3.0", + "contentHash": "23BNy/vziREC20Wwhb50K7+kZe0m07KlLWDQv4qjJ9tt3QjpDpDIqJFrhYHmMEo9xDkuSp55U/8h4bMF7MiB+g==" + }, + "Microsoft.Extensions.Configuration": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "0J/9YNXTMWSZP2p2+nvl8p71zpSwokZXZuJW+VjdErkegAnFdO1XlqtA62SJtgVYHdKu3uPxJHcMR/r35HwFBA==", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "8.0.0", + "Microsoft.Extensions.Primitives": "8.0.0" + } + }, + "Microsoft.Extensions.Configuration.Abstractions": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "3lE/iLSutpgX1CC0NOW70FJoGARRHbyKmG7dc0klnUZ9Dd9hS6N/POPWhKhMLCEuNN5nXEY5agmlFtH562vqhQ==", + "dependencies": { + "Microsoft.Extensions.Primitives": "8.0.0" + } + }, + "Microsoft.Extensions.Configuration.Binder": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "mBMoXLsr5s1y2zOHWmKsE9veDcx8h1x/c3rz4baEdQKTeDcmQAPNbB54Pi/lhFO3K431eEq6PFbMgLaa6PHFfA==", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "8.0.0" + } + }, + "Microsoft.Extensions.DependencyInjection": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "V8S3bsm50ig6JSyrbcJJ8bW2b9QLGouz+G1miK3UTaOWmMtFwNNNzUf4AleyDWUmTrWMLNnFSLEQtxmxgNQnNQ==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0" + } + }, + "Microsoft.Extensions.DependencyInjection.Abstractions": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "cjWrLkJXK0rs4zofsK4bSdg+jhDLTaxrkXu4gS6Y7MAlCvRyNNgwY/lJi5RDlQOnSZweHqoyvgvbdvQsRIW+hg==" + }, + "Microsoft.Extensions.Diagnostics": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "3PZp/YSkIXrF7QK7PfC1bkyRYwqOHpWFad8Qx+4wkuumAeXo1NHaxpS9LboNA9OvNSAu+QOVlXbMyoY+pHSqcw==", + "dependencies": { + "Microsoft.Extensions.Configuration": "8.0.0", + "Microsoft.Extensions.Diagnostics.Abstractions": "8.0.0", + "Microsoft.Extensions.Options.ConfigurationExtensions": "8.0.0" + } + }, + "Microsoft.Extensions.Diagnostics.Abstractions": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "JHYCQG7HmugNYUhOl368g+NMxYE/N/AiclCYRNlgCY9eVyiBkOHMwK4x60RYMxv9EL3+rmj1mqHvdCiPpC+D4Q==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0", + "Microsoft.Extensions.Options": "8.0.0" + } + }, + "Microsoft.Extensions.Http": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "cWz4caHwvx0emoYe7NkHPxII/KkTI8R/LC9qdqJqnKv2poTJ4e2qqPGQqvRoQ5kaSA4FU5IV3qFAuLuOhoqULQ==", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "8.0.0", + "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0", + "Microsoft.Extensions.Diagnostics": "8.0.0", + "Microsoft.Extensions.Logging": "8.0.0", + "Microsoft.Extensions.Logging.Abstractions": "8.0.0", + "Microsoft.Extensions.Options": "8.0.0" + } + }, + "Microsoft.Extensions.Logging": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "tvRkov9tAJ3xP51LCv3FJ2zINmv1P8Hi8lhhtcKGqM+ImiTCC84uOPEI4z8Cdq2C3o9e+Aa0Gw0rmrsJD77W+w==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection": "8.0.0", + "Microsoft.Extensions.Logging.Abstractions": "8.0.0", + "Microsoft.Extensions.Options": "8.0.0" + } + }, + "Microsoft.Extensions.Logging.Abstractions": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "arDBqTgFCyS0EvRV7O3MZturChstm50OJ0y9bDJvAcmEPJm0FFpFyjU/JLYyStNGGey081DvnQYlncNX5SJJGA==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0" + } + }, + "Microsoft.Extensions.Options": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "JOVOfqpnqlVLUzINQ2fox8evY2SKLYJ3BV8QDe/Jyp21u1T7r45x/R/5QdteURMR5r01GxeJSBBUOCOyaNXA3g==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0", + "Microsoft.Extensions.Primitives": "8.0.0" + } + }, + "Microsoft.Extensions.Options.ConfigurationExtensions": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "0f4DMRqEd50zQh+UyJc+/HiBsZ3vhAQALgdkcQEalSH1L2isdC7Yj54M3cyo5e+BeO5fcBQ7Dxly8XiBBcvRgw==", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "8.0.0", + "Microsoft.Extensions.Configuration.Binder": "8.0.0", + "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0", + "Microsoft.Extensions.Options": "8.0.0", + "Microsoft.Extensions.Primitives": "8.0.0" + } + }, + "Microsoft.Extensions.Primitives": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "bXJEZrW9ny8vjMF1JV253WeLhpEVzFo1lyaZu1vQ4ZxWUlVvknZ/+ftFgVheLubb4eZPSwwxBeqS1JkCOjxd8g==" + }, + "Microsoft.TestPlatform.ObjectModel": { + "type": "Transitive", + "resolved": "18.3.0", + "contentHash": "AEIEX2aWdPO9XbtR96eBaJxmXRD9vaI9uQ1T/JbPEKlTAZwYx0ZrMzKyULMdh/HH9Sg03kXCoN7LszQ90o6nPQ==" + }, + "Microsoft.TestPlatform.TestHost": { + "type": "Transitive", + "resolved": "18.3.0", + "contentHash": "twmsoelXnp1uWMU3VGip9f0Jr1mZ0PZqgJdF35CIrdYgYrkHIJMV1m8uKyhcdjLdsQDESHAgkR7KhS9i1qpJag==", + "dependencies": { + "Microsoft.TestPlatform.ObjectModel": "18.3.0", + "Newtonsoft.Json": "13.0.3" + } + }, + "Newtonsoft.Json": { + "type": "Transitive", + "resolved": "13.0.3", + "contentHash": "HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ==" + }, + "xunit.abstractions": { + "type": "Transitive", + "resolved": "2.0.3", + "contentHash": "pot1I4YOxlWjIb5jmwvvQNbTrZ3lJQ+jUGkGjWE3hEFM0l5gOnBWS+H3qsex68s5cO52g+44vpGzhAt+42vwKg==" + }, + "xunit.analyzers": { + "type": "Transitive", + "resolved": "1.18.0", + "contentHash": "OtFMHN8yqIcYP9wcVIgJrq01AfTxijjAqVDy/WeQVSyrDC1RzBWeQPztL49DN2syXRah8TYnfvk035s7L95EZQ==" + }, + "xunit.assert": { + "type": "Transitive", + "resolved": "2.9.3", + "contentHash": "/Kq28fCE7MjOV42YLVRAJzRF0WmEqsmflm0cfpMjGtzQ2lR5mYVj1/i0Y8uDAOLczkL3/jArrwehfMD0YogMAA==" + }, + "xunit.core": { + "type": "Transitive", + "resolved": "2.9.3", + "contentHash": "BiAEvqGvyme19wE0wTKdADH+NloYqikiU0mcnmiNyXaF9HyHmE6sr/3DC5vnBkgsWaE6yPyWszKSPSApWdRVeQ==", + "dependencies": { + "xunit.extensibility.core": "[2.9.3]", + "xunit.extensibility.execution": "[2.9.3]" + } + }, + "xunit.extensibility.core": { + "type": "Transitive", + "resolved": "2.9.3", + "contentHash": "kf3si0YTn2a8J8eZNb+zFpwfoyvIrQ7ivNk5ZYA5yuYk1bEtMe4DxJ2CF/qsRgmEnDr7MnW1mxylBaHTZ4qErA==", + "dependencies": { + "xunit.abstractions": "2.0.3" + } + }, + "xunit.extensibility.execution": { + "type": "Transitive", + "resolved": "2.9.3", + "contentHash": "yMb6vMESlSrE3Wfj7V6cjQ3S4TXdXpRqYeNEI3zsX31uTsGMJjEw6oD5F5u1cHnMptjhEECnmZSsPxB6ChZHDQ==", + "dependencies": { + "xunit.extensibility.core": "[2.9.3]" + } + }, + "contracts": { + "type": "Project", + "dependencies": { + "Grpc.AspNetCore": "[2.76.0, )" + } + }, + "Grpc.AspNetCore": { + "type": "CentralTransitive", + "requested": "[2.76.0, )", + "resolved": "2.76.0", + "contentHash": "LyXMmpN2Ba0TE35SOLSKbGqIYtJuhc1UgiaGfoW1X8KJERV70QI5KGW+ckEY7MrXoFWN/uWo4B70siVhbDmCgQ==", + "dependencies": { + "Google.Protobuf": "3.31.1", + "Grpc.AspNetCore.Server.ClientFactory": "2.76.0", + "Grpc.Tools": "2.76.0" + } + } + } + } +} \ No newline at end of file diff --git a/templates/Contracts/tests/UnitTests/stryker-config.json b/templates/Contracts/tests/UnitTests/stryker-config.json new file mode 100644 index 0000000..d462dee --- /dev/null +++ b/templates/Contracts/tests/UnitTests/stryker-config.json @@ -0,0 +1,15 @@ +{ + "stryker-config": { + "reporters": [ + "progress", + "html" + ], + "project": "Contracts.csproj", + "report-file-name": "mutation-tests-contracts-result", + "thresholds": { + "high": 90, + "low": 80, + "break": 50 + } + } +} \ No newline at end of file From c9d249b71a18c1b85d3369ce612b798df6813e2d Mon Sep 17 00:00:00 2001 From: Giovanni Previatti Date: Fri, 3 Apr 2026 14:11:49 -0300 Subject: [PATCH 3/4] refactor(66): remove test_no_build parameter from workflows --- .github/workflows/publish.yml | 1 - .github/workflows/validate.yml | 1 - 2 files changed, 2 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 758e21b..be3a67c 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -10,7 +10,6 @@ jobs: with: build_path: "./templates/Full" sonar_project: "hexagonal-solution-template-full" - test_no_build: false unit_test_project_path: "./templates/Full/tests/UnitTests/" dotnet_version: "${{ vars.PROJECT_DOTNET_VERSION }}" domain_stryker_config_path: "./templates/Full/tests/UnitTests/stryker-config-domain.json" diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 171f73e..6c00532 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -11,7 +11,6 @@ jobs: with: build_path: "./templates/Full" sonar_project: "hexagonal-solution-template-full" - test_no_build: false unit_test_project_path: "./templates/Full/tests/UnitTests/" dotnet_version: "${{ vars.PROJECT_DOTNET_VERSION }}" domain_stryker_config_path: "./templates/Full/tests/UnitTests/stryker-config-domain.json" From d9aa8eae52a2133a6f4786c6ad833923c089ea36 Mon Sep 17 00:00:00 2001 From: Giovanni Previatti Date: Fri, 3 Apr 2026 14:39:42 -0300 Subject: [PATCH 4/4] chore(66): remove vscode launch and tasks configuration files --- templates/Full/.vscode/launch.json | 18 ------------------ templates/Full/.vscode/tasks.json | 29 ----------------------------- 2 files changed, 47 deletions(-) delete mode 100644 templates/Full/.vscode/launch.json delete mode 100644 templates/Full/.vscode/tasks.json diff --git a/templates/Full/.vscode/launch.json b/templates/Full/.vscode/launch.json deleted file mode 100644 index 1243368..0000000 --- a/templates/Full/.vscode/launch.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "version": "0.2.0", - "configurations": [ - { - "name": "WebApp [Development]", - "type": "coreclr", - "request": "launch", - "preLaunchTask": "build", - "program": "${workspaceFolder}/src/WebApp/bin/Debug/net10.0/WebApp.dll", - "args": [], - "cwd": "${workspaceFolder}/src/WebApp", - "stopAtEntry": false, - "env": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - } - ] -} \ No newline at end of file diff --git a/templates/Full/.vscode/tasks.json b/templates/Full/.vscode/tasks.json deleted file mode 100644 index 6407faa..0000000 --- a/templates/Full/.vscode/tasks.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "version": "2.0.0", - "tasks": [ - { - "label": "build", - "command": "dotnet", - "type": "process", - "args": [ - "build", - "${workspaceFolder}/src/WebApp/WebApp.csproj", - "/property:GenerateFullPaths=true", - "/consoleloggerparameters:NoSummary;ForceNoAlign" - ], - "problemMatcher": "$msCompile" - }, - { - "label": "watch", - "command": "dotnet", - "type": "process", - "args": [ - "watch", - "run", - "--project", - "${workspaceFolder}/src/WebApp/WebApp.csproj" - ], - "problemMatcher": "$msCompile" - } - ] -} \ No newline at end of file