430 lines
14 KiB
Ruby
430 lines
14 KiB
Ruby
# 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
|