Files
SIG-CM2.0/tests/SIGCM2.Api.Tests/Usuarios/ResetPasswordEndpointTests.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

153 lines
6.0 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 POST /api/v1/users/{id}/password/reset (UDT-008 B7).
/// </summary>
[Collection("ApiIntegration")]
public sealed class ResetPasswordEndpointTests : IAsyncLifetime
{
private const string TestConnectionString = TestConnectionStrings.ApiTestDb;
private readonly HttpClient _client;
private readonly SqlTestFixture _db;
public ResetPasswordEndpointTests(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<string> GetAdminTokenAsync()
{
var response = await _client.PostAsJsonAsync("/api/v1/auth/login",
new { username = "admin", password = "@Diego550@" });
response.EnsureSuccessStatusCode();
var json = await response.Content.ReadFromJsonAsync<JsonElement>();
return json.GetProperty("accessToken").GetString()!;
}
private async Task<int> GetAdminIdAsync()
{
await using var conn = new SqlConnection(TestConnectionString);
await conn.OpenAsync();
return await conn.ExecuteScalarAsync<int>("SELECT Id FROM dbo.Usuario WHERE Username = 'admin'");
}
private async Task<int> SeedCajeroAsync(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}', '$2a$12$rmq6tlSAQ8WXhR2CwLCSeuwCJKz/.8Eab95UQCUNfwe4dokeOqMcW', 'Test', 'User', 'cajero', '[]', 1, 0);
SELECT Id FROM dbo.Usuario WHERE Username = '{username}'
""");
}
private async Task<string> GetCajeroTokenAsync()
{
await SeedCajeroAsync("cajero_reset_auth");
var response = await _client.PostAsJsonAsync("/api/v1/auth/login",
new { username = "cajero_reset_auth", password = "@Diego550@" });
response.EnsureSuccessStatusCode();
var json = await response.Content.ReadFromJsonAsync<JsonElement>();
return json.GetProperty("accessToken").GetString()!;
}
[Fact]
public async Task POST_Password_Reset_200_Returns_TempPassword()
{
var adminToken = await GetAdminTokenAsync();
var targetId = await SeedCajeroAsync("cajero_reset_happy");
var request = new HttpRequestMessage(HttpMethod.Post, $"/api/v1/users/{targetId}/password/reset");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", adminToken);
var response = await _client.SendAsync(request);
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var json = await response.Content.ReadFromJsonAsync<JsonElement>();
Assert.True(json.TryGetProperty("tempPassword", out var tempProp));
Assert.False(string.IsNullOrWhiteSpace(tempProp.GetString()));
}
[Fact]
public async Task POST_Password_Reset_TempPassword_Length_Gte_12()
{
var adminToken = await GetAdminTokenAsync();
var targetId = await SeedCajeroAsync("cajero_reset_length");
var request = new HttpRequestMessage(HttpMethod.Post, $"/api/v1/users/{targetId}/password/reset");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", adminToken);
var response = await _client.SendAsync(request);
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var json = await response.Content.ReadFromJsonAsync<JsonElement>();
var tempPassword = json.GetProperty("tempPassword").GetString()!;
Assert.True(tempPassword.Length >= 12, $"TempPassword too short: {tempPassword.Length}");
}
[Fact]
public async Task POST_Password_Reset_400_Cannot_Self_Reset()
{
var adminToken = await GetAdminTokenAsync();
var adminId = await GetAdminIdAsync();
var request = new HttpRequestMessage(HttpMethod.Post, $"/api/v1/users/{adminId}/password/reset");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", adminToken);
var response = await _client.SendAsync(request);
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
var body = await response.Content.ReadAsStringAsync();
Assert.Contains("cannot-self-reset", body, StringComparison.OrdinalIgnoreCase);
}
[Fact]
public async Task POST_Password_Reset_404_Target_Not_Found()
{
var adminToken = await GetAdminTokenAsync();
var request = new HttpRequestMessage(HttpMethod.Post, "/api/v1/users/9999/password/reset");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", adminToken);
var response = await _client.SendAsync(request);
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
[Fact]
public async Task POST_Password_Reset_401_No_Auth()
{
var response = await _client.SendAsync(new HttpRequestMessage(HttpMethod.Post, "/api/v1/users/1/password/reset"));
Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
}
[Fact]
public async Task POST_Password_Reset_403_No_Permission()
{
var cajeroToken = await GetCajeroTokenAsync();
var targetId = await SeedCajeroAsync("cajero_reset_403_target");
var request = new HttpRequestMessage(HttpMethod.Post, $"/api/v1/users/{targetId}/password/reset");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", cajeroToken);
var response = await _client.SendAsync(request);
Assert.Equal(HttpStatusCode.Forbidden, response.StatusCode);
}
}