diff --git a/.github/workflows/sonar.yml b/.github/workflows/sonar.yml
index 83374b4..d7b9a1c 100644
--- a/.github/workflows/sonar.yml
+++ b/.github/workflows/sonar.yml
@@ -5,10 +5,20 @@ on:
- master
pull_request:
types: [opened, synchronize, reopened]
+env:
+ ASPNETCORE_ENVIRONMENT: Test
jobs:
build:
name: Build
- runs-on: windows-latest
+ runs-on: ubuntu-latest
+ services:
+ sqlserver:
+ image: mcr.microsoft.com/mssql/server:2019-latest
+ ports:
+ - 1433:1433
+ env:
+ ACCEPT_EULA: Y
+ SA_PASSWORD: SqlServer2019!
steps:
- name: Set up JDK 17
uses: actions/setup-java@v3
@@ -18,33 +28,20 @@ jobs:
- uses: actions/checkout@v2
with:
fetch-depth: 0
- - name: Cache SonarCloud packages
- uses: actions/cache@v1
- with:
- path: ~\sonar\cache
- key: ${{ runner.os }}-sonar
- restore-keys: ${{ runner.os }}-sonar
- - name: Cache SonarCloud scanner
- id: cache-sonar-scanner
- uses: actions/cache@v1
- with:
- path: .\.sonar\scanner
- key: ${{ runner.os }}-sonar-scanner
- restore-keys: ${{ runner.os }}-sonar-scanner
- name: Install SonarCloud scanner
- if: steps.cache-sonar-scanner.outputs.cache-hit != 'true'
- shell: powershell
run: |
- New-Item -Path .\.sonar\scanner -ItemType Directory
- dotnet tool update dotnet-sonarscanner --tool-path .\.sonar\scanner
+ dotnet tool install --global dotnet-sonarscanner --version 5.14.0
+ - name: Install EF Core Tools
+ run: |
+ dotnet tool install --global dotnet-ef --version 7.0.14
- name: Build and analyze
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
- shell: powershell
run: |
- .\.sonar\scanner\dotnet-sonarscanner begin /k:"vanderlan_Orion-Api" /o:"vanderlan-gomes" /d:sonar.token="${{ secrets.SONAR_TOKEN }}" /d:sonar.host.url="https://sonarcloud.io" /d:sonar.cs.opencover.reportsPaths="opencover.xml"
+ dotnet ef database update -p src/Orion.Infra.Data -s src/Orion.Api --verbose
+ dotnet sonarscanner begin /k:"vanderlan_Orion-Api" /o:"vanderlan-gomes" /d:sonar.token="${{ secrets.SONAR_TOKEN }}" /d:sonar.host.url="https://sonarcloud.io" /d:sonar.cs.opencover.reportsPaths="opencover.xml"
dotnet build Orion.sln
dotnet test /p:CollectCoverage=true /p:CoverletOutputFormat=opencover /p:CoverletOutput=coverage /p:Exclude=[xunit.*]* Orion.sln
- cp tests\Orion.Test\coverage.opencover.xml opencover.xml
- .\.sonar\scanner\dotnet-sonarscanner end /d:sonar.token="${{ secrets.SONAR_TOKEN }}"
\ No newline at end of file
+ cp tests/Orion.Test/coverage.opencover.xml opencover.xml
+ dotnet sonarscanner end /d:sonar.token="${{ secrets.SONAR_TOKEN }}"
diff --git a/.gitignore b/.gitignore
index 3d99822..06dfc73 100644
--- a/.gitignore
+++ b/.gitignore
@@ -263,3 +263,4 @@ __pycache__/
/VBaseProject.Test/coverage.json
/.editorconfig
/.sonarqube
+/tests/Orion.Test/coverage
diff --git a/README.md b/README.md
index ffb5576..fc54424 100644
--- a/README.md
+++ b/README.md
@@ -1,9 +1,11 @@
# **Orion Api**
[![Build](https://github.com/vanderlan/Orion-Api/actions/workflows/sonar.yml/badge.svg)](https://github.com/vanderlan/Orion-Api/actions/workflows/sonar.yml)
-[![Coverage Status](https://coveralls.io/repos/github/vanderlan/Orion-Api/badge.svg)](https://coveralls.io/github/vanderlan/Orion-Api)
-[![GitHub release](https://img.shields.io/github/release/vanderlan/Orion-Api.svg)](https://GitHub.com/vanderlan/Orion-Api/)
-[![GitHub repo size](https://img.shields.io/github/repo-size/vanderlan/Orion-Api)](https://github.com/vanderlan/Orion-Api)
+[![Coverage](https://sonarcloud.io/api/project_badges/measure?project=vanderlan_Orion-Api&metric=coverage)](https://sonarcloud.io/summary/new_code?id=vanderlan_Orion-Api)
+[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=vanderlan_Orion-Api&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=vanderlan_Orion-Api)
+[![Technical Debt](https://sonarcloud.io/api/project_badges/measure?project=vanderlan_Orion-Api&metric=sqale_index)](https://sonarcloud.io/summary/new_code?id=vanderlan_Orion-Api)
+[![Manteinability](https://api.codeclimate.com/v1/badges/76a30970ddd45c75129b/maintainability)](https://codeclimate.com/github/vanderlan/Orion-Api/maintainability)
+[![GitHub release](https://img.shields.io/github/release/vanderlan/Orion-Api.svg)](https://github.com/vanderlan/Orion-Api/releases)
**About this Project**
@@ -50,7 +52,7 @@ The main objective is to start projects with a clean and simple architecture, wi
**CI & CD**
- 1. Automated tests;
+ 1. Unit, Integration and Api Tests;
2. Continuous Integration (GitHub CI);
3. Continuous Delivery (GitHub CI);
4. DockerHub Integration;
@@ -72,4 +74,4 @@ The main objective is to start projects with a clean and simple architecture, wi
dotnet ef migrations add MigrationName -p Orion.Infra.Data -s Orion.Api
dotnet ef database update -p Orion.Infra.Data -s Orion.Api --verbose
-Author: https://github.com/vanderlan
\ No newline at end of file
+*author:* https://github.com/vanderlan
\ No newline at end of file
diff --git a/docker-compose.yaml b/docker-compose.yaml
index 07847a6..b8816bb 100644
--- a/docker-compose.yaml
+++ b/docker-compose.yaml
@@ -8,6 +8,20 @@ services:
ports:
- "5010:80"
container_name: orion-api
+ depends_on:
+ - sqlserver
+ networks:
+ - backend-network
+
+ sqlserver:
+ image: mcr.microsoft.com/mssql/server:2019-latest
+ environment:
+ SA_PASSWORD: "SqlServer2019!"
+ ACCEPT_EULA: "Y"
+ MSSQL_PID: "Developer"
+ ports:
+ - "1434:1433"
+ container_name: sqlserver
networks:
- backend-network
diff --git a/src/Orion.Api/Configuration/HealthCheckConfiguration.cs b/src/Orion.Api/Configuration/HealthCheckConfiguration.cs
index 0f76074..ba52c0b 100644
--- a/src/Orion.Api/Configuration/HealthCheckConfiguration.cs
+++ b/src/Orion.Api/Configuration/HealthCheckConfiguration.cs
@@ -15,7 +15,7 @@ public static void ConfigureHealthCheck(this IApplicationBuilder app)
public static void AddApplicationHealthChecks(this IServiceCollection services, IConfiguration configuration)
{
- services.AddHealthChecks().AddSqlServer(configuration["DatabaseOptions:ConnectionString"],
+ services.AddHealthChecks().AddSqlServer(configuration["ConnectionStrings:OrionDatabase"],
tags: new[]
{
"database"
diff --git a/src/Orion.Api/Controllers/V1/UsersController.cs b/src/Orion.Api/Controllers/V1/UsersController.cs
index 819e5e5..bc1ac88 100644
--- a/src/Orion.Api/Controllers/V1/UsersController.cs
+++ b/src/Orion.Api/Controllers/V1/UsersController.cs
@@ -3,6 +3,7 @@
using Microsoft.AspNetCore.Mvc;
using Orion.Api.Attributes;
using Orion.Api.Controllers.Base;
+using Orion.Application.Core.Commands.UserChangePassword;
using Orion.Application.Core.Commands.UserCreate;
using Orion.Application.Core.Commands.UserDelete;
using Orion.Application.Core.Commands.UserUpdate;
@@ -83,4 +84,16 @@ public async Task Delete(string id)
return NoContent();
}
+
+ [HttpPatch("Me/PasswordChange")]
+ [SwaggerResponse((int)HttpStatusCode.BadRequest, "A error response with the error description", typeof(ExceptionResponse))]
+ [SwaggerResponse((int)HttpStatusCode.Accepted)]
+ [SwaggerResponse((int)HttpStatusCode.NotFound)]
+ [SwaggerResponse((int)HttpStatusCode.Unauthorized)]
+ public async Task PatchChangePassword([FromBody] UserChangePasswordRequest userChangePasswordRequest)
+ {
+ await Mediator.Send(userChangePasswordRequest);
+
+ return Accepted();
+ }
}
diff --git a/src/Orion.Api/Models/RefreshTokenModel.cs b/src/Orion.Api/Models/RefreshTokenModel.cs
deleted file mode 100644
index a1bee63..0000000
--- a/src/Orion.Api/Models/RefreshTokenModel.cs
+++ /dev/null
@@ -1,7 +0,0 @@
-namespace Orion.Api.Models;
-
-public class RefreshTokenModel
-{
- public string RefreshToken { get; set; }
- public string Token { get; set; }
-}
diff --git a/src/Orion.Api/Models/UserLoginModel.cs b/src/Orion.Api/Models/UserLoginModel.cs
deleted file mode 100644
index 3ed57b0..0000000
--- a/src/Orion.Api/Models/UserLoginModel.cs
+++ /dev/null
@@ -1,13 +0,0 @@
-using System.ComponentModel.DataAnnotations;
-
-namespace Orion.Api.Models;
-
-public class UserLoginModel
-{
- [Required]
- [EmailAddress]
- public string Email { get; set; }
-
- [Required]
- public string Password { get; set; }
-}
diff --git a/src/Orion.Api/Orion.Api.csproj b/src/Orion.Api/Orion.Api.csproj
index 6c75ea4..16f619a 100644
--- a/src/Orion.Api/Orion.Api.csproj
+++ b/src/Orion.Api/Orion.Api.csproj
@@ -13,10 +13,6 @@
-
- runtime; build; native; contentfiles; analyzers; buildtransitive
- all
-
@@ -26,7 +22,7 @@
-
+
diff --git a/src/Orion.Api/appsettings.Development.json b/src/Orion.Api/appsettings.Development.json
index 21ec9c5..33908bd 100644
--- a/src/Orion.Api/appsettings.Development.json
+++ b/src/Orion.Api/appsettings.Development.json
@@ -6,11 +6,9 @@
"Microsoft": "Information"
}
},
-
- "DatabaseOptions": {
- "ConnectionString": "Data Source=10.0.0.90,1433;Initial Catalog=Orion;User ID=sa;Password=123;TrustServerCertificate=True"
+ "ConnectionStrings": {
+ "OrionDatabase": "Data Source=localhost,1433;Initial Catalog=Orion;User ID=Orion;Password=123;TrustServerCertificate=True"
},
-
"JwtOptions": {
"SymmetricSecurityKey": "5cCI6IkpXVCJ9.eyJlbWFpbCI6InZhbmRlcmxhbi5nc0BnbWFpbC5jb20iLCJodHRwOi8vc2NoZW1hcy5taWNyb3NvZnQuY29tL3dzLzIwMDgvMDYvaWRlbnRpdHkvY2xhaW1zL3JvbGUiOiJhZG1p",
"Issuer": "Orion API",
diff --git a/src/Orion.Api/appsettings.Production.json b/src/Orion.Api/appsettings.Production.json
index cffddb4..3e5c31e 100644
--- a/src/Orion.Api/appsettings.Production.json
+++ b/src/Orion.Api/appsettings.Production.json
@@ -6,11 +6,9 @@
"Microsoft": "Information"
}
},
-
- "DatabaseOptions": {
- "ConnectionString": "Data Source=10.0.0.90,1433;Initial Catalog=Orion;User ID=sa;Password=123;TrustServerCertificate=True"
+ "ConnectionStrings": {
+ "OrionDatabase": "Data Source=localhost,1433;Initial Catalog=Orion;User ID=Orion;Password=123;TrustServerCertificate=True"
},
-
"JwtOptions": {
"SymmetricSecurityKey": "5cCI6IkpXVCJ9.eyJlbWFpbCI6InZhbmRlcmxhbi5nc0BnbWFpbC5jb20iLCJodHRwOi8vc2NoZW1hcy5taWNyb3NvZnQuY29tL3dzLzIwMDgvMDYvaWRlbnRpdHkvY2xhaW1zL3JvbGUiOiJhZG1p",
"Issuer": "Orion API",
diff --git a/src/Orion.Api/appsettings.Test.json b/src/Orion.Api/appsettings.Test.json
index fa32847..91afafa 100644
--- a/src/Orion.Api/appsettings.Test.json
+++ b/src/Orion.Api/appsettings.Test.json
@@ -6,11 +6,9 @@
"Microsoft": "Information"
}
},
-
- "DatabaseOptions": {
- "ConnectionString": "Data Source=localhost,1433;Initial Catalog=Orion;User ID=sa;Password=123;TrustServerCertificate=True"
+ "ConnectionStrings": {
+ "OrionDatabase": "Data Source=localhost,1433;Initial Catalog=Orion;User ID=sa;Password=SqlServer2019!;TrustServerCertificate=True"
},
-
"JwtOptions": {
"SymmetricSecurityKey": "5cCI6IkpXVCJ9.eyJlbWFpbCI6InZhbmRlcmxhbi5nc0BnbWFpbC5jb20iLCJodHRwOi8vc2NoZW1hcy5taWNyb3NvZnQuY29tL3dzLzIwMDgvMDYvaWRlbnRpdHkvY2xhaW1zL3JvbGUiOiJhZG1p",
"Issuer": "Orion API",
@@ -20,4 +18,4 @@
"ElasticConfiguration": {
"Uri": "http://localhost:9200"
}
-}
+}
\ No newline at end of file
diff --git a/src/Orion.Application.Core/Commands/UserChangePassword/UserChangePasswordRequest.cs b/src/Orion.Application.Core/Commands/UserChangePassword/UserChangePasswordRequest.cs
new file mode 100644
index 0000000..444eda5
--- /dev/null
+++ b/src/Orion.Application.Core/Commands/UserChangePassword/UserChangePasswordRequest.cs
@@ -0,0 +1,11 @@
+using MediatR;
+
+namespace Orion.Application.Core.Commands.UserChangePassword
+{
+ public class UserChangePasswordRequest : IRequest
+ {
+ public string CurrentPassword { get; set; }
+ public string NewPassword { get; set; }
+ public string NewPasswordConfirm { get; set; }
+ }
+}
diff --git a/src/Orion.Application.Core/Commands/UserChangePassword/UserUpdatePasswordRequestHandler.cs b/src/Orion.Application.Core/Commands/UserChangePassword/UserUpdatePasswordRequestHandler.cs
new file mode 100644
index 0000000..9545182
--- /dev/null
+++ b/src/Orion.Application.Core/Commands/UserChangePassword/UserUpdatePasswordRequestHandler.cs
@@ -0,0 +1,30 @@
+using MediatR;
+using Orion.Application.Core.Notifications.UserPasswordChanged;
+using Orion.Domain.Core.Authentication;
+using Orion.Domain.Core.Services.Interfaces;
+
+namespace Orion.Application.Core.Commands.UserChangePassword
+{
+ public class UserUpdatePasswordRequestHandler : IRequestHandler
+ {
+ private readonly IUserService _userService;
+ private readonly ICurrentUser _currentUser;
+ private readonly IMediator _mediator;
+
+ public UserUpdatePasswordRequestHandler(IUserService userService, ICurrentUser currentUser, IMediator mediator)
+ {
+ _userService = userService;
+ _currentUser = currentUser;
+ _mediator = mediator;
+ }
+
+ public async Task Handle(UserChangePasswordRequest request, CancellationToken cancellationToken)
+ {
+ await _userService.ChangePasswordAsync(_currentUser.Id, request.CurrentPassword, request.NewPassword);
+
+ await _mediator.Publish(new UserPasswordChangedNotification(), cancellationToken);
+
+ return Unit.Value;
+ }
+ }
+}
diff --git a/src/Orion.Application.Core/Commands/UserChangePassword/UserUpdateRequestValidator.cs b/src/Orion.Application.Core/Commands/UserChangePassword/UserUpdateRequestValidator.cs
new file mode 100644
index 0000000..957fd56
--- /dev/null
+++ b/src/Orion.Application.Core/Commands/UserChangePassword/UserUpdateRequestValidator.cs
@@ -0,0 +1,25 @@
+using FluentValidation;
+using Microsoft.Extensions.Localization;
+using Orion.Croscutting.Resources;
+using static Orion.Croscutting.Resources.Messages.MessagesKeys;
+
+namespace Orion.Application.Core.Commands.UserChangePassword;
+
+public class UserChangePasswordRequestValidator : AbstractValidator
+{
+ public UserChangePasswordRequestValidator(IStringLocalizer stringLocalizer)
+ {
+
+ RuleFor(c => c.CurrentPassword)
+ .NotEmpty().WithMessage(stringLocalizer[UserMessages.EmptyPasword]);
+
+ RuleFor(c => c.NewPassword)
+ .NotEmpty().WithMessage(stringLocalizer[UserMessages.EmptyNewPasword]);
+
+ RuleFor(c => c.NewPasswordConfirm)
+ .NotEmpty().WithMessage(stringLocalizer[UserMessages.EmptyNewPaswordConfirmation]);
+
+ RuleFor(x => x.NewPassword).Equal(x => x.NewPasswordConfirm)
+ .WithMessage(stringLocalizer[UserMessages.PaswordAndConfirmationDifferent]);
+ }
+}
diff --git a/src/Orion.Application.Core/Commands/UserCreate/UserCreateRequestValidator.cs b/src/Orion.Application.Core/Commands/UserCreate/UserCreateRequestValidator.cs
index ccf7685..ede9f8c 100644
--- a/src/Orion.Application.Core/Commands/UserCreate/UserCreateRequestValidator.cs
+++ b/src/Orion.Application.Core/Commands/UserCreate/UserCreateRequestValidator.cs
@@ -14,15 +14,12 @@ public UserCreateRequestValidator(IStringLocalizer stringLocaliz
.WithMessage(stringLocalizer[UserMessages.NullEntity]);
RuleFor(c => c.Name)
- .NotEmpty().WithMessage(stringLocalizer[UserMessages.EmptyName])
- .NotNull().WithMessage(stringLocalizer[UserMessages.EmptyName]);
+ .NotEmpty().WithMessage(stringLocalizer[UserMessages.EmptyName]);
RuleFor(c => c.Email)
- .NotEmpty().WithMessage(stringLocalizer[UserMessages.EmptyEmail])
- .NotNull().WithMessage(stringLocalizer[UserMessages.EmptyEmail]);
+ .NotEmpty().WithMessage(stringLocalizer[UserMessages.EmptyEmail]);
RuleFor(c => c.Password)
- .NotEmpty().WithMessage(stringLocalizer[UserMessages.EmptyPasword])
- .NotNull().WithMessage(stringLocalizer[UserMessages.EmptyPasword]);
+ .NotEmpty().WithMessage(stringLocalizer[UserMessages.EmptyPasword]);
}
}
diff --git a/src/Orion.Application.Core/Commands/UserDelete/UserDeleteRequestValidator.cs b/src/Orion.Application.Core/Commands/UserDelete/UserDeleteRequestValidator.cs
deleted file mode 100644
index 1642a52..0000000
--- a/src/Orion.Application.Core/Commands/UserDelete/UserDeleteRequestValidator.cs
+++ /dev/null
@@ -1,20 +0,0 @@
-using FluentValidation;
-using Microsoft.Extensions.Localization;
-using Orion.Croscutting.Resources;
-using static Orion.Croscutting.Resources.Messages.MessagesKeys;
-
-namespace Orion.Application.Core.Commands.UserDelete;
-
-public class UserDeleteRequestValidator : AbstractValidator
-{
- public UserDeleteRequestValidator(IStringLocalizer stringLocalizer)
- {
- RuleFor(c => c)
- .NotNull()
- .WithMessage(stringLocalizer[UserMessages.NullEntity]);
-
- RuleFor(c => c.PublicId)
- .NotEmpty().WithMessage(stringLocalizer[UserMessages.EmptyId])
- .NotNull().WithMessage(stringLocalizer[UserMessages.EmptyId]);
- }
-}
diff --git a/src/Orion.Application.Core/Commands/UserUpdate/UserUpdateRequestValidator.cs b/src/Orion.Application.Core/Commands/UserUpdate/UserUpdateRequestValidator.cs
index b87dd20..3bd7c86 100644
--- a/src/Orion.Application.Core/Commands/UserUpdate/UserUpdateRequestValidator.cs
+++ b/src/Orion.Application.Core/Commands/UserUpdate/UserUpdateRequestValidator.cs
@@ -14,15 +14,12 @@ public UserUpdateRequestValidator(IStringLocalizer stringLocaliz
.WithMessage(stringLocalizer[UserMessages.NullEntity]);
RuleFor(c => c.Name)
- .NotEmpty().WithMessage(stringLocalizer[UserMessages.EmptyName])
- .NotNull().WithMessage(stringLocalizer[UserMessages.EmptyName]);
+ .NotEmpty().WithMessage(stringLocalizer[UserMessages.EmptyName]);
RuleFor(c => c.Email)
- .NotEmpty().WithMessage(stringLocalizer[UserMessages.EmptyEmail])
- .NotNull().WithMessage(stringLocalizer[UserMessages.EmptyEmail]);
+ .NotEmpty().WithMessage(stringLocalizer[UserMessages.EmptyEmail]);
RuleFor(c => c.PublicId)
- .NotEmpty().WithMessage(stringLocalizer[UserMessages.EmptyId])
- .NotNull().WithMessage(stringLocalizer[UserMessages.EmptyId]);
+ .NotEmpty().WithMessage(stringLocalizer[UserMessages.EmptyId]);
}
}
diff --git a/src/Orion.Application.Core/Notifications/UserPasswordChanged/UserPasswordChangedNotification.cs b/src/Orion.Application.Core/Notifications/UserPasswordChanged/UserPasswordChangedNotification.cs
new file mode 100644
index 0000000..545cf59
--- /dev/null
+++ b/src/Orion.Application.Core/Notifications/UserPasswordChanged/UserPasswordChangedNotification.cs
@@ -0,0 +1,9 @@
+using MediatR;
+
+namespace Orion.Application.Core.Notifications.UserPasswordChanged
+{
+ public class UserPasswordChangedNotification : INotification
+ {
+
+ }
+}
diff --git a/src/Orion.Application.Core/Notifications/UserPasswordChanged/UserPasswordChangedNotificationHandler.cs b/src/Orion.Application.Core/Notifications/UserPasswordChanged/UserPasswordChangedNotificationHandler.cs
new file mode 100644
index 0000000..1df9f79
--- /dev/null
+++ b/src/Orion.Application.Core/Notifications/UserPasswordChanged/UserPasswordChangedNotificationHandler.cs
@@ -0,0 +1,29 @@
+using MediatR;
+using Microsoft.Extensions.Logging;
+using Newtonsoft.Json;
+using Orion.Domain.Core.Authentication;
+
+namespace Orion.Application.Core.Notifications.UserPasswordChanged
+{
+ public class UserPasswordChangedNotificationHandler : INotificationHandler
+ {
+ private readonly ILogger _logger;
+ private readonly ICurrentUser _currentUser;
+
+ public UserPasswordChangedNotificationHandler(ILogger logger, ICurrentUser currentUser)
+ {
+ _logger = logger;
+ _currentUser = currentUser;
+ }
+
+ public async Task Handle(UserPasswordChangedNotification notification, CancellationToken cancellationToken)
+ {
+ _logger.LogInformation("A notification {notificationType} has been received. Notification details: {notification}. Action performed by: {currentUserName}",
+ nameof(UserPasswordChangedNotification),
+ JsonConvert.SerializeObject(notification, Formatting.Indented),
+ _currentUser.ToString());
+
+ await Task.CompletedTask;
+ }
+ }
+}
diff --git a/src/Orion.Application.Core/Orion.Application.Core.csproj b/src/Orion.Application.Core/Orion.Application.Core.csproj
index 1cadd51..a3c061b 100644
--- a/src/Orion.Application.Core/Orion.Application.Core.csproj
+++ b/src/Orion.Application.Core/Orion.Application.Core.csproj
@@ -10,10 +10,10 @@
-
+
-
+
diff --git a/src/Orion.Application.Core/Queries/UserGetById/UserGetByIdRequestValidator.cs b/src/Orion.Application.Core/Queries/UserGetById/UserGetByIdRequestValidator.cs
deleted file mode 100644
index 25e2f44..0000000
--- a/src/Orion.Application.Core/Queries/UserGetById/UserGetByIdRequestValidator.cs
+++ /dev/null
@@ -1,20 +0,0 @@
-using FluentValidation;
-using Microsoft.Extensions.Localization;
-using Orion.Croscutting.Resources;
-using static Orion.Croscutting.Resources.Messages.MessagesKeys;
-
-namespace Orion.Application.Core.Queries.UserGetById;
-
-public class UserGetByIdRequestValidator : AbstractValidator
-{
- public UserGetByIdRequestValidator(IStringLocalizer stringLocalizer)
- {
- RuleFor(c => c)
- .NotNull()
- .WithMessage(stringLocalizer[UserMessages.NullEntity]);
-
- RuleFor(c => c.PublicId)
- .NotEmpty().WithMessage(stringLocalizer[UserMessages.EmptyId])
- .NotNull().WithMessage(stringLocalizer[UserMessages.EmptyId]);
- }
-}
diff --git a/src/Orion.Application.Core/Queries/UserGetById/UserGetByIdResponse.cs b/src/Orion.Application.Core/Queries/UserGetById/UserGetByIdResponse.cs
index 937a155..3ac684f 100644
--- a/src/Orion.Application.Core/Queries/UserGetById/UserGetByIdResponse.cs
+++ b/src/Orion.Application.Core/Queries/UserGetById/UserGetByIdResponse.cs
@@ -1,4 +1,5 @@
using Orion.Domain.Core.Entities;
+using Orion.Domain.Core.Entities.Enuns;
namespace Orion.Application.Core.Queries.UserGetById;
@@ -7,6 +8,7 @@ public class UserGetByIdResponse
public string PublicId { get; set; }
public string Name { get; set; }
public string Email { get; set; }
+ public UserProfile Profile { get; set; }
public DateTime LastUpdated { get; set; }
public DateTime CreatedAt { get; set; }
@@ -21,7 +23,8 @@ public static explicit operator UserGetByIdResponse(User user)
Name = user.Name,
Email = user.Email,
LastUpdated = user.LastUpdated,
- CreatedAt = user.CreatedAt
+ CreatedAt = user.CreatedAt,
+ Profile = user.Profile
};
}
diff --git a/src/Orion.Croscutting.Resources/Messages/MessagesKeys.cs b/src/Orion.Croscutting.Resources/Messages/MessagesKeys.cs
index f40b6cd..0933068 100644
--- a/src/Orion.Croscutting.Resources/Messages/MessagesKeys.cs
+++ b/src/Orion.Croscutting.Resources/Messages/MessagesKeys.cs
@@ -24,5 +24,10 @@ public static class UserMessages
public const string EmptyEmail = "User.EmptyEmail";
public const string EmptyId = "User.EmptyId";
public const string EmailExists = "User.EmailExists";
+ public const string InvalidPassword = "User.InvalidPassword";
+
+ public const string EmptyNewPasword = "User.InvalidNewPassword";
+ public const string EmptyNewPaswordConfirmation = "User.EmptyNewPaswordConfirmation";
+ public const string PaswordAndConfirmationDifferent = "User.PaswordConfirmationDifferent";
}
}
diff --git a/src/Orion.Croscutting.Resources/Resources/OrionResources.en-US.resx b/src/Orion.Croscutting.Resources/Resources/OrionResources.en-US.resx
index e385aea..32819c9 100644
--- a/src/Orion.Croscutting.Resources/Resources/OrionResources.en-US.resx
+++ b/src/Orion.Croscutting.Resources/Resources/OrionResources.en-US.resx
@@ -147,4 +147,16 @@
Is necessary to inform the Id
+
+ The password entered is incorrect
+
+
+ Is necessary to inform the New Password Confirmation
+
+
+ Is necessary to inform the New Password
+
+
+ New Password and Confirmation must be equal
+
\ No newline at end of file
diff --git a/src/Orion.Croscutting.Resources/Resources/OrionResources.pt-BR.resx b/src/Orion.Croscutting.Resources/Resources/OrionResources.pt-BR.resx
index f7ee555..44b1734 100644
--- a/src/Orion.Croscutting.Resources/Resources/OrionResources.pt-BR.resx
+++ b/src/Orion.Croscutting.Resources/Resources/OrionResources.pt-BR.resx
@@ -147,4 +147,16 @@
É necessário informar o Id
+
+ A senha informada está incorreta
+
+
+ É necessário informar a Confirmação da Nova Senha
+
+
+ É necessário informar a Nova Senha
+
+
+ A nova senha e a confiração da senha precisam ser iguais
+
\ No newline at end of file
diff --git a/src/Orion.Domain.Core/Authentication/AuthenticationConfiguration.cs b/src/Orion.Domain.Core/Authentication/AuthenticationConfiguration.cs
index 08f9e75..5f53e4c 100644
--- a/src/Orion.Domain.Core/Authentication/AuthenticationConfiguration.cs
+++ b/src/Orion.Domain.Core/Authentication/AuthenticationConfiguration.cs
@@ -5,6 +5,6 @@ public static class AuthorizationConfiguration
public static class Roles
{
public const string Admin = "admin";
- public const string Customer = "customer";
+ public const string Operator = "operator";
}
}
diff --git a/src/Orion.Domain.Core/Entities/Enuns/UserProfile.cs b/src/Orion.Domain.Core/Entities/Enuns/UserProfile.cs
index 67a7baf..611840d 100644
--- a/src/Orion.Domain.Core/Entities/Enuns/UserProfile.cs
+++ b/src/Orion.Domain.Core/Entities/Enuns/UserProfile.cs
@@ -8,6 +8,6 @@ public enum UserProfile
[Description(Roles.Admin)]
Admin = 1,
- [Description(Roles.Customer)]
- Customer = 2
+ [Description(Roles.Operator)]
+ Operator = 2
}
diff --git a/src/Orion.Domain.Core/Extensions/EnumExtensions.cs b/src/Orion.Domain.Core/Extensions/EnumExtensions.cs
index 137e845..b852055 100644
--- a/src/Orion.Domain.Core/Extensions/EnumExtensions.cs
+++ b/src/Orion.Domain.Core/Extensions/EnumExtensions.cs
@@ -7,7 +7,7 @@ public static class EnumExtensions
{
public static string Description(this T source)
{
- var fieldInfo = source.GetType().GetField(source.ToString());
+ var fieldInfo = source.GetType().GetField(source.ToString() ?? string.Empty);
var attributes = (DescriptionAttribute[])fieldInfo.GetCustomAttributes(typeof(DescriptionAttribute), false);
diff --git a/src/Orion.Domain.Core/Services/Interfaces/IUserService.cs b/src/Orion.Domain.Core/Services/Interfaces/IUserService.cs
index 659f155..527793a 100644
--- a/src/Orion.Domain.Core/Services/Interfaces/IUserService.cs
+++ b/src/Orion.Domain.Core/Services/Interfaces/IUserService.cs
@@ -11,4 +11,5 @@ public interface IUserService : IBaseService
Task<(User User, RefreshToken RefreshToken)> SignInWithCredentialsAsync(string email, string password);
Task<(User User, RefreshToken RefreshToken)> SignInWithRefreshTokenAsync(string refreshToken, string expiredToken);
Task> ListPaginateAsync(UserFilter filter);
+ Task ChangePasswordAsync(string userId, string currentPassword, string newPassword);
}
diff --git a/src/Orion.Domain.Core/Services/UserService.cs b/src/Orion.Domain.Core/Services/UserService.cs
index 93a03f8..94dead1 100644
--- a/src/Orion.Domain.Core/Services/UserService.cs
+++ b/src/Orion.Domain.Core/Services/UserService.cs
@@ -29,7 +29,7 @@ public UserService(IUnitOfWork unitOfWork, IStringLocalizer reso
public async Task AddAsync(User user)
{
- await ValidateUser(user, validatePassword: true);
+ await ValidateUser(user);
user.Password = user.Password.ToSha512();
@@ -41,6 +41,11 @@ public async Task AddAsync(User user)
public async Task DeleteAsync(string publicId)
{
+ var user = await FindByIdAsync(publicId);
+
+ if (user == null)
+ throw new NotFoundException(publicId);
+
await _unitOfWork.UserRepository.DeleteAsync(publicId);
await _unitOfWork.CommitAsync();
}
@@ -67,10 +72,11 @@ public async Task UpdateAsync(User user)
{
var entitySaved = await FindByIdAsync(user.PublicId);
- await ValidateUser(user, validatePassword: false);
+ await ValidateUser(user);
entitySaved.Email = user.Email;
entitySaved.Name = user.Name;
+ entitySaved.Profile = user.Profile;
_unitOfWork.UserRepository.Update(entitySaved);
@@ -142,11 +148,8 @@ private string GetClaimFromJwtToken(string jtwToken, string claimName)
}
}
- private async Task ValidateUser(User user, bool validatePassword)
+ private async Task ValidateUser(User user)
{
- if (string.IsNullOrEmpty(user.Password) && validatePassword)
- throw new BusinessException(_messages[UserMessages.EmptyPasword]);
-
var userFound = await _unitOfWork.UserRepository.FindByEmailAsync(user.Email);
if (userFound != null && userFound.PublicId != user.PublicId)
@@ -157,4 +160,21 @@ public async Task> ListPaginateAsync(UserFilter filter)
{
return await _unitOfWork.UserRepository.ListPaginateAsync(filter);
}
+
+ public async Task ChangePasswordAsync(string userId, string currentPassword, string newPassword)
+ {
+ var user = await FindByIdAsync(userId);
+
+ if (user == null)
+ throw new NotFoundException(userId);
+
+ if(user.Password != currentPassword.ToSha512())
+ throw new BusinessException(_messages[UserMessages.InvalidPassword]);
+
+ user.Password = newPassword.ToSha512();
+
+ _unitOfWork.UserRepository.Update(user);
+
+ await _unitOfWork.CommitAsync();
+ }
}
diff --git a/src/Orion.Infra.Data/Context/DataContext.cs b/src/Orion.Infra.Data/Context/DataContext.cs
index 53920ff..5c36227 100644
--- a/src/Orion.Infra.Data/Context/DataContext.cs
+++ b/src/Orion.Infra.Data/Context/DataContext.cs
@@ -17,11 +17,6 @@ public DataContext(IConfiguration configuration) : base(GetDefaultOptions(config
}
- public DataContext(DbContextOptions options) : base(options)
- {
-
- }
-
#region DBSet
public DbSet Users { get; set; }
public DbSet RefreshTokens { get; set; }
@@ -29,7 +24,7 @@ public DataContext(DbContextOptions options) : base(options)
private static DbContextOptions GetDefaultOptions(IConfiguration configuration)
{
- var connectionString = configuration.GetSection("DatabaseOptions:ConnectionString").Value;
+ var connectionString = configuration.GetSection("ConnectionStrings:OrionDatabase").Value;
return new DbContextOptionsBuilder().UseSqlServer(connectionString).Options;
}
diff --git a/src/Orion.Infra.Data/Migrations/20231030205851_Initial.cs b/src/Orion.Infra.Data/Migrations/20231030205851_Initial.cs
index 0491516..509afb2 100644
--- a/src/Orion.Infra.Data/Migrations/20231030205851_Initial.cs
+++ b/src/Orion.Infra.Data/Migrations/20231030205851_Initial.cs
@@ -1,11 +1,12 @@
using System;
+using System.Diagnostics.CodeAnalysis;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Orion.Infra.Data.Migrations
{
- ///
+ [ExcludeFromCodeCoverage]
public partial class Initial : Migration
{
///
diff --git a/src/Orion.Infra.Data/Migrations/DataContextModelSnapshot.cs b/src/Orion.Infra.Data/Migrations/DataContextModelSnapshot.cs
index a8f1907..0acb3da 100644
--- a/src/Orion.Infra.Data/Migrations/DataContextModelSnapshot.cs
+++ b/src/Orion.Infra.Data/Migrations/DataContextModelSnapshot.cs
@@ -1,15 +1,15 @@
//
-using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
-using Microsoft.EntityFrameworkCore.Metadata;
-using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Orion.Infra.Data.Context;
+using System;
+using System.Diagnostics.CodeAnalysis;
#nullable disable
namespace Orion.Infra.Data.Migrations
{
+ [ExcludeFromCodeCoverage]
[DbContext(typeof(DataContext))]
partial class DataContextModelSnapshot : ModelSnapshot
{
diff --git a/src/Orion.Infra.Data/Repository/Generic/BaseEntityRepository.cs b/src/Orion.Infra.Data/Repository/Generic/BaseEntityRepository.cs
index c3a147c..d45c5ff 100644
--- a/src/Orion.Infra.Data/Repository/Generic/BaseEntityRepository.cs
+++ b/src/Orion.Infra.Data/Repository/Generic/BaseEntityRepository.cs
@@ -1,13 +1,11 @@
using Microsoft.EntityFrameworkCore;
+using Orion.Domain.Core.Entities;
+using Orion.Infra.Data.Context;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Threading.Tasks;
-using Orion.Domain.Core.Entities;
-using Orion.Domain.Core.Filters;
-using Orion.Domain.Core.ValueObjects.Pagination;
-using Orion.Infra.Data.Context;
namespace Orion.Infra.Data.Repository.Generic;
diff --git a/src/Orion.Infra.Data/UnitOfWork/UnitOfWork.cs b/src/Orion.Infra.Data/UnitOfWork/UnitOfWork.cs
index 8b2bea1..f531b41 100644
--- a/src/Orion.Infra.Data/UnitOfWork/UnitOfWork.cs
+++ b/src/Orion.Infra.Data/UnitOfWork/UnitOfWork.cs
@@ -16,12 +16,7 @@ public class UnitOfWork : IUnitOfWork
public UnitOfWork(IConfiguration configuration)
{
- DbContext = new DataContext(GetOptions(configuration.GetSection("DatabaseOptions:ConnectionString").Value));
- }
-
- public UnitOfWork(DbContextOptions dbContextOptions)
- {
- DbContext = new DataContext(dbContextOptions);
+ DbContext = new DataContext(configuration);
}
private IUserRepository _userRepository;
@@ -34,12 +29,7 @@ public async Task CommitAsync()
{
await DbContext.SaveChangesAsync();
}
-
- private static DbContextOptions GetOptions(string connection)
- {
- return SqlServerDbContextOptionsExtensions.UseSqlServer(new DbContextOptionsBuilder(), connection).Options;
- }
-
+
public void DiscardChanges()
{
foreach (var entry in DbContext.ChangeTracker.Entries().Where(e => e.State != EntityState.Unchanged))
diff --git a/tests/Orion.Test/Api/V1/AuthApiTest.cs b/tests/Orion.Test/Api/V1/AuthApiTest.cs
new file mode 100644
index 0000000..2298822
--- /dev/null
+++ b/tests/Orion.Test/Api/V1/AuthApiTest.cs
@@ -0,0 +1,99 @@
+using Orion.Application.Core.Commands.UserCreate;
+using Orion.Test.Configuration.Faker;
+using Orion.Test.Integration.Setup;
+using System.Net;
+using System.Threading.Tasks;
+using Orion.Application.Core.Commands.LoginWithCredentials;
+using Orion.Application.Core.Commands.LoginWithRefreshToken;
+using Xunit;
+
+namespace Orion.Test.Api.V1
+{
+ public class AuthApiTest : IntegrationTestsBootstrapper
+ {
+ public AuthApiTest(IntegrationTestsFixture fixture) : base(fixture)
+ {
+ LoginWithDefaultUser();
+ }
+
+ [Theory]
+ [InlineData("invalid@email.com", "1233")]
+ [InlineData(null, "")]
+ [InlineData(null, "passs-invalid")]
+ public async Task AuthUser_WithCredentialsInvalid_ReturnsUnauthorized(string email, string password)
+ {
+ //arrange
+ var request = new LoginWithCredentialsRequest()
+ {
+ Email = email,
+ Password = password
+ };
+
+ //act
+ var httpResponse = await HttpClient.PostAsync("/api/Auth/Login", GetStringContent(request));
+
+ //assert
+ Assert.Equal(HttpStatusCode.Unauthorized, httpResponse.StatusCode);
+ }
+
+ [Theory]
+ [InlineData("invalid-refresh-token", "1233")]
+ [InlineData(null, "")]
+ [InlineData(null, "invalid-expired-token")]
+ public async Task AuthUser_WithRefreshTokenInvalid_ReturnsUnauthorized(string refreshToken, string token)
+ {
+ //arrange
+ var request = new LoginWithRefreshTokenRequest()
+ {
+ RefreshToken = refreshToken,
+ Token = token
+ };
+
+ //act
+ var httpResponse = await HttpClient.PostAsync("/api/Auth/RefreshToken", GetStringContent(request));
+
+ //assert
+ Assert.Equal(HttpStatusCode.Unauthorized, httpResponse.StatusCode);
+ }
+
+ [Fact]
+ public async Task AuthUser_WithValidRefreshToken_ReturnsNewToken()
+ {
+ //arrange
+ var user = UserFaker.GetUserCreateRequest();
+
+ var userCreated = await CreateUserAsync(user);
+
+ var httpClient = IntegrationTestsFixture.GetNewHttpClient();
+
+ var tokenResult = AuthUser(userCreated.Email, user.Password);
+
+ var refreshTokenRequest = new LoginWithRefreshTokenRequest
+ {
+ RefreshToken = tokenResult.RefreshToken,
+ Token = tokenResult.Token
+ };
+
+ //act
+ var httpResponseRefreshToken = await httpClient.PostAsync("/api/Auth/RefreshToken", GetStringContent(refreshTokenRequest));
+
+ var refreshTokenResponse = await GetResultContentAsync(httpResponseRefreshToken);
+
+ //assert
+ Assert.Equal(HttpStatusCode.OK, httpResponseRefreshToken.StatusCode);
+ Assert.NotNull(refreshTokenResponse.Token);
+ Assert.NotNull(refreshTokenResponse.RefreshToken);
+ }
+
+ private async Task CreateUserAsync(UserCreateRequest userCreateRequest = null)
+ {
+ userCreateRequest ??= UserFaker.GetUserCreateRequest();
+
+ var httpResponsePost = await AuthenticatedHttpClient.PostAsync("/api/Users", GetStringContent(userCreateRequest));
+
+ Assert.Equal(HttpStatusCode.Created, httpResponsePost.StatusCode);
+
+ return await GetResultContentAsync(httpResponsePost);
+ }
+ }
+}
diff --git a/tests/Orion.Test/Api/V1/UsersApiTest.cs b/tests/Orion.Test/Api/V1/UsersApiTest.cs
new file mode 100644
index 0000000..761707c
--- /dev/null
+++ b/tests/Orion.Test/Api/V1/UsersApiTest.cs
@@ -0,0 +1,158 @@
+using Orion.Application.Core.Commands.UserCreate;
+using Orion.Test.Configuration.Faker;
+using Orion.Test.Integration.Setup;
+using System;
+using System.Net;
+using System.Net.Http.Headers;
+using System.Threading.Tasks;
+using Orion.Application.Core.Commands.UserChangePassword;
+using Xunit;
+
+namespace Orion.Test.Api.V1
+{
+ public class UsersApiTest : IntegrationTestsBootstrapper
+ {
+ public UsersApiTest(IntegrationTestsFixture fixture) : base(fixture)
+ {
+ LoginWithDefaultUser();
+ }
+
+ [Fact]
+ public async Task PostUser_WithValidData_CreateAUser()
+ {
+ //arrange
+ var request = UserFaker.GetUserCreateRequest();
+
+ //act
+ var httpResponse = await AuthenticatedHttpClient.PostAsync("/api/Users", GetStringContent(request));
+
+ var userCreated = await GetResultContentAsync(httpResponse);
+
+ //assert
+ Assert.Equal(HttpStatusCode.Created, httpResponse.StatusCode);
+
+ Assert.NotNull(userCreated);
+ Assert.Equal(request.Email, userCreated.Email);
+ Assert.Equal(request.Name, userCreated.Name);
+ Assert.Equal(request.Profile, userCreated.Profile);
+ Assert.True(userCreated.CreatedAt > DateTime.MinValue);
+ Assert.True(userCreated.LastUpdated > DateTime.MinValue);
+ Assert.NotNull(userCreated.PublicId);
+ }
+
+ [Fact]
+ public async Task PutUser_WithValidData_UpdateUser()
+ {
+ //arrange
+ var userCreated = await CreateUserAsync();
+
+ var userUpdateRequest = UserFaker.GetUserUpdateRequest();
+ userUpdateRequest.PublicId = userCreated.PublicId;
+
+ //act
+ var httpResponsePut = await AuthenticatedHttpClient.PutAsync($"/api/Users/{userCreated.PublicId}", GetStringContent(userUpdateRequest));
+
+ Assert.Equal(HttpStatusCode.Accepted, httpResponsePut.StatusCode);
+
+ var userGetHttpResponse = await AuthenticatedHttpClient.GetAsync($"/api/Users/{userCreated.PublicId}");
+
+ var user = await GetResultContentAsync(userGetHttpResponse);
+
+ //assert
+ Assert.NotNull(user);
+ Assert.Equal(userUpdateRequest.Email, user.Email);
+ Assert.Equal(userUpdateRequest.Name, user.Name);
+ Assert.Equal(userUpdateRequest.Profile, user.Profile);
+ Assert.True(user.LastUpdated > userCreated.LastUpdated);
+ Assert.NotNull(user.PublicId);
+ }
+
+ [Fact]
+ public async Task DeleteUser_WithValidIdData_DeleteTheUser()
+ {
+ //arrange
+ var userCreated = await CreateUserAsync();
+
+ //act
+ var httpResponsePut = await AuthenticatedHttpClient.DeleteAsync($"/api/Users/{userCreated.PublicId}");
+
+ Assert.Equal(HttpStatusCode.NoContent, httpResponsePut.StatusCode);
+
+ var userGetHttpResponse = await AuthenticatedHttpClient.GetAsync($"/api/Users/{userCreated.PublicId}");
+
+ //assert
+ Assert.Equal(HttpStatusCode.NotFound, userGetHttpResponse.StatusCode);
+ }
+
+ [Fact]
+ public async Task DeleteUser_WithInvalidId_ReturnsNotFound()
+ {
+ //arrange & act
+ var httpResponseDelete = await AuthenticatedHttpClient.DeleteAsync($"/api/Users/{Guid.NewGuid()}");
+
+ //assert
+ Assert.Equal(HttpStatusCode.NotFound, httpResponseDelete.StatusCode);
+ }
+
+ [Theory]
+ [InlineData("123", "12345", "30298302")]
+ [InlineData("", "12345", "30298302")]
+ [InlineData("", "12345", null)]
+ [InlineData("", "", null)]
+ [InlineData("123", null, null)]
+ public async Task UpdateUserPassword_WithInvalidPasswordsAndConfirmation_ReturnsBadRequest(string currentPass, string newPass, string newPassConfirm)
+ {
+ //arrange
+ var changePasswordRequest = new UserChangePasswordRequest
+ {
+ CurrentPassword = currentPass,
+ NewPassword = newPass,
+ NewPasswordConfirm = newPassConfirm
+ };
+
+ //act
+ var httpResponsePatch = await AuthenticatedHttpClient.PatchAsync("/api/Users/Me/PasswordChange", GetStringContent(changePasswordRequest));
+
+ Assert.Equal(HttpStatusCode.BadRequest, httpResponsePatch.StatusCode);
+ }
+
+ [Fact]
+ public async Task UpdateUserPassword_WithValidPasswordsAndConfirmation_ReturnsAcepted()
+ {
+ //arrange
+ var user = UserFaker.GetUserCreateRequest();
+
+ var userCreated = await CreateUserAsync(user);
+
+ var changePasswordRequest = new UserChangePasswordRequest
+ {
+ CurrentPassword = user.Password,
+ NewPassword = "Ab647477382",
+ NewPasswordConfirm = "Ab647477382"
+ };
+
+ var httpClient = IntegrationTestsFixture.GetNewHttpClient();
+
+ var tokenResult = AuthUser(userCreated.Email, user.Password);
+
+ httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", tokenResult.Token);
+
+ //act
+ var httpResponsePatch = await httpClient.PatchAsync("/api/Users/Me/PasswordChange", GetStringContent(changePasswordRequest));
+
+ //assert
+ Assert.Equal(HttpStatusCode.Accepted, httpResponsePatch.StatusCode);
+ }
+
+ private async Task CreateUserAsync(UserCreateRequest userCreateRequest = null)
+ {
+ userCreateRequest ??= UserFaker.GetUserCreateRequest();
+
+ var httpResponsePost = await AuthenticatedHttpClient.PostAsync("/api/Users", GetStringContent(userCreateRequest));
+
+ Assert.Equal(HttpStatusCode.Created, httpResponsePost.StatusCode);
+
+ return await GetResultContentAsync(httpResponsePost);
+ }
+ }
+}
diff --git a/tests/Orion.Test/Configuration/ApiTestInitializer.cs b/tests/Orion.Test/Configuration/ApiTestInitializer.cs
deleted file mode 100644
index f6c2ec5..0000000
--- a/tests/Orion.Test/Configuration/ApiTestInitializer.cs
+++ /dev/null
@@ -1,77 +0,0 @@
-using Microsoft.AspNetCore.Hosting;
-using Microsoft.AspNetCore.Mvc.Testing;
-using Microsoft.Extensions.Configuration;
-using Newtonsoft.Json;
-using Orion.Api.Models;
-using System;
-using System.Net.Http;
-using System.Text;
-
-namespace Orion.Test.Configuration;
-
-public abstract class ApiTestInitializer : IDisposable
-{
- protected string AuthToken;
- protected readonly HttpClient Client;
- protected readonly HttpClient AuthenticatedClient;
- protected IServiceProvider ServiceProvider { get; private set; }
-
- public ApiTestInitializer()
- {
- var appFactory = new WebApplicationFactory()
- .WithWebHostBuilder(builder =>
- {
- var config = new ConfigurationBuilder()
- .AddJsonFile("appsettings.Test.json", optional: false, reloadOnChange: true)
- .Build();
-
- builder
- .UseConfiguration(config);
- });
- ServiceProvider = appFactory.Services;
- Client = appFactory.CreateClient();
- AuthenticatedClient = appFactory.CreateClient();
- }
-
- public void AuthUser()
- {
- var result = Client.PostAsync("/api/Auth/Login", GetStringContent(
- new UserLoginModel {
- Email = "vanderlan.gs@gmail.com",
- Password = "123"
- }))
- .GetAwaiter().GetResult();
-
- var content = result.Content.ReadAsStringAsync().GetAwaiter().GetResult();
-
- var tokenResult = JsonConvert.DeserializeObject(content);
-
- AuthToken = tokenResult.Token;
- }
-
- protected static StringContent GetStringContent(object obj)
- {
- return new StringContent(JsonConvert.SerializeObject(obj), Encoding.Default, "application/json");
- }
-
- private bool _disposedValue = false;
-
- protected virtual void Dispose(bool disposing)
- {
- if (!_disposedValue)
- {
- _disposedValue = true;
- }
- }
-
- ~ApiTestInitializer()
- {
- Dispose(false);
- }
-
- public void Dispose()
- {
- Dispose(true);
- GC.SuppressFinalize(this);
- }
-}
diff --git a/tests/Orion.Test/Configuration/DependencyInjectionSetupFixture.cs b/tests/Orion.Test/Configuration/DependencyInjectionSetupFixture.cs
deleted file mode 100644
index 84fdf18..0000000
--- a/tests/Orion.Test/Configuration/DependencyInjectionSetupFixture.cs
+++ /dev/null
@@ -1,24 +0,0 @@
-using Microsoft.Extensions.DependencyInjection;
-using Orion.Infra.Data.UnitOfWork;
-using Orion.Domain.Core.Repositories.UnitOfWork;
-using Orion.Croscutting.Ioc.Dependencies;
-
-namespace Orion.Test.Configuration;
-
-public class DependencyInjectionSetupFixture
-{
- public ServiceProvider ServiceProvider { get; private set; }
-
- public DependencyInjectionSetupFixture()
- {
- var serviceCollection = new ServiceCollection();
-
- serviceCollection.AddDomainServices();
-
- serviceCollection.AddTransient(s => new UnitOfWork(TestBootstrapper.GetInMemoryDbContextOptions()));
- serviceCollection.AddLogging();
- serviceCollection.AddLocalization(options => options.ResourcesPath = @"Resources");
-
- ServiceProvider = serviceCollection.BuildServiceProvider();
- }
-}
diff --git a/tests/Orion.Test/Faker/RefreshTokenFaker.cs b/tests/Orion.Test/Configuration/Faker/RefreshTokenFaker.cs
similarity index 91%
rename from tests/Orion.Test/Faker/RefreshTokenFaker.cs
rename to tests/Orion.Test/Configuration/Faker/RefreshTokenFaker.cs
index 56d1689..cca185d 100644
--- a/tests/Orion.Test/Faker/RefreshTokenFaker.cs
+++ b/tests/Orion.Test/Configuration/Faker/RefreshTokenFaker.cs
@@ -2,7 +2,7 @@
using Bogus;
using Orion.Domain.Core.Entities;
-namespace Orion.Test.Faker;
+namespace Orion.Test.Configuration.Faker;
public static class RefreshTokenFaker
{
diff --git a/tests/Orion.Test/Faker/UserFaker.cs b/tests/Orion.Test/Configuration/Faker/UserFaker.cs
similarity index 72%
rename from tests/Orion.Test/Faker/UserFaker.cs
rename to tests/Orion.Test/Configuration/Faker/UserFaker.cs
index 0051e60..ae8ff4d 100644
--- a/tests/Orion.Test/Faker/UserFaker.cs
+++ b/tests/Orion.Test/Configuration/Faker/UserFaker.cs
@@ -4,11 +4,23 @@
using Orion.Application.Core.Commands.UserUpdate;
using Orion.Domain.Core.Entities;
using Orion.Domain.Core.Entities.Enuns;
+using Orion.Domain.Core.Extensions;
-namespace Orion.Test.Faker;
+namespace Orion.Test.Configuration.Faker;
public static class UserFaker
{
+ public static User GetDefaultSystemUser()
+ {
+ return new Faker()
+ .RuleFor(o => o.Name, f => f.Name.FullName())
+ .RuleFor(o => o.Email, f => f.Internet.Email())
+ .RuleFor(o => o.Password, f => "123".ToSha512())
+ .RuleFor(o => o.Profile, f => UserProfile.Admin)
+ .RuleFor(o => o.PublicId, f => Guid.NewGuid().ToString())
+ .Generate();
+ }
+
public static User Get()
{
return new Faker()
@@ -29,7 +41,7 @@ public static UserCreateRequest GetUserCreateRequest()
.RuleFor(o => o.Profile, f => UserProfile.Admin)
.Generate();
}
-
+
public static UserUpdateRequest GetUserUpdateRequest()
{
return new Faker()
diff --git a/tests/Orion.Test/Configuration/TestBoostrapper.cs b/tests/Orion.Test/Configuration/TestBoostrapper.cs
deleted file mode 100644
index 5f061f7..0000000
--- a/tests/Orion.Test/Configuration/TestBoostrapper.cs
+++ /dev/null
@@ -1,16 +0,0 @@
-using Microsoft.EntityFrameworkCore;
-using Orion.Infra.Data.Context;
-
-namespace Orion.Test.Configuration;
-
-public class TestBootstrapper
-{
- public static DbContextOptions GetInMemoryDbContextOptions(string dbName = "Test_DB")
- {
- var context = new DbContextOptionsBuilder()
- .UseInMemoryDatabase(databaseName: dbName)
- .EnableDetailedErrors(true);
-
- return context.Options;
- }
-}
diff --git a/tests/Orion.Test/Domain/Services/BaseService/BaseServiceTest.cs b/tests/Orion.Test/Domain/Services/BaseService/BaseServiceTest.cs
deleted file mode 100644
index 526443e..0000000
--- a/tests/Orion.Test/Domain/Services/BaseService/BaseServiceTest.cs
+++ /dev/null
@@ -1,15 +0,0 @@
-using Microsoft.Extensions.DependencyInjection;
-using Orion.Test.Configuration;
-using Xunit;
-
-namespace Orion.Test.Domain.Services.BaseService;
-
-public class BaseServiceTest : IClassFixture
-{
- protected readonly ServiceProvider ServiceProvider;
-
- public BaseServiceTest(DependencyInjectionSetupFixture fixture)
- {
- ServiceProvider = fixture.ServiceProvider;
- }
-}
diff --git a/tests/Orion.Test/Domain/Services/UserServiceTest.cs b/tests/Orion.Test/Integration/Domain/Services/UserServiceTest.cs
similarity index 90%
rename from tests/Orion.Test/Domain/Services/UserServiceTest.cs
rename to tests/Orion.Test/Integration/Domain/Services/UserServiceTest.cs
index dd578b5..dd3f5b5 100644
--- a/tests/Orion.Test/Domain/Services/UserServiceTest.cs
+++ b/tests/Orion.Test/Integration/Domain/Services/UserServiceTest.cs
@@ -1,28 +1,23 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Localization;
-using Orion.Api.Configuration;
-using Orion.Domain.Core.Entities;
+using Orion.Croscutting.Resources;
using Orion.Domain.Core.Exceptions;
using Orion.Domain.Core.Extensions;
+using Orion.Domain.Core.Filters;
using Orion.Domain.Core.Services.Interfaces;
-using Orion.Croscutting.Resources;
-using Orion.Test.Configuration;
-using Orion.Test.Domain.Services.BaseService;
-using System;
+using Orion.Test.Configuration.Faker;
+using Orion.Test.Integration.Setup;
using System.Collections.Generic;
using System.Threading.Tasks;
-using Orion.Application.Core.Commands.LoginWithCredentials;
-using Orion.Domain.Core.Filters;
-using Orion.Test.Faker;
using Xunit;
using static Orion.Croscutting.Resources.Messages.MessagesKeys;
-namespace Orion.Test.Domain.Services;
+namespace Orion.Test.Integration.Domain.Services;
-public class UserServiceTest : BaseServiceTest
+public class UserServiceTest : IntegrationTestsBootstrapper
{
- public UserServiceTest(DependencyInjectionSetupFixture fixture) : base(fixture)
+ public UserServiceTest(IntegrationTestsFixture fixture) : base(fixture)
{
}
@@ -96,20 +91,6 @@ public async Task ListPaginateAsync_WithFilterByName_GetAllMatchedUsers()
await userService.DeleteAsync(userFound.PublicId);
}
- [Fact]
- public async Task AddAsync_WithInvalidData_ThrowsBusinessException()
- {
- //arrange
- using var scope = ServiceProvider.CreateScope();
- var userService = scope.ServiceProvider.GetService();
-
- var user = UserFaker.Get();
- user.Password = null;
-
- //act & assert
- await Assert.ThrowsAsync(() => userService.AddAsync(user));
- }
-
[Fact]
public async Task DeleteAsync_WithExistantId_RemoveUserAsSuccess()
{
@@ -221,7 +202,7 @@ public async Task SignInWithRefreshTokenAsync_WithInvalidToken_ThrowsUnauthorize
var userFound = await userService.FindByIdAsync(userAdded.PublicId);
Assert.NotNull(userFound);
-
+
//act
var exeption = await Assert.ThrowsAsync(() => userService.SignInWithRefreshTokenAsync(refreshToken, token));
diff --git a/tests/Orion.Test/Integration/Setup/IntegrationTestsBootstrapper.cs b/tests/Orion.Test/Integration/Setup/IntegrationTestsBootstrapper.cs
new file mode 100644
index 0000000..7fe2c08
--- /dev/null
+++ b/tests/Orion.Test/Integration/Setup/IntegrationTestsBootstrapper.cs
@@ -0,0 +1,86 @@
+using Newtonsoft.Json;
+using Orion.Api.Models;
+using Orion.Domain.Core.Entities;
+using System;
+using System.Net.Http;
+using System.Net.Http.Headers;
+using System.Text;
+using System.Threading.Tasks;
+using Orion.Application.Core.Commands.LoginWithCredentials;
+using Xunit;
+
+namespace Orion.Test.Integration.Setup;
+
+public abstract class IntegrationTestsBootstrapper : IClassFixture, IDisposable
+{
+ protected readonly HttpClient HttpClient;
+ protected readonly HttpClient AuthenticatedHttpClient;
+ private readonly User _defaultSystemUser;
+ protected readonly IntegrationTestsFixture IntegrationTestsFixture;
+
+ protected IServiceProvider ServiceProvider { get; private set; }
+
+ protected IntegrationTestsBootstrapper(IntegrationTestsFixture fixture)
+ {
+ HttpClient = fixture.HttpClient;
+ AuthenticatedHttpClient = fixture.AuthenticatedHttpClient;
+ _defaultSystemUser = fixture.DefaultSystemUser;
+ ServiceProvider = fixture.ServiceProvider;
+ IntegrationTestsFixture = fixture;
+ }
+
+ protected void LoginWithDefaultUser()
+ {
+ var tokenResult = AuthUser(_defaultSystemUser.Email, "123");
+
+ AuthenticatedHttpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", tokenResult.Token);
+ }
+
+ protected UserApiTokenModel AuthUser(string email, string password)
+ {
+ var result = HttpClient.PostAsync("/api/Auth/Login", GetStringContent(
+ new LoginWithCredentialsRequest
+ {
+ Email = email,
+ Password = password
+ }))
+ .GetAwaiter().GetResult();
+
+ var content = result.Content.ReadAsStringAsync().GetAwaiter().GetResult();
+
+ return JsonConvert.DeserializeObject(content);
+ }
+
+ protected static StringContent GetStringContent(object obj)
+ {
+ return new StringContent(JsonConvert.SerializeObject(obj), Encoding.Default, "application/json");
+ }
+
+ private bool _disposedValue;
+
+ private void Dispose(bool disposing)
+ {
+ if (!_disposedValue)
+ {
+ _disposedValue = true;
+ }
+ }
+
+ ~IntegrationTestsBootstrapper()
+ {
+ Dispose(false);
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ protected static async Task GetResultContentAsync(HttpResponseMessage httpResponse)
+ {
+ var content = await httpResponse.Content.ReadAsStringAsync();
+
+ return JsonConvert.DeserializeObject(content);
+ }
+}
diff --git a/tests/Orion.Test/Integration/Setup/IntegrationTestsFixture.cs b/tests/Orion.Test/Integration/Setup/IntegrationTestsFixture.cs
new file mode 100644
index 0000000..1d9d358
--- /dev/null
+++ b/tests/Orion.Test/Integration/Setup/IntegrationTestsFixture.cs
@@ -0,0 +1,86 @@
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.Mvc.Testing;
+using Microsoft.Data.SqlClient;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Orion.Domain.Core.Entities;
+using Orion.Domain.Core.Repositories.UnitOfWork;
+using Orion.Test.Configuration.Faker;
+using System;
+using System.Net.Http;
+
+namespace Orion.Test.Integration.Setup
+{
+ public class IntegrationTestsFixture
+ {
+ public readonly HttpClient HttpClient;
+ public readonly HttpClient AuthenticatedHttpClient;
+ public readonly User DefaultSystemUser;
+ public readonly IServiceProvider ServiceProvider;
+ private readonly IUnitOfWork _unitOfWork;
+ private readonly SqlConnection _sqlConnection;
+ private IConfiguration _configuration;
+ private WebApplicationFactory AppFactory;
+
+ public IntegrationTestsFixture()
+ {
+ var appFactory = new WebApplicationFactory()
+ .WithWebHostBuilder(builder =>
+ {
+ var config = new ConfigurationBuilder()
+ .AddJsonFile("appsettings.Test.json", optional: false, reloadOnChange: true)
+ .Build();
+
+ builder.UseConfiguration(config);
+
+ _configuration = config;
+ });
+
+ ServiceProvider = appFactory.Services;
+
+ _unitOfWork = appFactory.Services.GetRequiredService();
+
+ HttpClient = appFactory.CreateClient();
+ AuthenticatedHttpClient = appFactory.CreateClient();
+
+ DefaultSystemUser = UserFaker.GetDefaultSystemUser();
+
+ _sqlConnection = new SqlConnection(_configuration["ConnectionStrings:OrionDatabase"]);
+
+ AppFactory = appFactory;
+
+ BeforeEachTest();
+ }
+
+ public HttpClient GetNewHttpClient()
+ {
+ return AppFactory.CreateClient();
+ }
+
+ private void BeforeEachTest()
+ {
+ var tablesToTruncate = new[] { "User", "RefreshToken" };
+
+ lock (_sqlConnection)
+ {
+ using (_sqlConnection)
+ {
+ _sqlConnection.Open();
+
+ foreach (var table in tablesToTruncate)
+ {
+ var command = new SqlCommand($"TRUNCATE TABLE dbo.[{table}]", _sqlConnection);
+
+ command.ExecuteNonQuery();
+ }
+
+ lock (_unitOfWork)
+ {
+ _unitOfWork.UserRepository.AddAsync(DefaultSystemUser).GetAwaiter().GetResult();
+ _unitOfWork.CommitAsync().GetAwaiter().GetResult();
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/tests/Orion.Test/Orion.Test.csproj b/tests/Orion.Test/Orion.Test.csproj
index 4237770..f07d28e 100644
--- a/tests/Orion.Test/Orion.Test.csproj
+++ b/tests/Orion.Test/Orion.Test.csproj
@@ -5,30 +5,21 @@
false
-
-
-
-
-
-
runtime; build; native; contentfiles; analyzers; buildtransitive
all
-
-
-
-
-
+
+
+
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
diff --git a/tests/Orion.Test/Api/Controllers/AuthControllerTest.cs b/tests/Orion.Test/Unit/Controllers/AuthControllerTest.cs
similarity index 89%
rename from tests/Orion.Test/Api/Controllers/AuthControllerTest.cs
rename to tests/Orion.Test/Unit/Controllers/AuthControllerTest.cs
index 730d356..b3dc2c2 100644
--- a/tests/Orion.Test/Api/Controllers/AuthControllerTest.cs
+++ b/tests/Orion.Test/Unit/Controllers/AuthControllerTest.cs
@@ -9,15 +9,15 @@
using Orion.Application.Core.Commands.LoginWithRefreshToken;
using Orion.Domain.Core.Entities;
using Orion.Domain.Core.Entities.Enuns;
-using Orion.Test.Api.Controllers.BaseController;
-using Orion.Test.Faker;
+using Orion.Test.Configuration.Faker;
+using Orion.Test.Unit.Controllers.BaseController;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Xunit;
-namespace Orion.Test.Api.Controllers;
+namespace Orion.Test.Unit.Controllers;
public class AuthControllerTest : BaseControllerTest
{
@@ -80,12 +80,12 @@ public async Task RefreshToken_WithValidRefreshToken_ReturnsNewToken()
{
//arrange & act
var (token, _) = AuthenticationConfiguration.CreateToken(new LoginWithCredentialsResponse
- {
- Email = _validUser.Email,
- Name = _validUser.Name,
- PublicId = _validUser.PublicId,
- Profile = UserProfile.Admin
- },
+ {
+ Email = _validUser.Email,
+ Name = _validUser.Name,
+ PublicId = _validUser.PublicId,
+ Profile = UserProfile.Admin
+ },
_configuration);
var result = await _authController.RefreshToken(new LoginWithRefreshTokenRequest { RefreshToken = _validRefreshToken.Refreshtoken, Token = token });
diff --git a/tests/Orion.Test/Api/Controllers/BaseController/BaseControllerTest.cs b/tests/Orion.Test/Unit/Controllers/BaseController/BaseControllerTest.cs
similarity index 56%
rename from tests/Orion.Test/Api/Controllers/BaseController/BaseControllerTest.cs
rename to tests/Orion.Test/Unit/Controllers/BaseController/BaseControllerTest.cs
index eb29efd..1d09df9 100644
--- a/tests/Orion.Test/Api/Controllers/BaseController/BaseControllerTest.cs
+++ b/tests/Orion.Test/Unit/Controllers/BaseController/BaseControllerTest.cs
@@ -1,9 +1,9 @@
-namespace Orion.Test.Api.Controllers.BaseController;
+namespace Orion.Test.Unit.Controllers.BaseController;
public class BaseControllerTest
{
public BaseControllerTest()
{
-
+
}
}
diff --git a/tests/Orion.Test/Api/Controllers/UsersControllerTest.cs b/tests/Orion.Test/Unit/Controllers/UsersControllerTest.cs
similarity index 92%
rename from tests/Orion.Test/Api/Controllers/UsersControllerTest.cs
rename to tests/Orion.Test/Unit/Controllers/UsersControllerTest.cs
index bdeb326..b8a0435 100644
--- a/tests/Orion.Test/Api/Controllers/UsersControllerTest.cs
+++ b/tests/Orion.Test/Unit/Controllers/UsersControllerTest.cs
@@ -6,14 +6,14 @@
using Orion.Application.Core.Queries.UserGetPaginated;
using Orion.Domain.Core.Entities;
using Orion.Domain.Core.ValueObjects.Pagination;
-using Orion.Test.Api.Controllers.BaseController;
-using Orion.Test.Faker;
+using Orion.Test.Configuration.Faker;
+using Orion.Test.Unit.Controllers.BaseController;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Xunit;
-namespace Orion.Test.Api.Controllers;
+namespace Orion.Test.Unit.Controllers;
public class UsersControllerTestTest : BaseControllerTest
{
@@ -110,11 +110,11 @@ private void SetupMediatorMock()
var userListPaginated = new PagedList(userList, 4);
- mediatorMock.Setup(x => x.Send(It.IsAny(),It.IsAny()))
+ mediatorMock.Setup(x => x.Send(It.IsAny(), It.IsAny()))
.ReturnsAsync(userListPaginated);
- mediatorMock.Setup(x => x.Send(It.IsAny(),It.IsAny()))
- .ReturnsAsync((UserGetByIdResponse) _validUser);
+ mediatorMock.Setup(x => x.Send(It.IsAny(), It.IsAny()))
+ .ReturnsAsync((UserGetByIdResponse)_validUser);
_usersController = new UsersController(mediatorMock.Object);
}