From 1957df3bbd9310eadeb2f44af083f104ac62b317 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Mon, 10 Aug 2026 21:34:38 -0600 Subject: [PATCH] Fix avatars failing to be set on XMPP accounts There's a stray `end` that prematurely closes the class --- app/services/ejabberd_manager/set_avatar.rb | 4 +- .../ejabberd_manager/set_avatar_spec.rb | 104 ++++++++++++++++++ 2 files changed, 106 insertions(+), 2 deletions(-) create mode 100644 spec/services/ejabberd_manager/set_avatar_spec.rb diff --git a/app/services/ejabberd_manager/set_avatar.rb b/app/services/ejabberd_manager/set_avatar.rb index e5f8989..9c3a151 100644 --- a/app/services/ejabberd_manager/set_avatar.rb +++ b/app/services/ejabberd_manager/set_avatar.rb @@ -25,9 +25,8 @@ module EjabberdManager raise res.inspect if res.status != 200 end end - end - private + private def process_avatar @user.avatar.blob.open do |file| @@ -77,4 +76,5 @@ module EjabberdManager """.strip, ] end + end end diff --git a/spec/services/ejabberd_manager/set_avatar_spec.rb b/spec/services/ejabberd_manager/set_avatar_spec.rb new file mode 100644 index 0000000..8075f51 --- /dev/null +++ b/spec/services/ejabberd_manager/set_avatar_spec.rb @@ -0,0 +1,104 @@ +require 'rails_helper' +require 'webmock/rspec' +require 'digest' + +RSpec.describe EjabberdManager::SetAvatar, type: :model do + let(:user) { create :user, cn: "willherschel", ou: "kosmos.org" } + let(:fixture_path) { Rails.root.join("spec/fixtures/files/bender.png") } + let(:img_data) do + ImageProcessing::Vips + .source(File.open(fixture_path)) + .resize_to_fill(256, 256) + .convert("png") + .call.read + end + let(:sha1_hash) { Digest::SHA1.hexdigest(img_data) } + let(:base64_data) { Base64.strict_encode64(img_data) } + + before do + ActiveStorage::Blob.create_and_upload!( + io: File.open(fixture_path), + filename: "bender.png", + content_type: "image/png" + ).tap { |blob| user.avatar.attach(blob) } + end + + def stub_send_stanza_ok + stub_request(:post, "http://xmpp.example.com/api/send_stanza") + .to_return(status: 200, body: "", headers: {}) + end + + context "when an avatar is already set and overwrite is false" do + before do + allow(EjabberdManager::GetAvatar).to receive(:call).with(user: user).and_return({ img_base64: "abc", content_type: "image/png" }) + stub_send_stanza_ok + end + + it "does not send any stanzas" do + described_class.call(user: user) + expect(WebMock).not_to have_requested(:post, "http://xmpp.example.com/api/send_stanza") + end + end + + context "when no avatar is set yet" do + let(:sent_stanzas) { [] } + + before do + allow(EjabberdManager::GetAvatar).to receive(:call).with(user: user).and_return(nil) + stub_request(:post, "http://xmpp.example.com/api/send_stanza") + .to_return(status: 200, body: "", headers: {}) + end + + it "publishes the XEP-0084 data and metadata stanzas" do + service = described_class.new(user: user) + allow(service).to receive(:send_stanza) do |payload| + sent_stanzas << payload[:stanza] + double(status: 200) + end + + service.call + + expect(sent_stanzas.length).to eq(2) + + data_stanza = sent_stanzas.find { |s| s.include?("urn:xmpp:avatar:data") } + meta_stanza = sent_stanzas.find { |s| s.include?("urn:xmpp:avatar:metadata") } + + expect(data_stanza).to include("from='willherschel@kosmos.org'") + expect(data_stanza).to include("") + expect(data_stanza).to include("#{base64_data}") + + expect(meta_stanza).to include("from='willherschel@kosmos.org'") + expect(meta_stanza).to include("") + expect(meta_stanza).to include("bytes='#{img_data.size}'") + expect(meta_stanza).to include("id='#{sha1_hash}'") + expect(meta_stanza).to include("type='image/png'") + expect(meta_stanza).to include("height='256'") + expect(meta_stanza).to include("width='256'") + end + end + + context "when overwrite is true" do + before do + allow(EjabberdManager::GetAvatar).to receive(:call).and_return({ img_base64: "abc", content_type: "image/png" }) + stub_send_stanza_ok + end + + it "does not check for an existing avatar and sends stanzas" do + described_class.call(user: user, overwrite: true) + expect(EjabberdManager::GetAvatar).not_to have_received(:call) + expect(WebMock).to have_requested(:post, "http://xmpp.example.com/api/send_stanza").twice + end + end + + context "when the ejabberd API returns a non-200 status" do + before do + allow(EjabberdManager::GetAvatar).to receive(:call).with(user: user).and_return(nil) + stub_request(:post, "http://xmpp.example.com/api/send_stanza") + .to_return(status: 500, body: "error", headers: {}) + end + + it "raises" do + expect { described_class.call(user: user) }.to raise_error(RuntimeError) + end + end +end