Files
SIG-CM2.0/tests/SIGCM2.Api.Tests/Usuarios/ChangeMyPasswordEndpointTests.cs
dmolinari e5b6c06f64 refactor(tests): Api.Tests apunta a SIGCM2_Test_Api via TestConnectionStrings
Todos los archivos de Api.Tests reemplazan la connection string hardcodeada
por TestConnectionStrings.ApiTestDb. Cada proyecto de tests ahora tiene su
propia base de datos aislada, eliminando la contención entre Application.Tests
y Api.Tests que causaba flakiness.
2026-04-18 21:44:40 -03:00

130 lines
5.1 KiB
C#

using System.Net;
using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Text.Json;
using Dapper;
using Microsoft.Data.SqlClient;
using SIGCM2.TestSupport;
namespace SIGCM2.Api.Tests.Usuarios;
/// <summary>
/// Integration tests for PUT /api/v1/users/me/password (UDT-008 B6).
/// </summary>
[Collection("ApiIntegration")]
public sealed class ChangeMyPasswordEndpointTests : IAsyncLifetime
{
private const string TestConnectionString = TestConnectionStrings.ApiTestDb;
// This hash corresponds to "@Diego550@"
private const string DefaultHash = "$2a$12$rmq6tlSAQ8WXhR2CwLCSeuwCJKz/.8Eab95UQCUNfwe4dokeOqMcW";
private readonly HttpClient _client;
private readonly SqlTestFixture _db;
public ChangeMyPasswordEndpointTests(TestWebAppFactory factory)
{
_client = factory.CreateClient();
_db = new SqlTestFixture(TestConnectionString);
}
public async Task InitializeAsync() => await _db.InitializeAsync();
public async Task DisposeAsync() => await _db.DisposeAsync();
private async Task<int> SeedUserAsync(string username)
{
await using var conn = new SqlConnection(TestConnectionString);
await conn.OpenAsync();
return await conn.ExecuteScalarAsync<int>($"""
IF NOT EXISTS (SELECT 1 FROM dbo.Usuario WHERE Username = '{username}')
INSERT INTO dbo.Usuario (Username, PasswordHash, Nombre, Apellido, Rol, PermisosJson, Activo, MustChangePassword)
VALUES ('{username}', '{DefaultHash}', 'Test', 'User', 'cajero', '[]', 1, 0);
SELECT Id FROM dbo.Usuario WHERE Username = '{username}'
""");
}
private async Task<string> GetTokenAsync(string username)
{
var response = await _client.PostAsJsonAsync("/api/v1/auth/login",
new { username, password = "@Diego550@" });
response.EnsureSuccessStatusCode();
var json = await response.Content.ReadFromJsonAsync<JsonElement>();
return json.GetProperty("accessToken").GetString()!;
}
[Fact]
public async Task PUT_Me_Password_204_No_Content()
{
await SeedUserAsync("user_chpwd_happy");
var token = await GetTokenAsync("user_chpwd_happy");
var request = new HttpRequestMessage(HttpMethod.Put, "/api/v1/users/me/password");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
request.Content = JsonContent.Create(new { oldPassword = "@Diego550@", newPassword = "Nuevo1234!" });
var response = await _client.SendAsync(request);
Assert.Equal(HttpStatusCode.NoContent, response.StatusCode);
}
[Fact]
public async Task PUT_Me_Password_400_Wrong_Old_With_Error_Key()
{
await SeedUserAsync("user_chpwd_wrongold");
var token = await GetTokenAsync("user_chpwd_wrongold");
var request = new HttpRequestMessage(HttpMethod.Put, "/api/v1/users/me/password");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
request.Content = JsonContent.Create(new { oldPassword = "WrongPassword!", newPassword = "Nuevo1234!" });
var response = await _client.SendAsync(request);
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
var body = await response.Content.ReadAsStringAsync();
Assert.Contains("invalid-old-password", body, StringComparison.OrdinalIgnoreCase);
}
[Fact]
public async Task PUT_Me_Password_400_Weak_New_Password()
{
await SeedUserAsync("user_chpwd_weak");
var token = await GetTokenAsync("user_chpwd_weak");
var request = new HttpRequestMessage(HttpMethod.Put, "/api/v1/users/me/password");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
request.Content = JsonContent.Create(new { oldPassword = "@Diego550@", newPassword = "abc" }); // too short
var response = await _client.SendAsync(request);
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
}
[Fact]
public async Task PUT_Me_Password_401_No_Auth()
{
var request = new HttpRequestMessage(HttpMethod.Put, "/api/v1/users/me/password");
request.Content = JsonContent.Create(new { oldPassword = "@Diego550@", newPassword = "Nuevo1234!" });
var response = await _client.SendAsync(request);
Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
}
[Fact]
public async Task PUT_Me_Password_Does_NOT_Require_Users_Manage_Permission()
{
// Cajero user (no users:gestionar permission) should be able to change own password
await SeedUserAsync("cajero_chpwd");
var token = await GetTokenAsync("cajero_chpwd");
var request = new HttpRequestMessage(HttpMethod.Put, "/api/v1/users/me/password");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
request.Content = JsonContent.Create(new { oldPassword = "@Diego550@", newPassword = "Nuevo1234!" });
var response = await _client.SendAsync(request);
// Should succeed with 204, NOT 403
Assert.Equal(HttpStatusCode.NoContent, response.StatusCode);
}
}