diff --git a/blog_demo.rb b/blog_demo.rb index 3492375..540a3b0 100644 --- a/blog_demo.rb +++ b/blog_demo.rb @@ -1548,6 +1548,179 @@ Net::HTTP.start('127.0.0.1', port) do |http| unless moderator_view.body.include?('A brand new pending remark') raise "the post's own author (moderator) should see a pending comment even before approving it" end + puts "GET #{post_path} as the post's author -> sees reader2's still-pending comment (moderator sees " \ + 'everything on their own post, CommentPolicy#approve?/#reject? check the POST owner)' + + puts '-' * 40 + puts "checking AccountController#moderation (GET /account/moderation) - a cross-post moderation queue, " \ + "gathering reader2's still-pending comment onto ONE page instead of the author having to open every " \ + "post separately, and confirming add_index :posts, :user_id (db/migrate/20260907000009) plus the " \ + "existing add_index :comments, :post_id (.../20260907000005) answer it without a single full table scan" + moderation_log_offset = File.size(log_path) + moderation_queue = http.get('/account/moderation', 'Cookie' => cookie) + raise "expected GET /account/moderation to succeed, got #{moderation_queue.code}" unless moderation_queue.code == '200' + unless moderation_queue.body.include?('A brand new pending remark') && moderation_queue.body.include?('Hello from Prism') + raise "expected the moderation queue to list reader2's pending comment alongside its post's title" + end + moderation_log_lines = File.read(log_path)[moderation_log_offset..].to_s + if moderation_log_lines.include?('operation="SELECT" table="posts"') || moderation_log_lines.include?('operation="SELECT" table="comments"') + raise 'expected AccountController#moderation (current_user.posts then Comment.pending.where(post_id: ...)) ' \ + 'to resolve entirely through indexes, not a full table scan' + end + unless moderation_log_lines.include?('operation="SELECT INDEX" table="posts" columns=["user_id"]') + raise 'expected current_user.posts (owned post ids) to hit the posts.user_id index' + end + unless moderation_log_lines.include?('operation="SELECT INDEX IN" table="comments" columns=["post_id"]') + raise 'expected Comment.pending.where(post_id: owned_post_ids) to hit the comments.post_id index via an ' \ + 'IN-list lookup, not a scan' + end + puts ' GET /account/moderation -> 200, sql.prism log shows SELECT INDEX on posts.user_id then SELECT INDEX ' \ + 'IN on comments.post_id - zero full scans across both index-backed steps' + + reader2_forbidden_approve_body = URI.encode_www_form('_method' => 'PATCH', 'authenticity_token' => token) + reader2_forbidden_approve = http.post( + "/posts/#{post_id}/comments/#{pending_comment_id}/approve", reader2_forbidden_approve_body, + 'Content-Type' => 'application/x-www-form-urlencoded', 'Cookie' => pending_comment_cookie + ) + unless reader2_forbidden_approve.code == '302' && reader2_forbidden_approve['location'] == post_path + raise 'a non-owner (even the comment author themself) must not be able to approve their own comment, got ' \ + "#{reader2_forbidden_approve.code} Location=#{reader2_forbidden_approve['location']}" + end + puts "POST /posts/#{post_id}/comments/#{pending_comment_id}/approve as reader2 (not the post's author) -> " \ + "#{reader2_forbidden_approve.code} (CommentPolicy#approve? denied it, forbidden redirect back to the post)" + + approve_pending_body = URI.encode_www_form('_method' => 'PATCH', 'authenticity_token' => token) + approve_pending = http.post( + "/posts/#{post_id}/comments/#{pending_comment_id}/approve", approve_pending_body, + 'Content-Type' => 'application/x-www-form-urlencoded', 'Cookie' => cookie + ) + raise "expected the moderator's approval to redirect, got #{approve_pending.code}" unless approve_pending.code == '302' + approve_pending_cookie = cookie_from(approve_pending) + cookie = approve_pending_cookie unless approve_pending_cookie.empty? + puts "POST /posts/#{post_id}/comments/#{pending_comment_id}/approve as the post's author -> #{approve_pending.code}" + + stranger_view_after_approval = http.get(post_path) + unless stranger_view_after_approval.body.include?('A brand new pending remark') + raise "an approved comment should now be visible to a signed-out stranger too" + end + if stranger_view_after_approval.body.include?('Pending approval') + raise 'an approved comment must no longer carry the pending badge' + end + puts "GET #{post_path} signed out -> reader2's comment is now visible to everyone, no more pending badge" + + moderation_queue_after_approval = http.get('/account/moderation', 'Cookie' => cookie) + raise "expected GET /account/moderation to succeed, got #{moderation_queue_after_approval.code}" unless moderation_queue_after_approval.code == '200' + if moderation_queue_after_approval.body.include?('A brand new pending remark') + raise 'an approved comment must no longer show up in the moderation queue' + end + puts 'GET /account/moderation after approval -> the now-approved comment is no longer listed' + + puts '-' * 40 + puts 'comment moderation, the other outcome: reader2 posts a second comment and the post\'s ' \ + 'author REJECTS it instead of approving it' + second_pending_body = URI.encode_www_form( + 'comment[author]' => 'Reader2', 'comment[body]' => 'A remark that will get rejected', 'authenticity_token' => token + ) + second_pending = http.post("/posts/#{post_id}/comments", second_pending_body, + 'Content-Type' => 'application/x-www-form-urlencoded', 'Cookie' => pending_comment_cookie) + raise "expected reader2's second comment to redirect, got #{second_pending.code}" unless second_pending.code == '302' + puts "POST /posts/#{post_id}/comments as reader2 -> #{second_pending.code} (status defaults to pending again)" + + reader2_second_view = http.get(post_path, 'Cookie' => pending_comment_cookie) + unless reader2_second_view.body.include?('A remark that will get rejected') && reader2_second_view.body.include?('Pending approval') + raise "reader2 should see their own new comment pending, badge and all" + end + # posts#show orders comments by created_at desc, so this brand new + # comment is once again the first