require "rails_helper" RSpec.describe "Account API", type: :request do let!(:user) { User.create!(email: "account@example.com", name: "Account User", password: "Password123", role: "coach") } let(:auth_headers) do post "/api/v1/auth/login", params: { email: user.email, password: "Password123" } token = JSON.parse(response.body).fetch("access_token") { "Authorization" => "Bearer #{token}" } end describe "GET /api/v1/account" do it "returns the current user profile" do get "/api/v1/account", headers: auth_headers expect(response).to have_http_status(:ok) body = JSON.parse(response.body) expect(body).to include("email" => user.email, "name" => "Account User", "role" => "coach") end it "requires authentication" do get "/api/v1/account" expect(response).to have_http_status(:unauthorized) end end describe "PATCH /api/v1/account" do it "updates the name" do patch "/api/v1/account", params: { name: "Nuovo Nome" }, headers: auth_headers expect(response).to have_http_status(:ok) expect(JSON.parse(response.body)["name"]).to eq("Nuovo Nome") expect(user.reload.name).to eq("Nuovo Nome") end it "rejects a blank name" do patch "/api/v1/account", params: { name: " " }, headers: auth_headers expect(response).to have_http_status(:unprocessable_entity) end end describe "PATCH /api/v1/account/password" do it "changes the password with the current password" do patch "/api/v1/account/password", params: { current_password: "Password123", password: "NewPass123", password_confirmation: "NewPass123" }, headers: auth_headers expect(response).to have_http_status(:ok) expect(user.reload.authenticate("NewPass123")).to be_truthy end it "rejects an incorrect current password" do patch "/api/v1/account/password", params: { current_password: "wrong", password: "NewPass123", password_confirmation: "NewPass123" }, headers: auth_headers expect(response).to have_http_status(:unprocessable_entity) expect(user.reload.authenticate("Password123")).to be_truthy end it "rejects a password that fails complexity rules" do patch "/api/v1/account/password", params: { current_password: "Password123", password: "newpass123", password_confirmation: "newpass123" }, headers: auth_headers.merge("Accept-Language" => "en") expect(response).to have_http_status(:unprocessable_entity) expect(JSON.parse(response.body)["error"]).to match(/3 of/i) expect(user.reload.authenticate("Password123")).to be_truthy end it "rejects reusing the current password" do patch "/api/v1/account/password", params: { current_password: "Password123", password: "Password123", password_confirmation: "Password123" }, headers: auth_headers.merge("Accept-Language" => "en") expect(response).to have_http_status(:unprocessable_entity) expect(JSON.parse(response.body)["error"]).to match(/different/i) expect(user.reload.authenticate("Password123")).to be_truthy end it "localizes password errors from Accept-Language" do patch "/api/v1/account/password", params: { current_password: "Password123", password: "Password123", password_confirmation: "Password123" }, headers: auth_headers.merge("Accept-Language" => "it") expect(response).to have_http_status(:unprocessable_entity) expect(JSON.parse(response.body)["error"]).to eq("La nuova password deve essere diversa da quella attuale") end end describe "POST /api/v1/auth/password/forgot" do it "always returns ok and sends mail when the user exists" do expect { post "/api/v1/auth/password/forgot", params: { email: user.email } }.to change { ActionMailer::Base.deliveries.size }.by(1) expect(response).to have_http_status(:ok) expect(user.reload.password_reset_digest).to be_present end it "returns the same message for unknown emails" do expect { post "/api/v1/auth/password/forgot", params: { email: "nobody@example.com" } }.not_to change { ActionMailer::Base.deliveries.size } expect(response).to have_http_status(:ok) end it "localizes the response message from Accept-Language" do post "/api/v1/auth/password/forgot", params: { email: user.email }, headers: { "Accept-Language" => "fr" } expect(response).to have_http_status(:ok) expect(JSON.parse(response.body)["message"]).to include("réinitialiser") end end end