# typed: false require "rails_helper" RSpec.describe Domain::UsersController, type: :controller do render_views # Create a real user with admin role for tests that require authentication let(:admin_user) { create(:user, :admin) } # Shared examples for common show behavior across all user types shared_examples "show action for user type" do |user_factory, param_prefix, param_attr| let(:domain_user) { create(user_factory) } let(:composite_param) { "#{param_prefix}@#{domain_user.send(param_attr)}" } context "when user exists" do it "returns a successful response" do get :show, params: { id: composite_param } expect(response).to be_successful end it "renders the show template" do get :show, params: { id: composite_param } expect(response).to render_template(:show) end it "sets the @user instance variable" do get :show, params: { id: composite_param } expect(assigns(:user)).to eq(domain_user) end it "authorizes the user" do expect(controller).to receive(:authorize).with(domain_user) get :show, params: { id: composite_param } end it "does not require authentication" do # Ensure no user is signed in sign_out :user get :show, params: { id: composite_param } expect(response).to be_successful end end context "when user does not exist" do let(:invalid_param) { "#{param_prefix}@nonexistent" } it "raises ActiveRecord::RecordNotFound" do expect { get :show, params: { id: invalid_param } }.to raise_error( ActiveRecord::RecordNotFound, ) end end context "with invalid composite parameter format" do it "raises ActionController::BadRequest for malformed param" do expect { get :show, params: { id: "invalid_format" } }.to raise_error( ActionController::BadRequest, /invalid id/, ) end it "raises ActionController::BadRequest for unknown model type" do expect { get :show, params: { id: "unknown@test" } }.to raise_error( ActionController::BadRequest, /unknown model type/, ) end end end describe "GET #show" do before do # Mock authorization to allow all actions for these tests allow(controller).to receive(:authorize).and_return(true) end context "for Domain::User::FaUser" do include_examples "show action for user type", :domain_user_fa_user, "fa", :url_name end context "for Domain::User::E621User" do include_examples "show action for user type", :domain_user_e621_user, "e621", :name end context "for Domain::User::InkbunnyUser" do include_examples "show action for user type", :domain_user_inkbunny_user, "ib", :name end context "param configuration" do it "uses the correct param config for user_id_param" do param_config = described_class.param_config expect(param_config.user_id_param).to eq(:id) end end end describe "GET #users_faving_post" do before do # Mock authorization to allow all actions for these tests allow(controller).to receive(:authorize).and_return(true) end # Shared examples for common users_faving_post behavior across all post types shared_examples "users_faving_post action for post type" do |post_factory, param_prefix, param_attr| let(:domain_post) { create(post_factory) } let(:composite_param) do "#{param_prefix}@#{domain_post.send(param_attr)}" end context "when post exists" do context "with no faving users" do it "returns a successful response" do get :users_faving_post, params: { domain_post_id: composite_param } expect(response).to be_successful end it "renders the index template" do get :users_faving_post, params: { domain_post_id: composite_param } expect(response).to render_template(:index) end it "sets the @post instance variable" do get :users_faving_post, params: { domain_post_id: composite_param } expect(assigns(:post)).to eq(domain_post) end it "sets the @index_type to users_faving_post" do get :users_faving_post, params: { domain_post_id: composite_param } expect(assigns(:index_type)).to eq(:users_faving_post) end it "sets empty @users collection" do get :users_faving_post, params: { domain_post_id: composite_param } expect(assigns(:users)).to be_empty end it "authorizes the post" do expect(controller).to receive(:authorize).with(domain_post) get :users_faving_post, params: { domain_post_id: composite_param } end end context "with faving users" do before { setup_faving_users } it "returns users who have faved the post" do get :users_faving_post, params: { domain_post_id: composite_param } expect(assigns(:users)).to match_array(expected_faving_users) end it "includes avatar associations" do get :users_faving_post, params: { domain_post_id: composite_param } # Test that the query includes avatar associations (won't cause N+1) expect(assigns(:users).first.association(:avatar)).to be_loaded end end end context "when post does not exist" do let(:invalid_param) { "#{param_prefix}@999999" } it "raises ActiveRecord::RecordNotFound" do expect { get :users_faving_post, params: { domain_post_id: invalid_param } }.to raise_error(ActiveRecord::RecordNotFound) end end context "with invalid composite parameter format" do it "raises ActionController::BadRequest for malformed param" do expect { get :users_faving_post, params: { domain_post_id: "invalid_format" } }.to raise_error(ActionController::BadRequest, /invalid id/) end it "raises ActionController::BadRequest for unknown model type" do expect { get :users_faving_post, params: { domain_post_id: "unknown@test" } }.to raise_error(ActionController::BadRequest, /unknown model type/) end end end context "for Domain::Post::FaPost" do let(:faving_user1) { create(:domain_user_fa_user) } let(:faving_user2) { create(:domain_user_fa_user) } let(:expected_faving_users) { [faving_user1, faving_user2] } def setup_faving_users # Create FA-specific user-post-fav relationships Domain::UserPostFav::FaUserPostFav.create!( user: faving_user1, post: domain_post, ) Domain::UserPostFav::FaUserPostFav.create!( user: faving_user2, post: domain_post, ) end include_examples "users_faving_post action for post type", :domain_post_fa_post, "fa", :fa_id end context "for Domain::Post::E621Post" do let(:faving_user1) { create(:domain_user_e621_user) } let(:faving_user2) { create(:domain_user_e621_user) } let(:expected_faving_users) { [faving_user1, faving_user2] } def setup_faving_users # Create E621-specific user-post-fav relationships Domain::UserPostFav::E621UserPostFav.create!( user: faving_user1, post: domain_post, ) Domain::UserPostFav::E621UserPostFav.create!( user: faving_user2, post: domain_post, ) end include_examples "users_faving_post action for post type", :domain_post_e621_post, "e621", :e621_id end context "for Domain::Post::InkbunnyPost" do let(:faving_user1) { create(:domain_user_inkbunny_user) } let(:faving_user2) { create(:domain_user_inkbunny_user) } let(:expected_faving_users) { [faving_user1, faving_user2] } def setup_faving_users # Create Inkbunny-specific user-post-fav relationships Domain::UserPostFav.create!(user: faving_user1, post: domain_post) Domain::UserPostFav.create!(user: faving_user2, post: domain_post) end include_examples "users_faving_post action for post type", :domain_post_inkbunny_post, "ib", :ib_id end context "for Domain::Post::BlueskyPost" do let(:faving_user_with_display_name) do create(:domain_user_bluesky_user, display_name: "Cool Artist") end let(:faving_user_without_display_name) do create(:domain_user_bluesky_user, display_name: nil) end let(:expected_faving_users) do [faving_user_with_display_name, faving_user_without_display_name] end def setup_faving_users # Create Bluesky-specific user-post-fav relationships Domain::UserPostFav.create!( user: faving_user_with_display_name, post: domain_post, ) Domain::UserPostFav.create!( user: faving_user_without_display_name, post: domain_post, ) end include_examples "users_faving_post action for post type", :domain_post_bluesky_post, "bsky", :rkey context "with display name variations" do before do # Create Bluesky-specific user-post-fav relationships Domain::UserPostFav.create!( user: faving_user_with_display_name, post: domain_post, ) Domain::UserPostFav.create!( user: faving_user_without_display_name, post: domain_post, ) end it "includes users with display names" do get :users_faving_post, params: { domain_post_id: "bsky@#{domain_post.rkey}", } users = assigns(:users) user_with_display = users.find { |u| u.display_name.present? } expect(user_with_display.display_name).to eq("Cool Artist") end it "includes users without display names" do get :users_faving_post, params: { domain_post_id: "bsky@#{domain_post.rkey}", } users = assigns(:users) user_without_display = users.find { |u| u.display_name.blank? } expect(user_without_display.display_name).to be_blank expect(user_without_display.handle).to be_present end end end end describe "POST #monitor_bluesky_user" do let(:bluesky_user) { create(:domain_user_bluesky_user) } let(:composite_param) { "bsky@#{bluesky_user.handle}" } before do # Sign in admin user for authentication sign_in admin_user # Mock authorization to allow the action allow(controller).to receive(:authorize).and_return(true) # Set @user instance variable as the controller does in find_user controller.instance_variable_set(:@user, bluesky_user) end context "when monitoring succeeds" do it "creates a monitored object" do expect { post :monitor_bluesky_user, params: { id: composite_param } }.to change(Domain::Bluesky::MonitoredObject, :count).by(1) monitor = Domain::Bluesky::MonitoredObject.last expect(monitor.value).to eq(bluesky_user.did) expect(monitor.kind).to eq("user_did") end it "enqueues scan user job" do post :monitor_bluesky_user, params: { id: composite_param } enqueued_jobs = SpecUtil.enqueued_job_args(Domain::Bluesky::Job::ScanUserJob) expect(enqueued_jobs).to contain_exactly( hash_including(user: bluesky_user), ) end it "enqueues scan posts job" do post :monitor_bluesky_user, params: { id: composite_param } enqueued_jobs = SpecUtil.enqueued_job_args(Domain::Bluesky::Job::ScanPostsJob) expect(enqueued_jobs).to contain_exactly( hash_including(user: bluesky_user), ) end it "sets success flash message" do post :monitor_bluesky_user, params: { id: composite_param } expect(flash[:notice]).to eq("User is now being monitored") end it "redirects to user page" do post :monitor_bluesky_user, params: { id: composite_param } expect(response).to redirect_to(domain_user_path(bluesky_user)) end it "authorizes the user" do expect(controller).to receive(:authorize).with(bluesky_user) post :monitor_bluesky_user, params: { id: composite_param } end end context "when monitoring fails due to validation errors" do before do # Create existing monitor to trigger uniqueness validation failure Domain::Bluesky::MonitoredObject.create!( value: bluesky_user.did, kind: :user_did, ) end it "does not create a new monitored object" do expect { post :monitor_bluesky_user, params: { id: composite_param } }.not_to change(Domain::Bluesky::MonitoredObject, :count) end it "fails to save the monitor due to uniqueness" do monitor = Domain::Bluesky::MonitoredObject.build_for_user(bluesky_user) expect(monitor.save).to be_falsey expect(monitor.errors[:value]).to include("has already been taken") end it "does not enqueue any jobs" do SpecUtil.clear_enqueued_jobs! post :monitor_bluesky_user, params: { id: composite_param } expect( SpecUtil.enqueued_job_args(Domain::Bluesky::Job::ScanUserJob), ).to be_empty expect( SpecUtil.enqueued_job_args(Domain::Bluesky::Job::ScanPostsJob), ).to be_empty end it "sets error flash message with validation errors" do post :monitor_bluesky_user, params: { id: composite_param } expect(flash[:alert]).to match( /Error monitoring user: .*has already been taken/, ) end it "redirects to user page" do post :monitor_bluesky_user, params: { id: composite_param } expect(response).to redirect_to(domain_user_path(bluesky_user)) end end end end