feat: expand blog_demo

This commit is contained in:
dualfroz committed 2026-09-01 14:12:33 +02:00
1 parent 35141a84d5
commit 1910de414a
1 file changed
+173
+173
View File
@@ -337,6 +337,179 @@ Net::HTTP.start('127.0.0.1', port) do |http|
show_with_edited_comment = http.get(post_path, 'Cookie' => cookie)
raise 'edited comment body did not appear on the show page' unless show_with_edited_comment.body.include?('Nice post, edited!')
unless show_with_edited_comment.body.include?('changed: body') || show_with_edited_comment.body.include?('zmienione: body')
raise 'Comment#saved_changes-backed notice should name body as the changed field'
end
puts "GET #{post_path} -> comment body updated, dirty-tracking notice named the changed field"
comment_noop_body = URI.encode_www_form('comment[body]' => 'Nice post, edited!', '_method' => 'PATCH',
'authenticity_token' => token)
comment_noop = http.post(comment_edit_path, comment_noop_body,
'Content-Type' => 'application/x-www-form-urlencoded', 'Cookie' => cookie)
raise "expected the no-op comment update to redirect too, got #{comment_noop.code}" unless comment_noop.code == '302'
comment_noop_cookie = cookie_from(comment_noop)
cookie = comment_noop_cookie unless comment_noop_cookie.empty?
comment_noop_show = http.get(post_path, 'Cookie' => cookie)
unless comment_noop_show.body.include?('Nothing changed') || comment_noop_show.body.include?('Nic sie nie zmienilo')
raise 'submitting an identical comment body should report that zero fields actually changed'
end
puts "POST #{comment_edit_path} with identical body -> Comment#saved_changes was empty, notice said nothing changed"
# Prism::Orm::Locking on Comment: same "two browser tabs" scenario as the
# Post edit above, proving the mechanism (auto-detected via the
# add_lock_version_to_comments migration, zero model code) works
# identically for a second model, not just the one it was built against.
comment_tab_a_edit = http.get("#{comment_edit_path}/edit", 'Cookie' => cookie)
comment_tab_b_edit = http.get("#{comment_edit_path}/edit", 'Cookie' => cookie)
comment_tab_a_lock_version = comment_tab_a_edit.body[/name="comment\[lock_version\]"[^>]*value="([^"]*)"/, 1]
comment_tab_b_lock_version = comment_tab_b_edit.body[/name="comment\[lock_version\]"[^>]*value="([^"]*)"/, 1]
raise 'comment edit form should carry the current lock_version in a hidden field' if comment_tab_a_lock_version.to_s.empty?
unless comment_tab_a_lock_version == comment_tab_b_lock_version
raise 'both comment tabs should have observed the same lock_version before either saved'
end
comment_tab_a_save_body = URI.encode_www_form(
'comment[body]' => 'Nice post, tab A won!', 'comment[lock_version]' => comment_tab_a_lock_version,
'_method' => 'PATCH', 'authenticity_token' => token
)
comment_tab_a_save = http.post(comment_edit_path, comment_tab_a_save_body,
'Content-Type' => 'application/x-www-form-urlencoded', 'Cookie' => cookie)
raise "expected comment tab A's save to succeed, got #{comment_tab_a_save.code}" unless comment_tab_a_save.code == '302'
comment_tab_a_cookie = cookie_from(comment_tab_a_save)
cookie = comment_tab_a_cookie unless comment_tab_a_cookie.empty?
puts "POST #{comment_edit_path} tab A (lock_version=#{comment_tab_a_lock_version}) -> #{comment_tab_a_save.code}, saved first"
comment_tab_b_save_body = URI.encode_www_form(
'comment[body]' => 'Nice post, tab B lost!', 'comment[lock_version]' => comment_tab_b_lock_version,
'_method' => 'PATCH', 'authenticity_token' => token
)
comment_tab_b_save = http.post(comment_edit_path, comment_tab_b_save_body,
'Content-Type' => 'application/x-www-form-urlencoded', 'Cookie' => cookie)
unless comment_tab_b_save.code == '409'
raise "expected comment tab B's stale save to be rejected with 409, got #{comment_tab_b_save.code}"
end
unless comment_tab_b_save.body.include?('Someone else changed this comment') ||
comment_tab_b_save.body.include?('Ktos inny zmienil ten komentarz')
raise 'the 409 response should render the comment stale-conflict notice'
end
puts "POST #{comment_edit_path} tab B (stale lock_version=#{comment_tab_b_lock_version}) -> " \
"#{comment_tab_b_save.code} (expected 409, StaleObjectError caught)"
after_comment_conflict = http.get(post_path, 'Cookie' => cookie)
unless after_comment_conflict.body.include?('Nice post, tab A won!')
raise "tab B's rejected comment write must not have overwritten tab A's body"
end
raise "tab B's rejected comment write must not have leaked through" if after_comment_conflict.body.include?('tab B lost')
puts "GET #{post_path} -> tab A's comment body survives untouched, Prism::Orm::Locking prevented the lost update"
# Comment's enum :status defaults every new comment to "pending" (column
# default 0), so this comment - created way back before this feature
# existed in the script - is STILL pending right now. The moderation
# visibility scene further below (reader2, then an anonymous visitor)
# depends on it already being approved by the time they look, same as
# any real moderator would approve a comment before strangers see it.
raise "the pending badge should show for the comment's own author before it's approved" unless after_comment_conflict.body.include?('Pending approval')
approve_body = URI.encode_www_form('_method' => 'PATCH', 'authenticity_token' => token)
approve_comment = http.post("#{comment_edit_path}/approve", approve_body,
'Content-Type' => 'application/x-www-form-urlencoded', 'Cookie' => cookie)
raise "expected the comment approval to redirect, got #{approve_comment.code}" unless approve_comment.code == '302'
approve_comment_cookie = cookie_from(approve_comment)
cookie = approve_comment_cookie unless approve_comment_cookie.empty?
approved_show = http.get(post_path, 'Cookie' => cookie)
raise 'an approved comment should no longer show the pending badge' if approved_show.body.include?('Pending approval')
puts "POST #{comment_edit_path}/approve -> #{approve_comment.code} (Comment#status: pending -> approved, " \
'Comment enum :status + CommentPolicy#approve? checked the POST owner, not the comment author)'
fresh_show = http.get(post_path, 'Cookie' => cookie)
etag = fresh_show['etag']
raise 'posts#show should set an ETag now that it uses fresh_when' unless etag
puts "GET #{post_path} -> #{fresh_show.code}, ETag=#{etag}"
conditional_show = http.get(post_path, 'Cookie' => cookie, 'If-None-Match' => etag)
puts "GET #{post_path} with If-None-Match: #{etag} -> #{conditional_show.code}"
raise "expected a fresh conditional GET to return 304, got #{conditional_show.code}" unless conditional_show.code == '304'
raise '304 response must not carry a body' unless conditional_show.body.to_s.empty?
second_comment_body = URI.encode_www_form(
'comment[author]' => 'Another reader', 'comment[body]' => 'Me too!', 'authenticity_token' => token
)
second_comment = http.post("/posts/#{post_id}/comments", second_comment_body,
'Content-Type' => 'application/x-www-form-urlencoded', 'Cookie' => cookie)
raise "expected the second comment to redirect, got #{second_comment.code}" unless second_comment.code == '302'
second_comment_cookie = cookie_from(second_comment)
cookie = second_comment_cookie unless second_comment_cookie.empty?
now_stale = http.get(post_path, 'Cookie' => cookie, 'If-None-Match' => etag)
puts "GET #{post_path} with the OLD If-None-Match after a new comment -> #{now_stale.code}"
unless now_stale.code == '200' && now_stale.body.include?('Me too!')
raise "expected the stale ETag to be rejected and the fresh page (with the new comment) rendered, got #{now_stale.code}"
end
raise 'the fresh render must carry a NEW ETag, not the stale one' if now_stale['etag'] == etag
puts "GET #{post_path} -> #{now_stale.code} (ETag changed after a comment was added, Prism::ConditionalGet is content-aware)"
json_index = http.get('/posts.json')
puts "GET /posts.json -> #{json_index.code} #{json_index['content-type']}"
parsed = JSON.parse(json_index.body)
raise 'json index did not include the new post' unless parsed.any? { |row| row['title'] == 'Hello from Prism' }
puts '-' * 40
puts 'CORS: Prism::Middleware::Cors + Prism::Cors, matched against the .json API surface only - a ' \
'public read-only rule for any origin, and a credentialed read/write rule for one trusted admin origin'
public_client_origin = 'https://blog-widget.example.net'
cors_public_get = http.get('/posts.json', 'Origin' => public_client_origin)
unless cors_public_get.code == '200' && cors_public_get['access-control-allow-origin'] == public_client_origin
raise "expected a public GET /posts.json to echo Access-Control-Allow-Origin: #{public_client_origin}, " \
"got #{cors_public_get['access-control-allow-origin'].inspect}"
end
if cors_public_get['access-control-allow-credentials']
raise 'a public (non-credentialed) CORS resource must not set Access-Control-Allow-Credentials'
end
puts "GET /posts.json with Origin: #{public_client_origin} -> #{cors_public_get.code}, " \
"Access-Control-Allow-Origin=#{cors_public_get['access-control-allow-origin']} (any origin can read)"
cors_public_write_preflight = http.options('/posts', 'Origin' => public_client_origin,
'Access-Control-Request-Method' => 'POST')
unless cors_public_write_preflight.code == '403'
raise "expected the public origin's write preflight to be rejected, got #{cors_public_write_preflight.code}"
end
puts "OPTIONS /posts (preflight for POST) with Origin: #{public_client_origin} -> " \
"#{cors_public_write_preflight.code} (the public rule only allows GET, POST is refused before it " \
'ever reaches the router)'
admin_origin = 'https://admin.blog-demo.internal'
cors_admin_preflight = http.options('/posts', 'Origin' => admin_origin,
'Access-Control-Request-Method' => 'POST',
'Access-Control-Request-Headers' => 'Content-Type, X-CSRF-Token')
unless cors_admin_preflight.code == '204' && cors_admin_preflight['access-control-allow-credentials'] == 'true'
raise "expected the trusted admin origin's preflight to succeed with credentials allowed, got " \
"#{cors_admin_preflight.code}"
end
unless cors_admin_preflight['access-control-allow-methods'].to_s.include?('POST')
raise 'expected the preflight response to advertise POST among the allowed methods'
end
puts "OPTIONS /posts (preflight for POST) with Origin: #{admin_origin} -> #{cors_admin_preflight.code}, " \
"Allow-Methods=#{cors_admin_preflight['access-control-allow-methods']}, Allow-Credentials=true " \
'(a second, more privileged CORS rule for the trusted admin dashboard origin)'
cors_unrelated_path = http.get('/', 'Origin' => public_client_origin)
if cors_unrelated_path['access-control-allow-origin']
raise 'a path with no configured CORS resource must not carry any Access-Control-* headers, even with ' \
'an Origin header present'
end
puts "GET / with Origin: #{public_client_origin} -> #{cors_unrelated_path.code}, no Access-Control-* " \
'headers (the homepage is not a registered CORS resource, so the middleware passes it straight through)'
puts '-' * 40
puts 'JSON:API-lite validation/conflict errors (Prism::Orm::Errors#to_jsonapi + ' \
'Renderable#render_errors) - PostsController#create/#update now respond_to format.json ' \
'on both the happy and the failure path, not just posts#index'
json_create_body = URI.encode_www_form(
'post[title]' => 'JSON API post', 'post[body]' => 'Created via the .json format.',
'authenticity_token' => token
)
json_create = http.post('/posts.json', json_create_body,
'Content-Type' => 'application/x-www-form-urlencoded', 'Cookie' => cookie)
end
server.stop