From fd97d145cb4de8bd3a629c9aeb274877ec1686d7 Mon Sep 17 00:00:00 2001 From: Dylan Knutson Date: Sat, 16 Aug 2025 19:09:48 +0000 Subject: [PATCH] spec for showing users faving post --- ... - Show-post-file-count-in-gallery-view.md | 8 +- ...plicate-post-files-with-same-file-order.md | 10 +- .../domain/users_controller_spec.rb | 209 ++++++++++++++++++ 3 files changed, 223 insertions(+), 4 deletions(-) diff --git a/backlog/tasks/task-88 - Show-post-file-count-in-gallery-view.md b/backlog/tasks/task-88 - Show-post-file-count-in-gallery-view.md index 766b6516..4907ae36 100644 --- a/backlog/tasks/task-88 - Show-post-file-count-in-gallery-view.md +++ b/backlog/tasks/task-88 - Show-post-file-count-in-gallery-view.md @@ -14,7 +14,11 @@ dependencies: [] Users need to quickly see how many files are contained in each post when browsing the gallery view, without having to click into each post to see the details. ## Acceptance Criteria + -- [ ] #1 File count is visually displayed on each post in gallery view,Count accurately reflects the actual number of files in the post,Count is clearly visible and doesn't interfere with existing UI elements,Count updates dynamically when post files are added or removed -- [ ] #2 File count is visually displayed on each post in gallery view,Count accurately reflects the actual number of files in the post,Count is clearly visible and doesn't interfere with existing UI elements,Count updates dynamically when post files are added or removed + +- [ ] #1 File count is visually displayed on each post in gallery view +- [ ] #2 Count accurately reflects the actual number of files in the post +- [ ] #3 Count is clearly visible and doesn't interfere with existing UI elements +- [ ] #4 Count updates dynamically when post files are added or removed diff --git a/backlog/tasks/task-89 - Merge-duplicate-post-files-with-same-file-order.md b/backlog/tasks/task-89 - Merge-duplicate-post-files-with-same-file-order.md index d9353946..2f0b28d8 100644 --- a/backlog/tasks/task-89 - Merge-duplicate-post-files-with-same-file-order.md +++ b/backlog/tasks/task-89 - Merge-duplicate-post-files-with-same-file-order.md @@ -14,7 +14,13 @@ dependencies: [] Clean up duplicate post files that have the same file_order value within a post by keeping only the most recent file with a successful 200 HTTP response code. This addresses data inconsistencies from multiple scraping attempts of the same file. ## Acceptance Criteria + -- [ ] #1 System identifies posts with multiple files having same file_order,System merges duplicates keeping most recent file with 200 status code,Duplicate files are properly deleted with cascading cleanup,File associations and metadata are preserved for kept files,Process handles edge cases like no files with 200 status,Data integrity is maintained throughout merge process -- [ ] #2 System identifies posts with multiple files having same file_order,System merges duplicates keeping most recent file with 200 status code,Duplicate files are properly deleted with cascading cleanup,File associations and metadata are preserved for kept files,Process handles edge cases like no files with 200 status code,Data integrity is maintained throughout merge process + +- [ ] #1 System identifies posts with multiple files having same file_order +- [ ] #2 System merges duplicates keeping most recent file with 200 status code +- [ ] #3 Duplicate files are properly deleted with cascading cleanup +- [ ] #4 File associations and metadata are preserved for kept files +- [ ] #5 Process handles edge cases like no files with 200 status code +- [ ] #6 Data integrity is maintained throughout merge process diff --git a/spec/controllers/domain/users_controller_spec.rb b/spec/controllers/domain/users_controller_spec.rb index 26ce805b..8fb3e3ad 100644 --- a/spec/controllers/domain/users_controller_spec.rb +++ b/spec/controllers/domain/users_controller_spec.rb @@ -103,4 +103,213 @@ RSpec.describe Domain::UsersController, type: :controller do 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.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_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 end