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; /// /// Integration tests for PUT /api/v1/users/me/password (UDT-008 B6). /// [Collection("ApiIntegration")] public sealed class ChangeMyPasswordEndpointTests : IAsyncLifetime { private const string TestConnectionString = "Server=TECNICA3;Database=SIGCM2_Test;User Id=desarrollo;Password=desarrollo2026;TrustServerCertificate=True;"; // 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 SeedUserAsync(string username) { await using var conn = new SqlConnection(TestConnectionString); await conn.OpenAsync(); return await conn.ExecuteScalarAsync($""" 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 GetTokenAsync(string username) { var response = await _client.PostAsJsonAsync("/api/v1/auth/login", new { username, password = "@Diego550@" }); response.EnsureSuccessStatusCode(); var json = await response.Content.ReadFromJsonAsync(); 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); } }