diff --git a/.github/workflows/cd-pipeline.yml b/.github/workflows/cd-pipeline.yml index a95c728..f4ead48 100644 --- a/.github/workflows/cd-pipeline.yml +++ b/.github/workflows/cd-pipeline.yml @@ -3,54 +3,48 @@ on: push: tags: - 'v*.*.*' + jobs: + tests: + runs-on: ubuntu-latest + steps: + - name: Checkout code - Tests + uses: actions/checkout@v5 + + - name: Build and run tests + run: docker build --target tests --progress=plain -t riber-tests . + deploy: runs-on: ubuntu-latest + needs: tests steps: - - name: Checkout code - uses: actions/checkout@v4 + - name: Checkout code - Deploy + uses: actions/checkout@v5 - name: Get version id: image run: | VERSION=$(grep '' Directory.Build.props | sed 's/.*\(.*\)<\/Version>.*/\1/') echo "version=$VERSION" >> $GITHUB_OUTPUT - + + - name: Lowercase repository owner + id: repo + run: echo "owner=$(echo ${{ github.repository_owner }} | tr '[:upper:]' '[:lower:]')" >> $GITHUB_OUTPUT + - name: Login to GHCR - uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef + uses: docker/login-action@v3 with: registry: ghcr.io - username: ${{ github.repository_owner }} + username: ${{ github.actor }} password: ${{ secrets.GHCR }} - name: Build and push to GHCR - uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 + uses: docker/build-push-action@v6 with: context: . push: true platforms: linux/amd64 + target: runtime tags: | - ghcr.io/${{ github.repository_owner }}/riber:latest - ghcr.io/${{ github.repository_owner }}/riber:${{ steps.image.outputs.version }} -# -# - name: Deploy to AWS EC2 -# uses: appleboy/ssh-action@v1.0.3 -# with: -# host: ${{ secrets.EC2_HOST }} -# username: ${{ secrets.EC2_USERNAME }} -# key: ${{ secrets.EC2_KEY }} -# script: | -# echo "${{ secrets.GHCR }}" | docker login ghcr.io -u ${{ github.repository_owner }} --password-stdin -# docker pull ghcr.io/${{ github.repository_owner }}/riber:${{ steps.image.outputs.version }} -# -# docker stop riber-api || true -# docker rm riber-api || true -# -# docker run -d --name riber-api -p 80:8080 --restart unless-stopped \ -# -e ConnectionStrings__DefaultConnection="${{ secrets.CONNECTION_STRING }}" \ -# -e AccessTokenSettings__SecretKey="${{ secrets.ACCESS_TOKEN_SECRET_KEY }}" \ -# -e RefreshTokenSettings__SecretKey="${{ secrets.REFRESH_TOKEN_SECRET_KEY }}" \ -# -e AWS__S3__BucketImagesName="${{ secrets.S3_BUCKET_IMAGES_NAME }}" \ -# ghcr.io/${{ github.repository_owner }}/riber:${{ steps.image.outputs.version }} -# -# docker images | grep riber | tail -n +4 | awk '{print $3}' | xargs -r docker rmi || true \ No newline at end of file + ghcr.io/${{ steps.repo.outputs.owner }}/riber:latest + ghcr.io/${{ steps.repo.outputs.owner }}/riber:${{ steps.image.outputs.version }} \ No newline at end of file diff --git a/.github/workflows/ci-pipeline.yml b/.github/workflows/ci-pipeline.yml index 325a52a..e61a0f5 100644 --- a/.github/workflows/ci-pipeline.yml +++ b/.github/workflows/ci-pipeline.yml @@ -2,12 +2,10 @@ name: CI Pipeline on: pull_request: branches: [ main ] + paths: [ "src/**", "tests/**", ".github/**" ] push: branches: [ main ] - paths: - - "src/**" - - "tests/**" - - ".github/**" + paths: [ "src/**", "tests/**", ".github/**" ] jobs: test-and-build: @@ -15,14 +13,14 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v5 - - name: Setup .NET 9 - uses: actions/setup-dotnet@v4 + - name: Setup .NET 10 + uses: actions/setup-dotnet@v5 with: - dotnet-version: '9.0.x' + dotnet-version: '10.0.x' - - uses: actions/cache@v3 + - uses: actions/cache@v4 with: path: ~/.nuget/packages key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj') }} @@ -49,22 +47,21 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: - fetch-depth: 0 # Baixa o histórico de commits do repositório + fetch-depth: 0 - - name: Setup .NET 9 - uses: actions/setup-dotnet@v4 + - name: Setup .NET 10 + uses: actions/setup-dotnet@v5 with: - dotnet-version: '9.0.x' + dotnet-version: '10.0.x' - name: Cache SonarCloud packages uses: actions/cache@v4 with: - path: ~/.sonar/cache # pasta onde irá ficar guardado o cache - # runner.os = pega o nome do sistema operacional - key: ${{ runner.os }}-sonar # Nome único para identificar o cache - restore-keys: ${{ runner.os }}-sonar # Chave alternativa se não encontrar a principal + path: ~/.sonar/cache + key: ${{ runner.os }}-sonar + restore-keys: ${{ runner.os }}-sonar - name: Install SonarScanner run: dotnet tool install --global dotnet-sonarscanner @@ -82,7 +79,6 @@ jobs: - name: Run analisys with SonarScanner env: - CI: true GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} SONAR_PROJECT_KEY: "${{ github.repository_owner }}_riber" diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index 08f0173..0000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,237 +0,0 @@ -# Riber - Changelog - ---- - -## v4.3.2 - 11/12/2025 -**REFATORAÇÃO**: Mudança no result pattern e na classe de erro -- Remove a redundância de codigo na criação de um Error -- Remove sobrecargas na criação de Result com Errors -- Simplifica as respostas de Errors -- Remove método redundante do middleware de validação do token - ---- - -## v4.3.1 - 09/12/2025 -**REFATORAÇÃO**: Remove o método de busca por id da entidade nos IAiModelService -Adiciona relacionamento entre entidade `ProductEmbeddings` e `Company` para uma melhor busca - ---- - -## v4.3.0 - 08/11/2025 -- **NOVO**: Novas entidades par uso de chat e agendes de IA -- Adiciona entidade `Chat` para o usuário armazenar conversas com IA -- Adiciona entidade `ChatMessage` para armazenar mensagens dentro de um chat -- Adiciona a entidade `Assitant` junto com seeders de alguns assistente que terão na aplicacão -- **REFATORAÇÃO**: Mudança na organização de pasta das entidades e corrigindo os Namespace - ---- - -## v4.2.1 - 07/11/2025 -- **REFATORAÇÃO**: Unificar BaseEntity e BaseModel em Tracker para herança simplificada -- Remove `BaseEntityConfiguration` e `BaseModelConfiguration`, substituindo por `BaseTypeConfiguration` -- Introduz abstração `Tracker` consolidando propriedades e métodos comuns -- Atualiza modelos e mapeamentos para herdar de `Tracker` -- Simplifica lógica de auditoria em `AuditInterceptor` para manipular exclusivamente entidades `Tracker` -- Reorganiza mapeamentos seguindo a nova estrutura de herança - ---- - -## v4.2.0 - 04/11/2025 -- **NOVO**: Serviço para geração de Embeddings Genérico -- Adiciona um evento para gerar embeddings dos produtos -- Adiciona models ao banco onde referência as entidades para guardar os embeddings -- Adiciona um consumer para poder gerar os embeddings de forma assíncrona -- **TESTES**: Adiciona testes unitários para o novo consumer de geração de embeddings -- **REFATORAÇÃO**: Adapta o handler de produto para lançar evento de gerar embeddings do produto -- Refatora os teste do PermissionDataService para se tornarem de Integração em vez de Unitários -- Refatora os teste do AuditInterceptor para os casos de ModelBase ter SoftDelete - ---- - -## v4.1.2 - 02/11/2025 - -- **REFATORAÇÃO**: Substituição de jobs de exclusão por arquitetura baseada em mensagens -- Remove `DeleteImageFromStorageDispatcher` e `DeleteImageFromStorageJob` -- Remove `DeleteImageFromStorageScheduler` obsoleto -- Adiciona `ProductImageCreationFailedEvent` para falhas na criação de imagens -- Implementa `ProductImageCreationFailedEventHandler` para publicação de mensagens -- Adiciona `ProductImageCreationFailedMessage` para comunicação assíncrona -- Implementa `ProductImageCreationFailedMessageConsumer` para exclusão assíncrona de imagens -- **TESTES**: Cobertura completa para novo consumidor de mensagens -- Adiciona testes unitários para `ProductImageCreationFailedMessageConsumer` -- Atualiza testes do `CreateProductCommandHandler` para novo fluxo -- **MELHORIA**: Validação aprimorada com `IsNullOrWhiteSpace` para chaves de imagem -- **MELHORIA**: Processamento de exclusão de imagens agora totalmente baseado em eventos e mensagens -- Sistema mais resiliente e escalável para gerenciamento de falhas - ---- - -## v4.1.1 - 02/11/2025 - -- **NOVO**: Sistema de mensageria com MassTransit e RabbitMQ -- Adiciona `IMessagePublisher` para publicação de mensagens assíncronas -- Implementa `MassTransitMessagePublisher` para integração com MassTransit -- Adiciona `MassTransitPublishContextWrapper` para configuração de contexto de publicação -- Implementa `SendEmailMessageConsumer` para processamento assíncrono de e-mails -- Adiciona mensagem `SendEmailMessage` para comunicação entre serviços -- **REFATORAÇÃO**: Migração do sistema de envio de e-mails para arquitetura baseada em mensageria -- Remove implementação síncrona antiga de envio de e-mails -- Atualiza handlers para usar publicação de mensagens em vez de chamadas diretas -- Adiciona configuração do RabbitMQ no Docker Compose -- **MELHORIA**: Melhor separação de responsabilidades e desacoplamento entre camadas -- Sistema de e-mails agora opera de forma assíncrona e resiliente - ---- - -## v4.1.0 - 26/10/2025 - -- **NOVO**: Sistema modular de serviços de autenticação com separação de responsabilidades -- Adiciona `IAuthenticationService` para operações de login e autenticação -- Adiciona `IRoleManagementService` para gerenciamento de roles -- Adiciona `IUserManagementService` para gerenciamento de usuários -- Adiciona `IUserQueryService` para consultas de usuários -- Implementa serviços de Identity: `AuthenticationService`, `RoleManagementService`, `UserManagementService`, `UserQueryService`, `UserMappingService` e `UserCreationService` -- **NOVO**: `EmptyResult` para operações sem retorno de valor -- **REFATORAÇÃO**: Reorganização das interfaces de serviços para pasta `Authentication` -- **REFATORAÇÃO**: Atualização de toda a aplicação (handlers, controllers, middlewares) para usar novos serviços -- **REFATORAÇÃO**: Melhorias no `PermissionDataService` e repositórios -- Remove serviços obsoletos: `IAuthService`, `AuthService`, `UserCreationService` (versão antiga) -- Remove `SpecificationExtension` não utilizada -- Atualiza todos os testes para refletir as mudanças arquiteturais - ---- - -# v4.0.1 - 22/10/2025 - -- **CORREÇÃO**: Mudança no Job de limpeza de imagens na Bucket -- **MELHORIA**: Nova propriedade para mapear StatusCode no Result - ---- - -## v4.0.0 - 19/10/2025 - -- **BREAKING CHANGE**: Reestruturação completa da resposta de erros da API -- Adicionada propriedade `type` para identificar o tipo do erro -- Alterada propriedade `messages` (array) para `message` (string única) -- Adicionada propriedade `details` (objeto) para erros de validação agrupados por campo -- Novo formato: `{ "type": "ERROR_TYPE", "message": "...", "details": { "Field": ["error1", "error2"] } }` -- **MELHORIA**: Erros de validação agora agrupam múltiplas mensagens por campo -- **MELHORIA**: Formato de erros mais alinhado com padrões modernos de API (inspirado em FastEndpoints) - ---- - -## v3.0.1 - 18/10/2025 - -- CORREÇÃO: Troca do pacote de versionamento da API -- Ajuste no setup de testes da camada Api para testes de integração -- Ajuste no analyze.sh para ficar mais consistente a validação de ambiente - ---- - -## v3.0.0 - 17/10/2025 - -- **REFATORAÇÃO**: Mudança na resposta da API, ajustando o Result Pattern -- Remoção da propriedade Details usada para mensagens de validação do FluentValidation -- Remoção da propriedade Message usada para mensagens resumidas -- Unificação dessas duas propriedades de erro para propriedade Messages -- **CORREÇÃO**: Ajustando as versões dos pacotes da aplicação - ---- - -## v2.3.1 - 12/10/2025 - -- **NOVO**: Adiciona um novo job para limpeza de imagens não usadas na Bucket -- **CORREÇÃO**: Ajuste no validator de Criação de empresa com administrador -- Usar SHA da action em vez da versão -- **REFATORAÇÃO**: Padronização no cadastro dos Jobs -- Remoção das docs do projeto, centralizado tudo no README - ---- - -## v2.3.0 - 09/10/25 - -- **NOVO**: Integração do SonarQube no ambiente de desenvolvimento -- Adiciona serviço SonarQube Community no Docker Compose com profile 'analysis' -- Configura banco de dados PostgreSQL dedicado para SonarQube -- Script de inicialização automática do banco SonarQube -- **REFATORAÇÃO**: Melhorias na entidade base e otimizações de código -- Melhora comparação de igualdade na BaseEntity com verificação de tipos -- Otimiza serialização JSON na extensão HttpContext -- Simplifica validações e melhora formatação geral do código -- **MELHORIAS**: Limpeza e padronização de código -- Remove código redundante e melhora legibilidade -- Otimiza imports e espaçamento consistente -- Pequenas otimizações de performance em behaviors e services - ---- - -## v2.2.0 - 07/10/25 - -- **NOVO**: Sistema de diagnósticos e rastreamento distribuído -- Adiciona configuração centralizada para ActivitySource na camada Application -- Melhora significativamente o LoggingBehavior com suporte a distributed tracing -- **REFATORAÇÃO**: Otimização massiva de handlers e services -- Simplifica handlers de autenticação, empresas, usuários e categorias de produtos -- Otimiza services da camada Infrastructure (AuthService, PermissionDataService, AmazonSESService) -- Refatora Domain Specifications para melhor performance -- **MELHORIAS**: Padronização de código e configurações -- Melhora formatação XML em arquivos de configuração de pacotes -- Atualiza configurações de projeto seguindo padrões mais recentes -- Remove código redundante e melhora legibilidade geral -- **TESTES**: Cobertura abrangente para novas funcionalidades -- Adiciona testes completos para Activity tracing no LoggingBehavior -- Atualiza todos os testes para refletir código refatorado -- Melhora cenários de teste para tratamento de erros -- **DOCS**: Atualização da documentação de pacotes e dependências - ---- - -## v2.1.1 - 05/10/25 - -- Adicionado suporte ao SonarQube para análise de qualidade de código -- Substituídos DTOs por Models no projeto para simplificar a lógica de dados - ---- - -## v2.0.1 - 05/10/25 - -- **TEMPORÁRIO**: Deploy na AWS suspenso temporariamente - ---- - -## v2.0.0 - 05/10/25 - -- **BREAKING CHANGE**: Reestruturação das mensagens de erro em módulos organizados -- Remove classe monolítica `ErrorMessage.cs` e substitui por estrutura modular -- Cria subdiretórios organizados: `Common/`, `Entities/` e `ValueObjects/` -- Atualiza mais de 50 arquivos nas camadas Application, Domain, Infrastructure e testes -- Melhora a manutenibilidade do código e organização das mensagens de erro -- **NOVO**: Sistema de gerenciamento de imagens para produtos -- Adiciona entidade `Image` com validações de tipo, tamanho e nome -- Implementa interface de armazenamento com suporte a AWS S3 e armazenamento local -- Cria exceções customizadas para tratamento de erros de imagens -- Adiciona migração do banco de dados para tabela de imagens -- Estabelece relacionamento entre Product e Image (1:N) -- **TEMPORÁRIO**: CreateProduct desativado para refatoração (será reimplementado na próxima PR) - ---- - -## v1.0.3 - 29/09/25 - -- Retira os IsFailure do Result Pattern -- Ajustes os testes para remover o Result Pattern -- Correção no token de acesso do GHCR - ---- - -## v1.0.2 - 29/09/25 - -- Ajusta o CD para usar tags -- Adiciona CHANGELOG.md - ---- - -## v1.0.1 - 28/09/25 - -- Adiciona validação na criação de produto -- Adiciona testes que faltavam \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index afac008..68e3753 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,32 +1,48 @@ -FROM mcr.microsoft.com/dotnet/sdk:9.0-alpine AS build +FROM mcr.microsoft.com/dotnet/sdk:10.0-alpine AS build WORKDIR /build RUN apk add --no-cache bash dos2unix - -COPY Directory.Packages.props Directory.Build.props ./ +COPY Directory.Packages.props Directory.Build.props Riber.slnx ./ COPY src/Riber.Api/Riber.Api.csproj ./src/Riber.Api/ COPY src/Riber.Application/Riber.Application.csproj ./src/Riber.Application/ COPY src/Riber.Domain/Riber.Domain.csproj ./src/Riber.Domain/ COPY src/Riber.Infrastructure/Riber.Infrastructure.csproj ./src/Riber.Infrastructure/ -RUN dotnet restore ./src/Riber.Api/Riber.Api.csproj +COPY tests/Riber.Api.Tests/Riber.Api.Tests.csproj ./tests/Riber.Api.Tests/ +COPY tests/Riber.Application.Tests.Tests/Riber.Application.Tests.csproj ./tests/Riber.Application.Tests/ +COPY tests/Riber.Domain.Tests/Riber.Domain.Tests.csproj ./tests/Riber.Domain.Tests/ +COPY tests/Riber.Infrastructure.Tests/Riber.Infrastructure.Tests.csproj ./tests/Riber.Infrastructure.Tests/ + +RUN dotnet restore Riber.slnx +COPY src/ ./src/ +COPY tests/ ./tests/ -COPY src/Riber.Api ./src/Riber.Api/ -COPY src/Riber.Application ./src/Riber.Application/ -COPY src/Riber.Domain ./src/Riber.Domain/ -COPY src/Riber.Infrastructure ./src/Riber.Infrastructure/ +RUN dotnet publish Riber.slnx -c Release --no-restore + +FROM build AS tests +WORKDIR /build + +RUN dotnet test Riber.slnx \ + -c Release \ + --no-build \ + --no-restore \ + --logger "console;verbosity=detailed" + +FROM build AS publish +WORKDIR /build RUN dotnet publish src/Riber.Api/Riber.Api.csproj \ -c Release \ - -o /app/publish \ - --no-restore + -o /publish \ + --no-restore \ + --no-build -FROM mcr.microsoft.com/dotnet/aspnet:9.0-alpine AS runtime +FROM mcr.microsoft.com/dotnet/aspnet:10.0-alpine AS runtime WORKDIR /app -RUN addgroup -g 1000 appgroup && \ - adduser -u 1000 -G appgroup -s /bin/sh -D appuser && \ +RUN addgroup -g 1000 app-group && \ + adduser -u 1000 -G app-group -s /bin/sh -D app-user && \ apk add --no-cache bash ENV ConnectionStrings__DefaultConnection="" \ @@ -36,10 +52,8 @@ ENV ConnectionStrings__DefaultConnection="" \ ASPNETCORE_ENVIRONMENT=Production \ ASPNETCORE_URLS=http://0.0.0.0:8080 -COPY --from=build /app/publish . - -RUN chown -R appuser:appgroup /app -USER appuser +COPY --from=publish --chown=app-user:app-group /publish . +USER app-user EXPOSE 8080 ENTRYPOINT ["dotnet", "Riber.Api.dll"] \ No newline at end of file diff --git a/Dockerfile.dev b/Dockerfile.dev deleted file mode 100644 index d2c8fca..0000000 --- a/Dockerfile.dev +++ /dev/null @@ -1,34 +0,0 @@ -FROM mcr.microsoft.com/dotnet/sdk:9.0-alpine AS build -WORKDIR /build - -RUN apk add --no-cache bash dos2unix - -COPY Directory.Packages.props ./ -COPY Directory.Build.props ./ -COPY Riber.slnx ./ - -COPY src/Riber.Api/Riber.Api.csproj ./src/Riber.Api/ -COPY src/Riber.Application/Riber.Application.csproj ./src/Riber.Application/ -COPY src/Riber.Domain/Riber.Domain.csproj ./src/Riber.Domain/ -COPY src/Riber.Infrastructure/Riber.Infrastructure.csproj ./src/Riber.Infrastructure/ - -COPY tests/Riber.Api.Tests/Riber.Api.Tests.csproj ./tests/Riber.Api.Tests/ -COPY tests/Riber.Application.Tests/Riber.Application.Tests.csproj ./tests/Riber.Application.Tests/ -COPY tests/Riber.Domain.Tests/Riber.Domain.Tests.csproj ./tests/Riber.Domain.Tests/ -COPY tests/Riber.Infrastructure.Tests/Riber.Infrastructure.Tests.csproj ./tests/Riber.Infrastructure.Tests/ - -RUN dotnet restore Riber.slnx -COPY . . - -RUN dotnet build Riber.slnx -c Release --no-restore -RUN dotnet test Riber.slnx -c Release --no-build --no-restore -RUN dotnet publish src/Riber.Api/Riber.Api.csproj -c Release -o /app/publish --no-build --no-restore - -FROM mcr.microsoft.com/dotnet/aspnet:9.0-alpine AS runtime -WORKDIR /app - -RUN apk add --no-cache \ - bash tzdata nano curl - -COPY --from=build /app/publish . -ENTRYPOINT ["dotnet", "Riber.Api.dll"] \ No newline at end of file diff --git a/Riber.slnx b/Riber.slnx index e27be32..12749de 100644 --- a/Riber.slnx +++ b/Riber.slnx @@ -4,9 +4,7 @@ - - - + diff --git a/analyze.sh b/analyze.sh index 8aa2d1a..56458d6 100755 --- a/analyze.sh +++ b/analyze.sh @@ -2,41 +2,30 @@ set -e -# Detectar ambiente e configurar variáveis -if [[ -n "$CI" || -n "$GITHUB_ACTIONS" ]]; then - if [[ -z "$SONAR_TOKEN" ]]; then - echo "Erro: SONAR_TOKEN não definido" - exit 1 - fi - - TOKEN="$SONAR_TOKEN" - PROJECT_KEY="${SONAR_PROJECT_KEY:-samuelzedec_riber}" - ORGANIZATION="${SONAR_ORGANIZATION:-samuelzedec}" - HOST_URL="https://sonarcloud.io" - SOLUTION_FILE="riber.sln" - USE_ORG_FLAG=true -else - # Local - SonarQube - if [[ -z "$1" ]]; then - echo "Erro: Token não fornecido" - echo "Uso: ./analyze.sh SEU_TOKEN" - exit 1 - fi - - TOKEN="$1" - PROJECT_KEY="riber" - ORGANIZATION="samuelzedec" - HOST_URL="http://localhost:9000" - SOLUTION_FILE="riber.slnx" - USE_ORG_FLAG=false +# Validar variáveis obrigatórias +if [[ -z "$SONAR_TOKEN" ]]; then + echo "Erro: SONAR_TOKEN não definido" + exit 1 fi -# Construir comando base do sonarscanner begin -SONAR_BEGIN_ARGS=( - /k:"$PROJECT_KEY" - /d:sonar.host.url="$HOST_URL" - /d:sonar.token="$TOKEN" - /d:sonar.cs.opencover.reportsPaths="**/coverage.opencover.xml" +# Configurar variáveis +TOKEN="$SONAR_TOKEN" +PROJECT_KEY="${SONAR_PROJECT_KEY:-samuelzedec_riber}" +ORGANIZATION="${SONAR_ORGANIZATION:-samuelzedec}" +HOST_URL="https://sonarcloud.io" +SOLUTION_FILE="${SOLUTION_FILE:-Riber.slnx}" + +echo "Iniciando análise SonarCloud..." +echo " Projeto: $PROJECT_KEY" +echo " Organização: $ORGANIZATION" + +# Begin +dotnet sonarscanner begin \ + /k:"$PROJECT_KEY" \ + /o:"$ORGANIZATION" \ + /d:sonar.host.url="$HOST_URL" \ + /d:sonar.token="$TOKEN" \ + /d:sonar.cs.opencover.reportsPaths="**/coverage.opencover.xml" \ /d:sonar.exclusions="\ **/Migrations/**,\ **/bin/**,\ @@ -46,7 +35,7 @@ SONAR_BEGIN_ARGS=( **/Seeders/**,\ **/Schedulers/**,\ **/*.html,\ -**/Mappings/**" +**/Mappings/**" \ /d:sonar.coverage.exclusions="\ **/Migrations/**,\ **/Program.cs,\ @@ -64,24 +53,17 @@ SONAR_BEGIN_ARGS=( **/Identity/**,\ **/Common/Api/**,\ **/Requests/**,\ -**/UserManagementService.cs/**,\ -**/UserMappingService.cs/**,\ +**/UserManagementService.cs,\ +**/UserMappingService.cs,\ **/EventHandlers/**,\ **/Services/AI/**" -) - -# Adicionar /o: apenas se for SonarCloud -if [[ "$USE_ORG_FLAG" = true ]]; then - SONAR_BEGIN_ARGS=(/o:"$ORGANIZATION" "${SONAR_BEGIN_ARGS[@]}") -fi - -# Begin -dotnet sonarscanner begin "${SONAR_BEGIN_ARGS[@]}" # Build +echo "Building solution..." dotnet build "$SOLUTION_FILE" --no-incremental -# Test +# Test with coverage +echo "Running tests with coverage..." dotnet test "$SOLUTION_FILE" \ --no-build \ --logger trx \ @@ -90,5 +72,7 @@ dotnet test "$SOLUTION_FILE" \ /p:CoverletOutput=./coverage.opencover.xml # End +echo "Uploading results to SonarCloud..." dotnet sonarscanner end /d:sonar.token="$TOKEN" -echo "Análise concluída com sucesso" \ No newline at end of file + +echo "Análise concluída com sucesso!" \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 83dc568..ad0dc4e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -12,7 +12,6 @@ services: - "5432:5432" volumes: - data-dev:/var/lib/postgresql/data - - ./init-sonar-db.sql:/docker-entrypoint-initdb.d/init-sonar-db.sql healthcheck: test: [ "CMD-SHELL", "pg_isready -U postgres" ] interval: 5s @@ -22,7 +21,7 @@ services: api: build: context: . - dockerfile: Dockerfile.dev + dockerfile: Dockerfile target: runtime container_name: riber-api-dev depends_on: @@ -37,25 +36,6 @@ services: RabbitMQ__Host: "amqp://rabbitmq:5672" RabbitMQ__Username: "admin" RabbitMQ__Password: "admin123" - - sonarqube: - profiles: [ "analysis" ] - image: sonarqube:community - container_name: riber-sonarqube-dev - ports: - - "9000:9000" - environment: - SONAR_ES_BOOTSTRAP_CHECKS_DISABLE: true - SONAR_JDBC_URL=jdbc: postgresql://postgres:5432/sonar - SONAR_JDBC_USERNAME: sonar - SONAR_JDBC_PASSWORD: sonar - depends_on: - postgres: - condition: service_healthy - volumes: - - sonarqube_data:/opt/sonarqube/data - - sonarqube_extensions:/opt/sonarqube/extensions - - sonarqube_logs:/opt/sonarqube/logs rabbitmq: image: rabbitmq:4.2.0-management-alpine @@ -68,16 +48,6 @@ services: RABBITMQ_DEFAULT_PASS: admin123 RABBITMQ_DEFAULT_VHOST: / restart: unless-stopped - - dashboard: - image: mcr.microsoft.com/dotnet/aspire-dashboard:latest - container_name: riber-aspire-dashboard - ports: - - "18888:18888" - - "4317:4317" - environment: - DOTNET_DASHBOARD_UNSECURED_ALLOW_ANONYMOUS: true - restart: unless-stopped volumes: data-dev: diff --git a/init-sonar-db.sql b/init-sonar-db.sql deleted file mode 100644 index b199bbf..0000000 --- a/init-sonar-db.sql +++ /dev/null @@ -1,4 +0,0 @@ -CREATE USER sonar WITH PASSWORD 'sonar'; -CREATE DATABASE sonar OWNER sonar; -GRANT ALL PRIVILEGES ON DATABASE sonar TO sonar; -ALTER DATABASE sonar OWNER TO sonar; \ No newline at end of file diff --git a/tests/Riber.Application.Tests/Behaviors/LoggingBehaviorTests.cs b/tests/Riber.Application.Tests/Behaviors/LoggingBehaviorTests.cs index 37d87e9..619589d 100644 --- a/tests/Riber.Application.Tests/Behaviors/LoggingBehaviorTests.cs +++ b/tests/Riber.Application.Tests/Behaviors/LoggingBehaviorTests.cs @@ -122,8 +122,8 @@ public async Task Handle_WhenCancellationRequested_ShouldLogWarningWithElapsedTi } [Trait("Category", "Unit")] - [Fact(DisplayName = "Should log error and re-throw when handler throws exception")] - public async Task Handle_WhenHandlerThrowsException_ShouldLogErrorAndReThrow() + [Fact(DisplayName = "Should add metadata to exception when handler throws")] + public async Task Handle_WhenHandlerThrowsException_ShouldAddMetadataAndReThrow() { // Arrange var sut = CreateSut(); @@ -138,22 +138,18 @@ public async Task Handle_WhenHandlerThrowsException_ShouldLogErrorAndReThrow() ); // Assert - await act.Should() + var exception = await act.Should() .ThrowExactlyAsync() .WithMessage("Test error"); - VerifyLogContains( - LogLevel.Error, - expectedMessageName, - "failed after", - "ms", - Times.Once() - ); + exception.Which.Data["RequestName"].Should().Be(expectedMessageName); + exception.Which.Data["ElapsedMs"].Should().NotBeNull(); + exception.Which.Data["ElapsedMs"].Should().BeOfType(); } [Trait("Category", "Unit")] - [Fact(DisplayName = "Should preserve exception details when logging error")] - public async Task Handle_WhenHandlerThrowsException_ShouldLogExceptionDetails() + [Fact(DisplayName = "Should NOT log error when handler throws exception")] + public async Task Handle_WhenHandlerThrowsException_ShouldNotLogError() { // Arrange var sut = CreateSut(); @@ -173,19 +169,54 @@ await sut.Handle( // Expected exception } - // Assert + // Assert - Verifica que NÃO logou erro _mockLogger.Verify( x => x.Log( LogLevel.Error, It.IsAny(), It.IsAny(), - expectedException, // Verifica que a exceção original foi passada + It.IsAny(), It.IsAny>() ), - Times.Once + Times.Never // ← Mudou de Once para Never ); } + [Trait("Category", "Unit")] + [Fact(DisplayName = "Should set activity status to error when exception occurs")] + public async Task Handle_WhenHandlerThrowsException_ShouldSetActivityStatusToError() + { + // Arrange + var sut = CreateSut(); + var expectedException = new InvalidOperationException("Test error"); + Activity? capturedActivity = null; + + using var listener = new ActivityListener(); + listener.ShouldListenTo = source => source.Name == "Riber.Application"; + listener.Sample = (ref _) => ActivitySamplingResult.AllData; + listener.ActivityStarted = activity => capturedActivity = activity; + ActivitySource.AddActivityListener(listener); + + // Act + try + { + await sut.Handle( + _request, + CreateFailingHandler(expectedException), + CancellationToken.None + ); + } + catch (InvalidOperationException) + { + // Expected + } + + // Assert + capturedActivity.Should().NotBeNull(); + capturedActivity!.Status.Should().Be(ActivityStatusCode.Error); + capturedActivity.StatusDescription.Should().Be("Test error"); + } + [Trait("Category", "Unit")] [Fact(DisplayName = "Should create activity with correct name")] public async Task Handle_WhenRequestProcessed_ShouldCreateActivityWithCorrectName() @@ -194,10 +225,9 @@ public async Task Handle_WhenRequestProcessed_ShouldCreateActivityWithCorrectNam var sut = CreateSut(); Activity? capturedActivity = null; - // Captura a Activity criada using var listener = new ActivityListener(); listener.ShouldListenTo = source => source.Name == "Riber.Application"; - listener.Sample = (ref ActivityCreationOptions _) => ActivitySamplingResult.AllData; + listener.Sample = (ref _) => ActivitySamplingResult.AllData; listener.ActivityStarted = activity => capturedActivity = activity; ActivitySource.AddActivityListener(listener); @@ -209,6 +239,28 @@ public async Task Handle_WhenRequestProcessed_ShouldCreateActivityWithCorrectNam capturedActivity!.DisplayName.Should().Be($"Mediator.{nameof(RequestTest)}"); } + [Trait("Category", "Unit")] + [Fact(DisplayName = "Should set activity status to Ok when request succeeds")] + public async Task Handle_WhenRequestSucceeds_ShouldSetActivityStatusToOk() + { + // Arrange + var sut = CreateSut(); + Activity? capturedActivity = null; + + using var listener = new ActivityListener(); + listener.ShouldListenTo = source => source.Name == "Riber.Application"; + listener.Sample = (ref _) => ActivitySamplingResult.AllData; + listener.ActivityStarted = activity => capturedActivity = activity; + ActivitySource.AddActivityListener(listener); + + // Act + await sut.Handle(_request, CreateSuccessfulHandler(), CancellationToken.None); + + // Assert + capturedActivity.Should().NotBeNull(); + capturedActivity!.Status.Should().Be(ActivityStatusCode.Ok); + } + #region Helper Methods private LoggingBehavior CreateSut()